Compare commits

...

2612 commits

Author SHA1 Message Date
Marc-Aurel Zent
13500264b7 Use iced to create AsmHooks in PapRewriter.
Some checks failed
.NET Build / build (push) Has been cancelled
2025-12-22 15:31:12 +01:00
Actions User
6ba735eefb [CI] Updating repo.json for 1.5.1.12
Some checks failed
.NET Build / build (push) Has been cancelled
2025-12-20 20:53:36 +00:00
Ottermandias
73f02851a6 Cherry pick API support for other block compression types from Luna branch. 2025-12-20 21:51:40 +01:00
Actions User
069323cfb8 [CI] Updating repo.json for 1.5.1.11
Some checks are pending
.NET Build / build (push) Waiting to run
2025-12-20 14:38:27 +00:00
Ottermandias
9aa566f521 Fix typo in new IPC providers. 2025-12-20 15:36:11 +01:00
Ottermandias
eff3784a85 Fix multi-release bug in texturearrayslicer. 2025-12-20 15:36:03 +01:00
Ottermandias
9cf7030f87 ...
Some checks failed
.NET Build / build (push) Has been cancelled
2025-12-19 01:13:08 +01:00
Actions User
deb3686df5 [CI] Updating repo.json for 1.5.1.9 2025-12-19 00:12:44 +00:00
Ottermandias
953f243caf . 2025-12-19 01:08:18 +01:00
Ottermandias
59fec5db82 Needs both versions for now due to flatsharp? 2025-12-19 01:05:50 +01:00
Ottermandias
37f3044376 Update dotnet. 2025-12-19 00:56:50 +01:00
Ottermandias
fb299d71f0 Remove unimplemented ipc. 2025-12-19 00:54:09 +01:00
Ottermandias
dbcb2e38ec Merge remote-tracking branch 'Exter-N/settings-sections' 2025-12-19 00:51:45 +01:00
Ottermandias
ebcbc5d98a Update SDK. 2025-12-19 00:51:39 +01:00
Ottermandias
febced0708 Fix bug in slicer. 2025-12-19 00:47:19 +01:00
Ottermandias
4c8ff40821 Fix private Unks.
Some checks are pending
.NET Build / build (push) Waiting to run
2025-12-18 20:47:49 +01:00
Ottermandias
7717251c6a Update to TerraFX. 2025-12-18 20:45:15 +01:00
Ottermandias
3e7511cb34 Update SDK.
Some checks are pending
.NET Build / build (push) Waiting to run
2025-12-17 18:33:10 +01:00
Karou
ccb5b01290 Api version bump and remove redundant framework thread call
Some checks failed
.NET Build / build (push) Has been cancelled
2025-12-05 13:39:19 +01:00
Actions User
5dd74297c6 [CI] Updating repo.json for 1.5.1.8
Some checks failed
.NET Build / build (push) Has been cancelled
2025-11-28 22:10:17 +00:00
Exter-N
338e3bc1a5 Update Penumbra.Api 2025-11-20 18:41:32 +01:00
Exter-N
e240a42a2c Replace GetPlugin(Delegate) stub by actual implementation 2025-11-13 19:55:32 +01:00
Exter-N
5be021b0eb Add integration settings sections 2025-11-13 19:53:50 +01:00
Karou
ce54aa5d25 Added IPC call to allow for redrawing only members of specified collections
Some checks failed
.NET Build / build (push) Has been cancelled
2025-11-03 15:15:40 +01:00
Actions User
c4b6e4e00b [CI] Updating repo.json for testing_1.5.1.7
Some checks failed
.NET Build / build (push) Has been cancelled
2025-10-23 21:50:20 +00:00
Ottermandias
912c183fc6 Improve file watcher. 2025-10-23 23:45:20 +02:00
Ottermandias
5bf901d0c4 Update actorobjectmanager when setting cutscene index.
Some checks are pending
.NET Build / build (push) Waiting to run
2025-10-23 17:30:29 +02:00
Ottermandias
cbedc878b9 Slight cleanup and autoformat. 2025-10-22 21:56:16 +02:00
Ottermandias
c8cf560fc1 Merge branch 'refs/heads/StoiaCode/fileWatcher' 2025-10-22 21:48:42 +02:00
Stoia
f05cb52da2 Add Option to notify instead of auto install.
And General Fixes
2025-10-22 18:20:44 +02:00
Ottermandias
7ed81a9823 Update OtterGui.
Some checks are pending
.NET Build / build (push) Waiting to run
2025-10-22 17:53:02 +02:00
Stoia
60aa23efcd
Merge branch 'xivdev:master' into fileWatcher 2025-10-22 14:28:08 +02:00
Ottermandias
ebbe957c95 Remove login screen log spam.
Some checks failed
.NET Build / build (push) Has been cancelled
2025-10-11 20:13:51 +02:00
Actions User
300e0e6d84 [CI] Updating repo.json for 1.5.1.6
Some checks failed
.NET Build / build (push) Has been cancelled
2025-10-07 10:45:04 +00:00
Ottermandias
049baa4fe4 Again. 2025-10-07 12:42:54 +02:00
Ottermandias
0881dfde8a Update signatures. 2025-10-07 12:27:35 +02:00
Actions User
23c0506cb8 [CI] Updating repo.json for testing_1.5.1.5
Some checks failed
.NET Build / build (push) Has been cancelled
2025-09-28 10:43:01 +00:00
Ottermandias
699745413e Make priority an int. 2025-09-28 12:40:52 +02:00
Actions User
eb53f04c6b [CI] Updating repo.json for testing_1.5.1.4
Some checks are pending
.NET Build / build (push) Waiting to run
2025-09-27 12:03:35 +00:00
Ottermandias
c6b596169c Add default constructor. 2025-09-27 14:01:21 +02:00
Actions User
a0c3e820b0 [CI] Updating repo.json for testing_1.5.1.3
Some checks are pending
.NET Build / build (push) Waiting to run
2025-09-27 11:02:39 +00:00
Ottermandias
a59689ebfe CS API update and add http API routes. 2025-09-27 13:00:18 +02:00
Exter-N
e9f67a009b Lift "shaders known" restriction for saving materials
Some checks failed
.NET Build / build (push) Has been cancelled
2025-09-19 11:18:39 +02:00
Ottermandias
97c8d82b33 Prevent default-named collection from being renamed and always put it at the top of the selector.
Some checks failed
.NET Build / build (push) Has been cancelled
2025-09-07 10:45:28 +02:00
Stoia
c3b00ff426 Integrate FileWatcher
HEAVY WIP
2025-09-06 14:22:18 +02:00
Actions User
6348c4a639 [CI] Updating repo.json for 1.5.1.2
Some checks failed
.NET Build / build (push) Has been cancelled
2025-09-02 14:25:55 +00:00
Ottermandias
5a6e06df3b git is stupid 2025-09-02 16:22:02 +02:00
Ottermandias
f5f6dd3246 Handle some TODOs. 2025-09-02 16:12:01 +02:00
Ottermandias
4e788f7c2b Update sig. 2025-09-02 11:51:59 +02:00
Ottermandias
ad1659caf6 Update libraries. 2025-09-02 11:29:58 +02:00
Ottermandias
18a6ce2a5f Merge branch 'refs/heads/Exter-N/cldapi'
Some checks are pending
.NET Build / build (push) Waiting to run
2025-09-01 15:59:26 +02:00
Ottermandias
e68e821b2a Merge branch 'master' into Exter-N/cldapi 2025-09-01 15:58:22 +02:00
Ottermandias
96764b34ca Merge branch 'refs/heads/Exter-N/restree-stuff' 2025-09-01 15:57:06 +02:00
Exter-N
2cf60b78cd Reject and warn about cloud-synced base directories 2025-08-31 06:42:45 +02:00
Exter-N
d59be1e660 Refine IsCloudSynced 2025-08-31 05:25:37 +02:00
Exter-N
5503bb32e0 CloudApi testing in Debug tab 2025-08-31 04:13:56 +02:00
Exter-N
f3ec4b2e08 Only display the file name and last dir for externals 2025-08-30 19:19:07 +02:00
Exter-N
b3379a9710 Stop redacting external paths 2025-08-30 16:55:20 +02:00
Exter-N
8c25ef4b47 Make the save button ResourceTreeViewer baseline 2025-08-30 16:53:12 +02:00
Ottermandias
912020cc3f Update for staging and wrong tooltip.
Some checks failed
.NET Build / build (push) Has been cancelled
2025-08-29 16:36:42 +02:00
Ottermandias
be8987a451 Merge branch 'master' of github.com:xivDev/Penumbra
Some checks are pending
.NET Build / build (push) Waiting to run
2025-08-28 18:52:29 +02:00
Ottermandias
f7cf5503bb Fix deleting PCP collections. 2025-08-28 18:52:06 +02:00
Ottermandias
a04a5a071c Add warning in file redirections if extension doesn't match. 2025-08-28 18:51:57 +02:00
Actions User
71e24c13c7 [CI] Updating repo.json for 1.5.1.0
Some checks failed
.NET Build / build (push) Has been cancelled
2025-08-25 08:39:42 +00:00
Ottermandias
c0120f81af 1.5.1.0 2025-08-25 10:37:38 +02:00
Ottermandias
da47c19aeb Woops, increment version. 2025-08-25 10:25:05 +02:00
Actions User
e16800f216 [CI] Updating repo.json for testing_1.5.0.10 2025-08-25 08:16:04 +00:00
Ottermandias
79a4fc5904 Fix wrong logging. 2025-08-25 10:13:48 +02:00
Ottermandias
bf90725dd2 Fix resolvecontext issue. 2025-08-25 10:13:39 +02:00
Ottermandias
a14347f73a Update temporary collection creation. 2025-08-25 10:13:31 +02:00
Actions User
1e07e43498 [CI] Updating repo.json for testing_1.5.0.9
Some checks are pending
.NET Build / build (push) Waiting to run
2025-08-24 13:51:43 +00:00
Ottermandias
f51f8a7bf8 Try to filter meta entries for relevance. 2025-08-24 15:24:57 +02:00
Exter-N
1fca78fa71 Add Kdb files to ResourceTree 2025-08-24 14:09:02 +02:00
Exter-N
c8b6325a87 Add game integrity message to On-Screen 2025-08-24 14:06:39 +02:00
Ottermandias
6079103505 Add collection PCP settings.
Some checks are pending
.NET Build / build (push) Waiting to run
2025-08-23 14:46:27 +02:00
Actions User
d302a17f1f [CI] Updating repo.json for testing_1.5.0.8
Some checks are pending
.NET Build / build (push) Waiting to run
2025-08-22 18:33:43 +00:00
Ottermandias
0d64384059 Add cleanup buttons to PCP, add option to turn off PCP IPC. 2025-08-22 20:31:40 +02:00
Ottermandias
10894d451a Add Pcp Events. 2025-08-22 18:08:22 +02:00
Actions User
fb34238530 [CI] Updating repo.json for testing_1.5.0.7 2025-08-22 13:51:50 +00:00
Ottermandias
8043e6fb6b Add option to disable PCP. 2025-08-22 15:49:15 +02:00
Ottermandias
e3b7f72893 Add initial PCP. 2025-08-22 15:44:33 +02:00
Ottermandias
b7f326e29c Fix bug with collection setting and empty collection. 2025-08-22 15:43:55 +02:00
Ottermandias
dad01e1af8 Update GameData. 2025-08-20 15:24:00 +02:00
Ottermandias
10b71930a1 Merge branch 'refs/heads/Exter-N/stockings-skin-slot' 2025-08-18 15:41:22 +02:00
Ottermandias
23257f94a4 Some cleanup and add option to disable skin material attribute scanning. 2025-08-18 15:41:10 +02:00
Ottermandias
83a36ed4cb Merge branch 'master' into Exter-N/stockings-skin-slot 2025-08-18 15:31:52 +02:00
Ottermandias
8304579d29 Add predefined tags to the multi mod selector. 2025-08-17 13:54:36 +02:00
Actions User
24cbc6c5e1 [CI] Updating repo.json for 1.5.0.6 2025-08-17 08:46:26 +00:00
Exter-N
41edc23820 Allow changing the skin mtrl suffix 2025-08-17 03:11:11 +02:00
Exter-N
aa920b5e9b Fix ImGui texture usage issue 2025-08-17 01:41:49 +02:00
Ottermandias
87ace28bcf Update OtterGui. 2025-08-16 11:56:24 +02:00
Ottermandias
5917f5fad1 Small fixes. 2025-08-13 17:42:45 +02:00
Actions User
f69c264317 [CI] Updating repo.json for 1.5.0.5 2025-08-13 14:53:11 +00:00
Ottermandias
a7246b9d98 Add PBD Post-Processor that appends EPBD data if the loaded PBD does not contain it. 2025-08-13 16:50:26 +02:00
Actions User
9aff388e21 [CI] Updating repo.json for 1.5.0.4 2025-08-12 12:53:33 +00:00
Ottermandias
091aff1b8a Merge branch 'master' of github.com:xivDev/Penumbra 2025-08-12 14:47:50 +02:00
Ottermandias
9f8185f67b Add new parameter to LoadWeapon hook. 2025-08-12 14:47:35 +02:00
Actions User
b112d75a27 [CI] Updating repo.json for 1.5.0.3 2025-08-12 10:31:13 +00:00
Ottermandias
7af81a6c18 Fix issue with removing default metadata. 2025-08-12 12:29:09 +02:00
Ottermandias
12a218bb2b Protect against empty requested paths. 2025-08-12 12:28:56 +02:00
Ottermandias
f6bac93db7 Update ChangedEquipData. 2025-08-11 19:58:24 +02:00
Actions User
155d3d49aa [CI] Updating repo.json for 1.5.0.2 2025-08-09 16:40:42 +00:00
Exter-N
9aae2210a2 Fix nullptr crashes 2025-08-09 14:57:15 +02:00
Actions User
3785a629ce [CI] Updating repo.json for 1.5.0.1 2025-08-09 11:03:24 +00:00
Ottermandias
02af52671f Need staging again ... 2025-08-09 13:00:40 +02:00
Ottermandias
391c9d727e Fix shifted timeline vfunc offset. 2025-08-09 12:51:39 +02:00
Ottermandias
ff2b2be953 Fix popups not working early. 2025-08-09 12:11:29 +02:00
Ottermandias
6242b30f93 Fix resizable child. 2025-08-09 11:58:35 +02:00
Exter-N
11cd08a9de ClientStructs-ify stuff 2025-08-09 10:29:29 +02:00
Ottermandias
46cfbcb115 Set Repo API level to 13 and remove stg from future releases. 2025-08-08 23:13:23 +02:00
Actions User
66543cc671 [CI] Updating repo.json for 1.5.0.0 2025-08-08 21:12:00 +00:00
Ottermandias
13283c9690 Fix dumb. 2025-08-08 23:08:26 +02:00
Ottermandias
bedfb22466 Use staging for release. 2025-08-08 23:04:50 +02:00
Ottermandias
13df8b2248 Update gamedata. 2025-08-08 23:02:22 +02:00
Ottermandias
93406e4d4e 1.5.0.0 2025-08-08 16:17:59 +02:00
Ridan Vandenbergh
8140d08557 Add vertex material types for usages of 2 colour attributes 2025-08-08 16:15:19 +02:00
Ridan Vandenbergh
2b36f39848 Fix basecolor texture in material export 2025-08-08 16:12:37 +02:00
Ottermandias
a69811800d Update GameData 2025-08-08 15:56:25 +02:00
Ottermandias
3f18ad50de Initial API13 / 7.3 update. 2025-08-08 00:45:24 +02:00
Ridan Vandenbergh
6689e326ee Material tab: disallow "Enable Transparency" for stockings shader 2025-08-02 00:38:24 +02:00
Passive
bdcab22a55 Cleanup methods to extension class 2025-08-02 00:16:55 +02:00
Passive
f5f4fe7259 Invalid tangent fix example 2025-08-02 00:16:55 +02:00
Sebastina
898963fea5 Allow focusing a specified mod via HTTP API under the mods tab. 2025-08-02 00:16:38 +02:00
Ottermandias
8527bfa29c Fix missing updates for OtterGui. 2025-08-02 00:13:35 +02:00
Ottermandias
baca3cdec2 Update Libs. 2025-08-02 00:08:09 +02:00
Ottermandias
dc93eba34c Add initial complex group things. 2025-08-02 00:06:25 +02:00
Ottermandias
012052daa0 Change behavior for directory names. 2025-08-02 00:06:03 +02:00
Ottermandias
a9546e31ee Update packages. 2025-08-02 00:05:27 +02:00
Actions User
a4a6283e7b [CI] Updating repo.json for testing_1.4.0.6 2025-07-14 15:12:06 +00:00
Ottermandias
00c02fd16e Fix tex file migration for small textures. 2025-07-14 17:09:07 +02:00
Ottermandias
140d150bb4 Fix character sound data. 2025-07-14 17:08:46 +02:00
Actions User
49a6d935f3 [CI] Updating repo.json for testing_1.4.0.5 2025-07-05 20:11:28 +00:00
Ottermandias
692beacc2e Merge remote-tracking branch 'Exter-N/human-skin-materials' 2025-07-05 22:04:48 +02:00
Ottermandias
a953febfba Add support for imc-toggle attributes to accessories, and fix up attributes when item swapping models. 2025-07-05 22:03:32 +02:00
Ottermandias
c0aa2e36ea Merge branch 'refs/heads/Exter-N/reslogger-tid' 2025-07-05 22:03:14 +02:00
Exter-N
278bf43b29 ClientStructs-ify ResourceTree stuff 2025-07-05 05:20:24 +02:00
Exter-N
a97d9e4953 Add Human skin material handling 2025-07-05 04:37:37 +02:00
Exter-N
30e3cd1f38 Add OS thread ID info to the Resource Logger 2025-07-04 19:41:31 +02:00
Ottermandias
62e9dc164d Add support button. 2025-06-26 14:49:28 +02:00
Actions User
9fc572ba0c [CI] Updating repo.json for testing_1.4.0.4 2025-06-15 21:47:41 +00:00
Ottermandias
3c20b541ce Make mousewheel-scrolling work for setting combos, also filters. 2025-06-15 23:20:13 +02:00
Ottermandias
1961b03d37 Fix issues with shapes and attributes with ID. 2025-06-15 23:18:46 +02:00
Ottermandias
1f4ec984b3 Use improved filesystem. 2025-06-13 17:27:56 +02:00
Ottermandias
4981b0348f BNPCs. 2025-06-13 17:27:56 +02:00
Ottermandias
a8c05fc6ee Make middle-mouse button handle temporary settings. 2025-06-13 17:27:56 +02:00
Actions User
3d05662384 [CI] Updating repo.json for testing_1.4.0.3 2025-06-08 09:38:30 +00:00
Ottermandias
973814b31b Some more BNPCs. 2025-06-08 11:36:32 +02:00
Ottermandias
a16fd85a7e Handle .tex files with broken mip map offsets on import, also remove unnecessary mipmaps (any after reaching minimum size once). 2025-06-08 11:28:12 +02:00
Ottermandias
4c0e6d2a67 Update Mod Merger for other group types. 2025-06-07 22:10:59 +02:00
Ottermandias
535694e9c8 Update some BNPC Names. 2025-06-07 22:10:17 +02:00
Ottermandias
318a41fe52 Add checking for supported features with the currently new supported features 'Atch', 'Shp' and 'Atr'. 2025-06-03 18:39:54 +02:00
Actions User
98203e4e8a [CI] Updating repo.json for testing_1.4.0.2 2025-06-01 11:06:37 +00:00
Ottermandias
6cba63ac98 Make shape names editable in models. 2025-06-01 13:04:26 +02:00
Ottermandias
b48c4f440a Make attributes and shapes completely toggleable. 2025-06-01 13:04:26 +02:00
Actions User
75f4e66dbf [CI] Updating repo.json for 1.4.0.1 2025-05-30 12:38:32 +00:00
Ottermandias
74bd1cf911 Fix checking the flags for all races and genders for specific IDs in shapes/attributes. 2025-05-30 14:36:33 +02:00
Ottermandias
ff2a9f95c4 Fix Atr and Shp not being transmitted via Mare, add improved compression but don't use it yet. 2025-05-30 14:36:07 +02:00
Ottermandias
9921c3332e Merge branch 'master' of github.com:xivDev/Penumbra 2025-05-30 14:35:24 +02:00
Ottermandias
f2927290f5 Fix exceptions when unsubscribing during event invocation. 2025-05-30 14:35:13 +02:00
Actions User
1551d9b6f3 [CI] Updating repo.json for 1.4.0.0 2025-05-28 11:56:49 +00:00
Ottermandias
5e985f4a84 1.4.0.0 2025-05-28 13:54:42 +02:00
Ottermandias
2c115eda94 Slightly improve error message when importing wrongly named atch files. 2025-05-28 13:54:23 +02:00
Ottermandias
ebe45c6a47 Update Lib. 2025-05-27 11:33:10 +02:00
Ottermandias
82fc334be7 Use dynamis for some pointers. 2025-05-23 15:17:19 +02:00
Actions User
cd56163b1b [CI] Updating repo.json for testing_1.3.6.15 2025-05-23 09:32:11 +00:00
Ottermandias
ccc2c1fd4c Fix missing other option notifications for shp/atr. 2025-05-23 11:30:10 +02:00
Ottermandias
08c9124858 Fix issue with shapes/attributes not checking the groups correctly. 2025-05-23 11:29:52 +02:00
Ottermandias
1bdbfe22c1 Update Libraries. 2025-05-23 10:50:04 +02:00
Actions User
9e7c304556 [CI] Updating repo.json for testing_1.3.6.14 2025-05-22 09:16:22 +00:00
Ottermandias
bc4f88aee9 Fix shape/attribute mask stupidity. 2025-05-22 11:14:17 +02:00
Ottermandias
400d7d0bea Slight improvements. 2025-05-22 11:13:58 +02:00
Ottermandias
ac4c75d3c3 Fix not updating meta count correctly. 2025-05-22 11:13:42 +02:00
Ottermandias
507b0a5aee Slight description update. 2025-05-21 18:07:17 +02:00
Actions User
f5db888bbd [CI] Updating repo.json for testing_1.3.6.13 2025-05-21 13:49:29 +00:00
Ottermandias
d7dee39fab Add attribute handling, rework atr and shape caches. 2025-05-21 15:45:05 +02:00
Ottermandias
3412786282 Optimize used memory by metadictionarys a bit. 2025-05-21 15:45:05 +02:00
Ottermandias
861cbc7759 Add global EQP edits to always hide horns or ears. 2025-05-21 15:45:05 +02:00
Actions User
fefa3852f7 [CI] Updating repo.json for testing_1.3.6.12 2025-05-19 15:17:54 +00:00
Ottermandias
68b68d6ce7 Fix some issues with customization IDs and supported counts. 2025-05-19 17:15:29 +02:00
Ottermandias
47b5895404 Fix issue with temp settings again. 2025-05-18 22:00:17 +02:00
Actions User
e18e4bb0e1 [CI] Updating repo.json for testing_1.3.6.11 2025-05-18 14:02:16 +00:00
Ottermandias
6e4e28fa00 Fix disabling conditional shapes. 2025-05-18 16:00:09 +02:00
Ottermandias
e326e3d809 Update shp conditions. 2025-05-18 15:52:47 +02:00
Ottermandias
fbc4c2d054 Improve option select combo. 2025-05-18 12:54:23 +02:00
Ottermandias
3078c467d0 Fix issue with empty and temporary settings. 2025-05-18 12:54:23 +02:00
Ottermandias
52927ff06b Fix clipping in meta edits. 2025-05-18 12:54:23 +02:00
Actions User
08e8b9d2a4 [CI] Updating repo.json for testing_1.3.6.10 2025-05-15 22:28:36 +00:00
Ottermandias
f1448ed947 Add conditional connector shapes. 2025-05-16 00:25:13 +02:00
Ottermandias
c0dcfdd835 Update shape string format. 2025-05-15 22:23:42 +02:00
Actions User
70295b7a6b [CI] Updating repo.json for testing_1.3.6.9 2025-05-15 15:50:16 +00:00
Ottermandias
480942339f Add draggable mod selector width. 2025-05-15 17:47:32 +02:00
Ottermandias
6ad0b4299a Add shape meta manipulations and rework attribute hook. 2025-05-15 17:46:53 +02:00
Ottermandias
0adec35848 Add initial support for custom shapes. 2025-05-15 00:26:59 +02:00
Ottermandias
0fe4a3671a Improve small issue with redraw service. 2025-05-08 23:46:25 +02:00
Caraxi
363d115be8 Add filter for temporary mods 2025-04-19 23:14:39 +02:00
Ottermandias
7595827d29 Merge branch 'master' of github.com:xivDev/Penumbra 2025-04-19 23:12:02 +02:00
Ottermandias
117724b0ae Update npc names. 2025-04-19 23:11:45 +02:00
Ottermandias
a5d221dc13 Make temporary mode checkbox more visible. 2025-04-18 00:17:07 +02:00
Ottermandias
cbebfe5e99 Fix sizing of mod panel. 2025-04-17 01:06:58 +02:00
Ottermandias
0c768979d4 Don't use DalamudPackager for no reason. 2025-04-17 01:06:22 +02:00
Ottermandias
53ef42adfa Update EST Customization identification. 2025-04-17 01:06:09 +02:00
Ottermandias
0954f50912 Update OtterGui, GameData, Namespaces. 2025-04-17 01:05:56 +02:00
Actions User
5d5fc673b1 [CI] Updating repo.json for 1.3.6.8 2025-04-10 14:42:26 +00:00
Ottermandias
2bd0c89588 Better item sort for item swap selectors. 2025-04-10 16:04:49 +02:00
Ottermandias
f03a139e0e blech 2025-04-10 00:17:23 +02:00
Ottermandias
f9b5a626cf Add some migration stuff. 2025-04-10 00:02:49 +02:00
Ottermandias
dc336569ff Add context to copy the full file path from redirections. 2025-04-10 00:02:36 +02:00
Ottermandias
0ec6a17ac7 Add context to open backup directory. 2025-04-10 00:02:21 +02:00
Ottermandias
129156a1c1 Add some more safety and better IPC for draw object storage. 2025-04-09 15:04:47 +02:00
Ottermandias
33ada1d994 Remove meta-default-value checking from TT imports, move it entirely to mod loads, and keep default-valued entries if other options actually edit the same entry. 2025-04-08 16:56:23 +02:00
Ottermandias
0afcae4504 Run API redraws on framework. 2025-04-05 18:49:30 +02:00
Ottermandias
93e60471de Update for new objectmanager. 2025-04-05 18:49:18 +02:00
Actions User
5437ab477f [CI] Updating repo.json for 1.3.6.7 2025-04-05 12:44:47 +00:00
Ottermandias
3b54485127 Maybe fix AtchCaller crashes. 2025-04-05 14:42:25 +02:00
Ottermandias
c3b2443ab5 Add Incognito modifier. 2025-04-04 22:35:23 +02:00
Actions User
2fdafc5c85 [CI] Updating repo.json for 1.3.6.6 2025-04-02 21:45:19 +00:00
Ottermandias
09c2264de4 Revert overeager BNPC Name update. 2025-04-02 23:41:08 +02:00
Ottermandias
c3be151d40 Maybe fix crash issue in AtchHook1 / issue with kept draw object links. 2025-04-02 23:37:06 +02:00
Exter-N
abb47751c8 Mtrl editor: Disregard obsolete modded ShPks 2025-03-30 20:32:03 +02:00
Exter-N
1d517103b3 Mtrl editor: Fix texture pinning 2025-03-30 20:32:03 +02:00
Actions User
fe5d1bc36e [CI] Updating repo.json for 1.3.6.5 2025-03-30 16:08:59 +00:00
Exter-N
b589103b05 Make resolvedData thread-local 2025-03-30 13:56:32 +02:00
Actions User
cc76125b1c [CI] Updating repo.json for 1.3.6.4 2025-03-29 17:07:46 +00:00
Ottermandias
f3bcc4d554 Update changelog. 2025-03-29 18:05:47 +01:00
Ottermandias
2dd6dd201c Update PAP records. 2025-03-29 18:03:57 +01:00
Exter-N
cb0214ca2f Fix material editor and improve pinning logic 2025-03-29 16:55:44 +01:00
Exter-N
5a5a1487a3 Fix texture naming in Resource Trees 2025-03-29 16:55:44 +01:00
Actions User
de408e4d58 [CI] Updating repo.json for 1.3.6.3 2025-03-28 17:33:26 +00:00
Ottermandias
a1bf26e7e8 Run HTTP redraws on framework thread. 2025-03-28 18:30:26 +01:00
Actions User
3bb7db10fb [CI] Updating repo.json for 1.3.6.2 2025-03-28 16:29:20 +00:00
Ottermandias
8a68a1bff5 Update GameData. 2025-03-28 17:25:03 +01:00
Ottermandias
01e6f58463 Add Launching IPC Event. API 5.8 2025-03-28 16:53:50 +01:00
Actions User
7498bc469f [CI] Updating repo.json for 1.3.6.1 2025-03-28 14:55:12 +00:00
Ottermandias
23ba77c107 Update build step and check for pre 7.2 shps. 2025-03-28 15:52:40 +01:00
Ottermandias
1a1d1c1840 Revert Dalamud staging on release, and update api level. 2025-03-28 14:10:52 +01:00
Actions User
b019da2a8c [CI] Updating repo.json for 1.3.6.0 2025-03-28 13:09:26 +00:00
Ottermandias
60becf0a09 Use staging build for release for now. 2025-03-28 14:06:21 +01:00
Ottermandias
974b215610 1.3.6.0 2025-03-28 13:58:11 +01:00
Ottermandias
8e191ae075 Fix offsets. 2025-03-28 13:33:43 +01:00
Ottermandias
b189ac027b Fix imgui assert. 2025-03-28 02:29:49 +01:00
Ottermandias
6cbc8bd58f Merge remote-tracking branch 'Exter-N/72' 2025-03-28 00:59:14 +01:00
Exter-N
49f077aca0 Fixes for 7.2 (ResourceTree + ShPk 13.1) 2025-03-27 22:32:07 +01:00
Ottermandias
525d1c6bf9 Update GameData. 2025-03-27 18:18:04 +01:00
Ottermandias
124b54ab04 Update GameData. 2025-03-27 16:00:30 +01:00
Ottermandias
b8b2127a5d Update STM and signatures. 2025-03-27 15:53:59 +01:00
Ottermandias
586bd9d0cc Re-add wrong dependencies. 2025-03-27 12:07:45 +01:00
Ottermandias
03bb07a9c0 Update for SDK. 2025-03-27 12:04:38 +01:00
Ottermandias
279a861582 Fix error in parser. 2025-03-16 22:17:37 +01:00
Ottermandias
82a1271281 Add option to import atch files from the mod itself via context. 2025-03-16 15:46:51 +01:00
Ottermandias
26a6cc4735 Fix clipping in changed items panel without grouping. 2025-03-16 15:46:51 +01:00
Ottermandias
61d70f7b4e Fix identification of EST changes. 2025-03-16 15:46:51 +01:00
Ottermandias
0213096c58 Add BodyHideGloveCuffs name to eqp entries. 2025-03-16 15:46:51 +01:00
Actions User
dc47a08988 [CI] Updating repo.json for testing_1.3.5.1 2025-03-13 23:20:54 +00:00
Ottermandias
83574dfeb1 Merge remote-tracking branch 'Exter-N/better-tex' 2025-03-14 00:16:33 +01:00
Ottermandias
87f44d7a88 Some minor parser fixes thanks to Anna. 2025-03-14 00:13:57 +01:00
Ottermandias
cda6a4c420 Make preferred changed item star more noticeable, and make the color configurable. 2025-03-14 00:13:01 +01:00
Exter-N
4093228e61 Improve wording of block compressions (suggested by @Theo-Asterio) 2025-03-12 23:04:57 +01:00
Exter-N
442ae960cf Add encoding support for BC1, BC4 and BC5 2025-03-12 20:03:53 +01:00
Exter-N
e7f7077e96 Simplify passing of the device (suggested by @rootdarkarchon) 2025-03-12 15:18:20 +01:00
Exter-N
e5620e17e0 Improve texture saving 2025-03-12 01:20:36 +01:00
Ottermandias
93b0996794 Add chat command to clear temporary settings. 2025-03-11 18:13:08 +01:00
Actions User
1d70be8060 [CI] Updating repo.json for 1.3.5.0 2025-03-09 22:16:58 +00:00
Ottermandias
eab98ec0e4 1.3.5.0 2025-03-09 14:41:45 +01:00
Ottermandias
861b7b78cd Merge branch 'master' of github.com:xivDev/Penumbra 2025-03-09 13:48:56 +01:00
Ottermandias
6eacc82dcd Update references. 2025-03-09 13:48:41 +01:00
Ottermandias
1afbbfef78 Update NuGet packages. 2025-03-09 13:39:41 +01:00
Ottermandias
7cf0367361 Try moving extracted folders 3 times for unknown issues. 2025-03-09 13:39:25 +01:00
Ottermandias
0b0c92eb09 Some cleanup. 2025-03-02 14:27:40 +01:00
Actions User
34d51b66aa [CI] Updating repo.json for testing_1.3.4.6 2025-03-01 21:37:03 +00:00
Ottermandias
cda9b1df65 Fix weapon identification bug. 2025-03-01 22:34:50 +01:00
Ottermandias
509f11561a Add preferred changed items to mods. 2025-03-01 22:21:36 +01:00
Ottermandias
13adbd5466 Allow configuration of the changed item display. 2025-03-01 16:56:02 +01:00
Actions User
26985e01a2 [CI] Updating repo.json for testing_1.3.4.5 2025-02-28 23:37:03 +00:00
Ottermandias
deba8ac910 Heavily improve changed item display. 2025-03-01 00:33:56 +01:00
Ottermandias
1ebe4099d6 Add ImGuiCacheService. 2025-02-27 17:51:27 +01:00
Ottermandias
c6de7ddebd Improve GamePaths and parsing, add support for identifying skeletons and phybs. 2025-02-27 13:08:41 +01:00
Ottermandias
8860d1e39a Fix an exception in incognito names in weird cutscene cases. 2025-02-27 06:01:08 +01:00
Ottermandias
2413424c8a Merge branch 'rt-more-files' 2025-02-27 05:51:43 +01:00
Ottermandias
9b25193d4e ImUtf8 and null-check cleanup. 2025-02-27 05:51:25 +01:00
Ottermandias
70844610d8 Primary constructor and some null-check cleanup. 2025-02-27 05:45:06 +01:00
Ottermandias
e4cfd674ee Probably unnecessary size optimization. 2025-02-27 05:39:19 +01:00
Ottermandias
776a93dc73 Some null-check cleanup. 2025-02-27 05:38:56 +01:00
Exter-N
514b0e7f30 Add file types to Resource Tree and require Ctrl+Shift for some quick imports 2025-02-27 00:10:24 +01:00
Actions User
4a00d82921 [CI] Updating repo.json for testing_1.3.4.4 2025-02-20 18:20:23 +00:00
Ottermandias
fdd75e2866 Use Meta Compression V1. 2025-02-20 19:17:55 +01:00
Ottermandias
b2860c1047 Merge branch 'refs/heads/adamm789/model-export' 2025-02-20 18:38:21 +01:00
Ottermandias
1f172b4632 Make default constructed models use V6 instead of V5. 2025-02-20 18:37:15 +01:00
Ottermandias
d40c59eee9 Slight cleanup. 2025-02-20 18:36:46 +01:00
Ottermandias
f8d0616acd Notify when an unhandled UV count is reached. 2025-02-20 18:36:33 +01:00
Ottermandias
31f23024a4 Notify and fail when a list of vertex usages has more than one entry where this is not expected. 2025-02-20 18:36:08 +01:00
Adam Moy
6d2b72e079 Removed irrelevant comments 2025-02-20 18:13:53 +01:00
Adam Moy
b76626ac8d Added VertexTexture3
Not sure of accuracy but followed existing pattern
2025-02-20 18:13:53 +01:00
Adam Moy
579969a9e1 Using LINQ
And also change types from using LINQ
2025-02-20 18:13:53 +01:00
Adam Moy
2f0bf19d00 Use First().Value 2025-02-20 18:13:53 +01:00
Adam Moy
ef26049c53 Consider VertexElement's UsageIndex
Allows VertexDeclarations to have multiple VertexElements of the same Type but different UsageIndex
2025-02-20 18:13:53 +01:00
Actions User
a73dee83b3 [CI] Updating repo.json for testing_1.3.4.3 2025-02-18 14:12:54 +00:00
Ottermandias
41672c31ce Update message slightly. 2025-02-18 15:10:16 +01:00
Ottermandias
a561e70410 Add option to always work in temporary settings. 2025-02-17 17:39:48 +01:00
Ottermandias
b7b9defaa6 Add context menu to clear temporary settings. 2025-02-17 17:39:48 +01:00
Actions User
79938b6dd0 [CI] Updating repo.json for testing_1.3.4.2 2025-02-15 15:50:18 +00:00
Ottermandias
40f24344af Update OtterGui. 2025-02-15 16:46:39 +01:00
Ottermandias
93e184c9a5 Add import of .atch files into metadata. 2025-02-15 16:46:39 +01:00
Ottermandias
2be5bd0611 Make EQP swaps also swap multi-slot items correctly. 2025-02-15 16:46:39 +01:00
Actions User
f89eab8b2b [CI] Updating repo.json for testing_1.3.4.1 2025-02-13 15:42:58 +00:00
Ottermandias
a9a556eb55 Add CheckCurrentChangedItemFunc, 2025-02-13 13:43:54 +01:00
Ottermandias
0af9667789 Add changed item adapters. 2025-02-12 16:44:22 +01:00
Ottermandias
60b9facea3 Cont. 2025-02-09 15:27:32 +01:00
Ottermandias
50c4207844 Give messages for unsupported file redirection types. 2025-02-09 15:12:34 +01:00
Ottermandias
9b18ffce66 Updated submodule Versions. 2025-02-06 16:58:48 +01:00
Actions User
214be98662 [CI] Updating repo.json for 1.3.4.0 2025-02-06 15:55:30 +00:00
Ottermandias
f9952ada75 1.3.4.0 2025-02-06 16:22:08 +01:00
Ottermandias
3ba2563e0b Merge branch 'master' of github.com:xivDev/Penumbra 2025-02-03 17:43:49 +01:00
Ottermandias
4cc5041f0a Improve cleanup. 2025-02-03 17:43:44 +01:00
Exter-N
f9b163e7c5 Add explanations on why paths are redacted 2025-02-03 17:13:04 +01:00
Exter-N
981c2bace4 Fix out-of-root path detection logic 2025-02-03 17:13:04 +01:00
Ottermandias
ec09a7eb0e Add initial cleaning functions, to be improved. 2025-01-31 18:46:17 +01:00
Ottermandias
7022b37043 Add some improved Mod Setting API. 2025-01-31 15:31:05 +01:00
Ottermandias
64748790cc Make limits a bit cleaner. 2025-01-30 14:06:39 +01:00
Theo
b0a8b1baa5 Bone and Material Limit updates.
Fix UI in Models tab to allow for more than 4 Materials per DT spec.
2025-01-30 13:57:43 +01:00
Actions User
ac64b4db24 [CI] Updating repo.json for testing_1.3.3.10 2025-01-25 13:01:04 +00:00
Ottermandias
e508e6158f Merge branch 'async-stuff'
# Conflicts:
#	Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs
2025-01-25 13:55:54 +01:00
Ottermandias
4d26a63944 Test disabling MtrlForceSync. 2025-01-25 13:55:13 +01:00
Ottermandias
30a957356a Minor changes. 2025-01-25 13:54:19 +01:00
Ottermandias
9ab8985343 Debug logging. 2025-01-25 12:38:10 +01:00
Exter-N
a3ddce0ef5 Add mechanism to handle completion of async res loads 2025-01-24 02:50:02 +01:00
Actions User
0159eb3d83 [CI] Updating repo.json for testing_1.3.3.9 2025-01-23 17:13:52 +00:00
Ottermandias
55ce633832 Try forcing IMC files to load synchronously for now. 2025-01-23 18:11:35 +01:00
Ottermandias
40168d7daf Fix issue with IPC adding mods before character utility is ready in rare cases. 2025-01-22 23:14:09 +01:00
Actions User
dcab443b2f [CI] Updating repo.json for testing_1.3.3.8 2025-01-22 16:38:33 +00:00
Ottermandias
dcc4354777 Fix clipping height in changed items tab. 2025-01-22 17:36:13 +01:00
Ottermandias
2afd6b966e Add debug logging facilities. 2025-01-22 17:36:01 +01:00
Actions User
737e74582b [CI] Updating repo.json for testing_1.3.3.7 2025-01-20 19:36:05 +00:00
Ottermandias
39c73af238 Fix stupid. 2025-01-20 20:33:35 +01:00
Actions User
9ca0145a7f [CI] Updating repo.json for testing_1.3.3.6 2025-01-20 16:48:19 +00:00
Ottermandias
0c8571fba9 Reduce and pad IMC allocations and log allocations. 2025-01-20 17:14:13 +01:00
Ottermandias
8779f4b689 Add new cutscene ENPC tracking hooks. 2025-01-20 15:36:05 +01:00
Ottermandias
7b517390b6 Fix temporary settings causing collection saves. 2025-01-20 15:30:03 +01:00
Ottermandias
7d75c7d7a5 Merge branch 'json-schema' 2025-01-20 15:13:20 +01:00
Ottermandias
4f0428832c Fix solution file for schemas. 2025-01-20 15:13:09 +01:00
Exter-N
b62563d721 Remove $id from shpk_devkit schema 2025-01-17 20:06:21 +01:00
Ottermandias
ec3ec7db4e update schema organization anf change some things. 2025-01-17 19:55:02 +01:00
Ottermandias
5f8377acaa Update mod loading structure. 2025-01-17 19:54:40 +01:00
Exter-N
3b8aac8eca Add schema for Material Development Kit files 2025-01-17 18:50:00 +01:00
Exter-N
a1931a93fb Add drafts of JSON schemas 2025-01-17 01:45:37 +01:00
Actions User
df148b556a [CI] Updating repo.json for testing_1.3.3.5 2025-01-16 16:25:18 +00:00
Ottermandias
bdc2da95c4 Make mods write empty containers again for now. 2025-01-16 17:22:25 +01:00
Actions User
1462891bd3 [CI] Updating repo.json for testing_1.3.3.4 2025-01-15 17:05:31 +00:00
Ottermandias
d2a8cec01f Merge branch 'combining' 2025-01-15 17:44:58 +01:00
Ottermandias
795fa7336e Update with workable prototype. 2025-01-15 17:44:22 +01:00
Ottermandias
e77fa18c61 Start for combining groups. 2025-01-15 14:25:15 +01:00
Ottermandias
9559bd7358 Improve RSP Identifier ToString. 2025-01-14 15:20:37 +01:00
Ottermandias
9c25fab183 Increase API minor version. 2025-01-14 14:41:32 +01:00
Ottermandias
cc981eba15 Fix used dye channel in material editor previews. 2025-01-14 14:34:34 +01:00
Ottermandias
30a4b90e84 Add IPC for querying temporary settings. 2025-01-14 14:34:18 +01:00
Ottermandias
82689467aa Add counts to multi mod selection. 2025-01-12 00:03:36 +01:00
Ottermandias
415e15f3b1 Fix another issue with temporary mod settings. 2025-01-11 21:12:21 +01:00
Actions User
3687c99ee6 [CI] Updating repo.json for testing_1.3.3.3 2025-01-11 17:02:43 +00:00
Ottermandias
6ea38eac0a Share PeSigScanner and use in RenderTargetHdrEnabler because of ReShade. 2025-01-11 17:59:50 +01:00
Actions User
7f52777fd4 [CI] Updating repo.json for testing_1.3.3.2 2025-01-11 14:58:57 +00:00
Ottermandias
2753c786fc Only put out warnings if the path is rooted. 2025-01-11 15:55:14 +01:00
Ottermandias
7b2e82b27f Add some HDR related debug data and support info. 2025-01-11 15:22:04 +01:00
Ottermandias
aebd22ed64 Merge branch 'rt-hdr' 2025-01-11 14:21:12 +01:00
Actions User
c99a7884bb [CI] Updating repo.json for 1.3.3.1 2025-01-11 12:49:05 +00:00
Ottermandias
e73b3e85bd Autoformat and remove nagging. 2025-01-11 13:46:44 +01:00
Ottermandias
0758739666 Cleanup UI code. 2025-01-11 13:46:08 +01:00
Ottermandias
d4e6688369 Fix issue when empty settings are turned temporary. 2025-01-11 13:26:51 +01:00
Actions User
e6872cff64 [CI] Updating repo.json for 1.3.3.0 2025-01-10 19:00:27 +00:00
Ottermandias
b83564bce8 1.3.3.0 2025-01-10 19:55:33 +01:00
Exter-N
e8300fc5c8 Improve RT-HDR texture comments 2025-01-09 20:42:48 +01:00
Exter-N
f07780cf7b Add RenderTargetHdrEnabler 2025-01-08 20:02:14 +01:00
Ottermandias
349241d0ab Better attribution of authors in item swap. 2025-01-07 16:49:19 +01:00
Ottermandias
1845c4b89b Merge branch 'master' of github.com:xivDev/Penumbra 2025-01-07 16:11:38 +01:00
Ottermandias
756537c776 Add Turn Permanent button for temporary settings and improve buttons, make secure. 2025-01-07 16:11:26 +01:00
Ottermandias
9a457a1a95 Add debug panel to check changed item identification for paths. 2025-01-07 16:11:05 +01:00
Ottermandias
af7a8fbddd Fix bug with atch counter. 2025-01-07 16:10:37 +01:00
N. Lo.
0eed5f1707 Add a watched plugin to Support Info 2025-01-06 17:56:58 +01:00
Actions User
6374362b28 [CI] Updating repo.json for testing_1.3.2.2 2024-12-31 17:05:32 +00:00
Ottermandias
7da5d73b47 Keep enabled and priority at the top of settings, add button to turn temporary. 2024-12-31 17:56:58 +01:00
Ottermandias
a2258e6160 Add some temporary context menu things. 2024-12-31 17:54:16 +01:00
Ottermandias
dbef1cccb2 Fix stuff after submodule update. 2024-12-31 17:10:09 +01:00
Ottermandias
653f6269b7 Update submodule. 2024-12-31 16:38:15 +01:00
Ottermandias
a5d8baebca Merge branch 'TempSettings' 2024-12-31 16:36:59 +01:00
Ottermandias
cff482a2ed Allow non-locking, negative identifier-locks 2024-12-31 16:36:46 +01:00
Ottermandias
5f9cbe9ab1 Current State. 2024-12-31 15:40:25 +01:00
Ottermandias
282189ef6d Current State. 2024-12-31 15:40:25 +01:00
Ottermandias
98a89bb2b4 Current state. 2024-12-31 15:40:25 +01:00
Ottermandias
67305d507a Extract ModCollectionIdentity. 2024-12-31 15:40:24 +01:00
Ottermandias
fbbfe5e00d Extract collection counters. 2024-12-31 15:40:24 +01:00
Ottermandias
7a2691b942 Add colors for temporary settings. 2024-12-31 15:40:24 +01:00
Ottermandias
50b5eeb700 Add FullModSettings struct. 2024-12-31 15:40:24 +01:00
Ottermandias
2483f3dcdf Add Temporary Settings class 2024-12-31 15:40:24 +01:00
Ottermandias
0e2364497f Maybe fix mtrl file issues. 2024-12-30 00:33:46 +01:00
Ottermandias
25d0a2c9a8 Fix issue with ring IMCs in resource tree. 2024-12-29 23:04:43 +01:00
Actions User
f24056ea31 [CI] Updating repo.json for testing_1.3.2.1 2024-12-25 23:08:52 +00:00
Ottermandias
b3883c1306 Add handling for cached TMBs. 2024-12-26 00:06:51 +01:00
Ottermandias
f679e0ccee Fix some imgui assertions. 2024-12-25 22:22:30 +01:00
Ottermandias
d5e575423b Merge branch 'master' of github.com:xivDev/Penumbra 2024-12-17 18:04:34 +01:00
Ottermandias
18288815b2 Add partial copying of color and colordye tables. 2024-12-17 18:04:17 +01:00
Ottermandias
cc97ea0ce9 Add an option to automatically select the collection assigned to the current character on login. 2024-12-16 17:52:57 +01:00
Actions User
b5a469c524 [CI] Updating repo.json for 1.3.2.0 2024-12-13 17:13:38 +00:00
Ottermandias
510b9a5f1f 1.3.2.0 2024-12-13 18:11:17 +01:00
Ottermandias
5db3d53994 Small improvements. 2024-12-13 17:56:27 +01:00
Ottermandias
08ff9b679e Add changing mod settings to command / macro API. 2024-12-13 17:48:54 +01:00
Ottermandias
22c3b3b629 Again. 2024-12-13 15:43:09 +01:00
Ottermandias
4cc7d1930b Update GameData. 2024-12-09 21:30:00 +01:00
Ottermandias
01db37cbd4 Add Copy for paths, update npc names 2024-12-09 21:22:30 +01:00
Actions User
e9014fe4c3 [CI] Updating repo.json for testing_1.3.1.6 2024-12-05 19:38:51 +00:00
Ottermandias
1434ad6190 Add context menu copying for paths in advanced editing. 2024-12-05 20:36:03 +01:00
Ottermandias
22541b3fd8 Update variables drawer. 2024-12-05 20:36:03 +01:00
Actions User
b377ca372c [CI] Updating repo.json for testing_1.3.1.5 2024-11-29 16:35:08 +00:00
Ottermandias
d7095af89b Add jumping to mods in OnScreen tab. 2024-11-29 17:33:05 +01:00
Ottermandias
8b9f59426e No V1 Meta yet... wait until next version ban or API increase. 2024-11-27 23:07:08 +01:00
Ottermandias
97d7ea7759 tmp 2024-11-27 22:51:14 +01:00
Ottermandias
9787e5a852 Fix some meta issues. 2024-11-27 18:49:04 +01:00
Ottermandias
242c0ee38f Add testing to IPC Meta. 2024-11-27 18:41:16 +01:00
Ottermandias
c8ad4bc106 Use meta transfer v1. 2024-11-27 18:01:53 +01:00
Ottermandias
ac2631384f Fix mod reload of atch manipulations. 2024-11-27 18:01:53 +01:00
Ottermandias
0aa8a44b8d Fix meta manipulation copy/paste. 2024-11-27 18:01:52 +01:00
Ottermandias
8242cde15c Don't spam logs. 2024-11-27 18:01:52 +01:00
Actions User
28250a9304 [CI] Updating repo.json for testing_1.3.1.4 2024-11-26 16:11:29 +00:00
Ottermandias
10279fdc18 fix inverted hook logic. 2024-11-26 17:09:14 +01:00
Ottermandias
b1be868a6a Atch stuff. 2024-11-26 17:09:14 +01:00
Ottermandias
65538868c3 Add Artemis 2024-11-26 17:09:14 +01:00
Actions User
cc49bdcb36 [CI] Updating repo.json for 1.3.1.3 2024-11-26 01:02:41 +00:00
Ottermandias
d2a015f32a Ughhhhhhhhhh 2024-11-26 01:58:44 +01:00
Ottermandias
d0e0ae46e6 Push an ID in itemselector. 2024-11-25 17:46:14 +01:00
Ottermandias
9822ab4128 Add some debug helper output for SeFileDescriptor. 2024-11-25 16:59:07 +01:00
Ottermandias
25aac1a03e Fix CalculateHeight. 2024-11-23 13:58:00 +01:00
Actions User
5a46361d4f [CI] Updating repo.json for 1.3.1.2 2024-11-22 18:16:51 +00:00
Ottermandias
17d8826ae9 This time correctly, maybe? 2024-11-22 19:13:01 +01:00
Actions User
22be9f2d07 [CI] Updating repo.json for 1.3.1.1 2024-11-22 16:34:25 +00:00
Ottermandias
06ba0ba956 Fix glasses issue with resource trees. 2024-11-22 17:32:00 +01:00
Ottermandias
234130cf86
Update repo.json 2024-11-22 14:44:00 +01:00
Actions User
977cb2196a [CI] Updating repo.json for 1.3.1.0 2024-11-22 13:29:57 +00:00
Ottermandias
37332c432b 1.3.1.0 2024-11-22 12:36:30 +01:00
Ottermandias
ee48ea0166 Some stashed changes already applied. 2024-11-22 00:43:36 +01:00
Ottermandias
f2bdaf1b49 Circumvent rsf not existing. 2024-11-22 00:35:17 +01:00
Ottermandias
9e72432682 Merge branch 'master' of github.com:xivDev/Penumbra 2024-11-20 18:08:36 +01:00
Ottermandias
ce75471e51 Fix issue with resetting GEQP parameters on reload (again?) 2024-11-20 18:08:24 +01:00
Actions User
688b84141f [CI] Updating repo.json for testing_1.3.0.4 2024-11-20 09:46:01 +00:00
Ottermandias
3beef61c6f Some debug vis improvements, disable .atch file modding for the moment until modular .atch file modding is implemented. 2024-11-20 10:44:09 +01:00
Ottermandias
11d0cfd1e2 Merge branch 'master' of github.com:xivDev/Penumbra 2024-11-18 20:14:35 +01:00
Ottermandias
8a53313c33 Add .atch file debugging. 2024-11-18 20:14:06 +01:00
Actions User
0928d712c9 [CI] Updating repo.json for testing_1.3.0.3 2024-11-17 23:30:41 +00:00
Ottermandias
41718d8f8f Fix screen actor indices. 2024-11-18 00:28:42 +01:00
Ottermandias
7ab5299f7a Add SCD handling and crc cache visualization. 2024-11-18 00:28:42 +01:00
Actions User
597380355a [CI] Updating repo.json for testing_1.3.0.2 2024-11-17 21:02:11 +00:00
Ottermandias
a864ac1965 1.3.0.2 2024-11-17 22:00:07 +01:00
Ottermandias
83e5feb7db Update OtterGui. 2024-11-17 14:30:47 +01:00
Ottermandias
5599f12753 Further fixes. 2024-11-17 14:28:33 +01:00
Ottermandias
e3a1ae6938 Current state. 2024-11-17 00:50:39 +01:00
Actions User
c54141be54 [CI] Updating repo.json for testing_1.3.0.1 2024-11-04 12:59:01 +00:00
Ottermandias
7dfc564a4c Add path resolving / est handling for kdb and bnmb files. 2024-11-04 13:55:45 +01:00
Ottermandias
d50fbf5a1c Merge remote-tracking branch 'origin/master' 2024-10-30 20:48:26 +01:00
Ottermandias
ed717c69f9 Make temporary collection always respect ownership. 2024-10-30 20:48:16 +01:00
Ottermandias
c4f6038d1e Make temporary collection always respect ownership. 2024-10-30 20:40:10 +01:00
Ottermandias
2358eb378d Fix issue with characters in login screen, maybe. 2024-10-30 17:31:38 +01:00
Ottermandias
7e6ea5008c Maybe fix other issue with left rings and resource trees. 2024-10-30 17:31:24 +01:00
Ottermandias
69971c12af Fix EQP entries for earring hiding. 2024-10-19 19:23:58 +02:00
Actions User
71101ef553 [CI] Updating repo.json for 1.3.0.0 2024-10-18 14:26:25 +00:00
Ottermandias
472d803141 1.3.0.0 2024-10-18 16:24:30 +02:00
Ottermandias
9ddb011545 Fix issue with long mod titles in the merge mods tab. 2024-10-18 16:03:08 +02:00
Ottermandias
339d1f8caf Update GameData 2024-10-13 14:41:27 +02:00
Actions User
9bd1f86a1d [CI] Updating repo.json for testing_1.2.1.9 2024-10-13 11:57:07 +00:00
Ottermandias
50db83146a Maybe fix left finger resource nodes. 2024-10-13 13:55:07 +02:00
Actions User
a54e45f9c3 [CI] Updating repo.json for testing_1.2.1.8 2024-10-12 13:15:18 +00:00
Ottermandias
e646b48afa Add swaps to and from Glasses. 2024-10-12 15:13:22 +02:00
Ottermandias
97b310ca3f Fix issue with meta file not being saved synchronously on creation. 2024-10-12 15:13:06 +02:00
Ottermandias
db2ce1328f Enable VFX for the glasses slot. 2024-10-11 18:19:12 +02:00
Ottermandias
1d5a7a41ab Remove BonusItem from use and update ResourceTree a bit. 2024-10-11 16:35:47 +02:00
Ottermandias
2c5ffc1bc5 Add delete and single add button and fix child sizes. 2024-10-10 16:50:12 +02:00
Actions User
40c772a9da [CI] Updating repo.json for testing_1.2.1.7 2024-10-09 16:49:59 +00:00
Ottermandias
4a0c996ff6 Fix some off-by-one errors with the import progress reports, add test implementation for pbd editing. 2024-10-09 18:47:30 +02:00
Ottermandias
2e424a693d Update GameData 2024-10-07 16:18:58 +02:00
Actions User
c4b59295cb [CI] Updating repo.json for testing_1.2.1.6 2024-10-06 12:54:51 +00:00
Ottermandias
740816f3a6 Fix accessory VFX change not working. 2024-10-06 14:51:52 +02:00
Ottermandias
df0526e6e5 Fix readoing and displaying DemiHuman IMC Identifiers. 2024-10-06 14:23:36 +02:00
Ottermandias
76c0264cbe Reenable model IO for testing. 2024-10-06 14:05:13 +02:00
ackwell
a1a880a0f4 Fix hair.shpk 2024-10-06 14:02:56 +02:00
ackwell
3b21de35cc Fix iris.shpk 2024-10-06 14:02:56 +02:00
ackwell
efd08ae053 Add charactertattoo.shpk support 2024-10-06 14:02:56 +02:00
ackwell
8468ed2c07 Fix skin.shpk 2024-10-06 14:02:56 +02:00
ackwell
8fa0875ec6 Fix character*.shpk exports 2024-10-06 14:02:56 +02:00
Passive
4719f413b6 Fix adjustment switch 2024-10-06 14:02:56 +02:00
Passive
5258c600b7 Rework AdjustByteArray 2024-10-06 14:02:56 +02:00
Passive
3e90524b06 Remove old impl comments 2024-10-06 14:02:56 +02:00
Passive
9c6498e028 Conditionally still check for weights1 even if weights0 is 0 2024-10-06 14:02:56 +02:00
Passive
9de6b3a905 Vector4 to float array 2024-10-06 14:02:56 +02:00
Passive
fecdee05bd Cleanup 2024-10-06 14:02:56 +02:00
Passive
8084f48144 Init support for DT model i/o 2024-10-06 14:02:56 +02:00
Ottermandias
389c42e68f Update GameData. 2024-10-06 13:02:28 +02:00
Ottermandias
776b4e9efb Update obsolete properties from CS. 2024-10-06 11:58:06 +02:00
Ottermandias
caf4382e1f Update BNPCs 2024-10-06 11:58:06 +02:00
Actions User
22aca49112 [CI] Updating repo.json for testing_1.2.1.5 2024-09-22 19:47:34 +00:00
Ottermandias
af2a14826c Add potential hidden priorities. 2024-09-19 22:50:00 +02:00
Ottermandias
9b958a9d37 Update actions. 2024-09-16 23:16:43 +02:00
Ottermandias
00fbb2686b Add option to apply only attributes from IMC group. 2024-09-16 22:54:14 +02:00
Actions User
ac1ea124d9 [CI] Updating repo.json for testing_1.2.1.4 2024-09-09 14:53:17 +00:00
Ottermandias
26371d42f7 Be less dumb. 2024-09-09 16:51:29 +02:00
Actions User
10ce5da8c9 [CI] Updating repo.json for testing_1.2.1.3 2024-09-09 13:42:42 +00:00
Ottermandias
0c6d777c75 Allow copying paths out of the resource logger. 2024-09-09 14:10:54 +02:00
Ottermandias
bd59591ed8 Add display of ImportDate and allow resetting it, add button to open local data json. 2024-09-08 23:42:19 +02:00
Ottermandias
22cbecc6a4 Add Page to mod group data for TT interop. 2024-09-08 22:48:15 +02:00
Ottermandias
1b17404876 Fix small issue with changed item tooltips. 2024-08-31 20:52:08 +02:00
Ottermandias
6b858dc5ac Hmpf. 2024-08-31 20:52:08 +02:00
Ottermandias
75858a61b5 Fix MetaManipulations not resetting count when clearing. 2024-08-31 20:52:08 +02:00
Ottermandias
04582ba00b Add CustomArmor to UI events. 2024-08-31 20:52:08 +02:00
Ottermandias
fb144d0b74 Cleanup. 2024-08-31 20:52:08 +02:00
Actions User
ff3e5410aa [CI] Updating repo.json for testing_1.2.1.2 2024-08-29 19:18:17 +00:00
Ottermandias
176001195b Improve mod filters. 2024-08-29 21:13:33 +02:00
Ottermandias
2a7d2ef0d5 Allow reading BC6. 2024-08-29 18:58:30 +02:00
Ottermandias
de3644e9e1 Make BC4 textures importable. 2024-08-29 18:46:37 +02:00
Ottermandias
5c5e45114f Make loading mods for advanced editing async. 2024-08-29 18:38:37 +02:00
Ottermandias
e5ff9cee9e Stop raising errors when compressing the deleted files after updating Heliosphere mods. 2024-08-29 18:38:09 +02:00
Ottermandias
f5e6132462 Delete default meta entries from archives and api added mods if not configured otherwise. 2024-08-29 17:47:51 +02:00
Ottermandias
f8e3b6777f Add DeleteDefaultValues on general dicts. 2024-08-28 23:10:59 +02:00
Ottermandias
f043311882 Fix vulnerability warning. 2024-08-28 23:10:22 +02:00
Ottermandias
4117d45d15 Use ReadWriteDictionary as base for meta changes. 2024-08-28 18:33:01 +02:00
Ottermandias
6d408ba695 Clip meta changes. 2024-08-28 18:29:58 +02:00
Ottermandias
4970e57131 Improve tooltip of file redirections tab. 2024-08-28 18:29:12 +02:00
Ottermandias
d713d5a112 Improve handling of mod selection. 2024-08-28 18:28:49 +02:00
Ottermandias
a3c22f2826 Fix ordering of meta entries. 2024-08-28 15:49:07 +02:00
Ottermandias
233a999650 Add button to remove default-valued meta entries. 2024-08-28 15:48:42 +02:00
Ottermandias
3e2c9177a7 Prepare API for new meta format. 2024-08-28 15:48:02 +02:00
Ottermandias
ded910d8a1 Add Targa export. 2024-08-26 21:21:38 +02:00
Ottermandias
c4853434c8 Whatever. 2024-08-26 18:25:43 +02:00
Ottermandias
f3346c5d7e Add Targa support. 2024-08-26 18:20:29 +02:00
Ottermandias
726340e4f8 Meh. 2024-08-24 20:45:18 +02:00
Ottermandias
a2237773e3 Update packages. 2024-08-24 20:43:21 +02:00
Ottermandias
3549283769 Order meta entries. 2024-08-24 20:43:21 +02:00
Ottermandias
96f0479b53 Some cleanup. 2024-08-24 20:43:21 +02:00
Actions User
1da095be99 [CI] Updating repo.json for 1.2.1.1 2024-08-12 19:00:43 +00:00
Ottermandias
bedf5dab79 Make collection resolver not cache early resolved actors that aren't characters. 2024-08-12 20:58:17 +02:00
Ottermandias
bb9dd184a3 Fix order of gender and model in EQDP drawer. 2024-08-12 20:57:52 +02:00
Ottermandias
3135d5e7e6 Make collection combo mousewheel-scrollable with ctrl. 2024-08-12 20:57:38 +02:00
Ottermandias
6e351aa68b Make mods added via API migrate models if enabled. 2024-08-12 20:57:16 +02:00
Ottermandias
8ee326853d Update GameData. 2024-08-11 23:24:18 +02:00
Ottermandias
5663822b2b Merge branch 'master' of github.com:xivDev/Penumbra 2024-08-11 23:22:58 +02:00
Ottermandias
47268ab377 Prevent loading crashy shpks. 2024-08-11 23:22:42 +02:00
Ottermandias
6b0b1629bd Move GuidExtensions to OtterGui (unused atm) 2024-08-11 23:22:05 +02:00
N. Lo.
7710d92496 Add another plugin to Copy Support Info 2024-08-10 20:39:53 +02:00
Ottermandias
a27ce6b0a7 Update non-testing DalamudApiLevel. 2024-08-10 12:23:25 +02:00
Actions User
5c9e158da3 [CI] Updating repo.json for 1.2.1.0 2024-08-10 10:01:17 +00:00
Ottermandias
ccce087b87 Update GameData. 2024-08-10 11:59:18 +02:00
Ottermandias
7ba7a6e319 API 5.3 2024-08-10 11:55:30 +02:00
Ottermandias
421fde70b0 Addendum 2024-08-09 23:57:42 +02:00
Ottermandias
741141f227 Woops. 2024-08-09 23:52:12 +02:00
Ottermandias
1b671b95ab 1.2.1.0 2024-08-09 23:04:07 +02:00
Ottermandias
b5f7f03e11 Update BNPC Names. 2024-08-09 22:46:34 +02:00
Ottermandias
6c0d8ea889 Merge branch 'master' of github.com:xivDev/Penumbra 2024-08-09 22:29:04 +02:00
Ottermandias
e44b450548 Disable model import/export for now. 2024-08-09 22:17:23 +02:00
Actions User
465e65e8fe [CI] Updating repo.json for testing_1.2.0.23 2024-08-08 23:17:52 +00:00
Ottermandias
a52a43bd86 Minor cleanup. 2024-08-09 01:15:05 +02:00
Ottermandias
b3d841a8ec Merge remote-tracking branch 'Exter-N/shader-stuff' 2024-08-09 01:08:31 +02:00
Exter-N
c265b917b4 "This is how you end up on a list." 2024-08-09 01:03:46 +02:00
Exter-N
03e9dc55df Use read-only MMIO for legacy ShPk ban 2024-08-08 23:19:44 +02:00
Exter-N
fb58a9c271 Add/improve ShaderReplacementFixer hooks 2024-08-08 23:19:18 +02:00
Actions User
d630a3dff4 [CI] Updating repo.json for testing_1.2.0.22 2024-08-07 14:47:58 +00:00
Ottermandias
1648bfe424 Merge branch 'dt-shmod' 2024-08-07 16:45:28 +02:00
Ottermandias
fe4a046cc9 Make ChatWarningService part of the MessageService. 2024-08-07 16:37:58 +02:00
Ottermandias
f0c034c84d Merge branch 'master' into dt-shmod
# Conflicts:
#	Penumbra/Communication/MtrlLoaded.cs
2024-08-07 15:45:24 +02:00
Ottermandias
df58ac7e92 Fix ref. 2024-08-07 15:44:21 +02:00
Ottermandias
b8a3a854bd Merge remote-tracking branch 'Exter-N/patch-1' 2024-08-06 17:13:17 +02:00
Ottermandias
a40a5d343f Merge remote-tracking branch 'Exter-N/single-row-highlight' 2024-08-06 17:13:02 +02:00
N. Lo.
2bf08c8c89
Fix dye template combo (aka "git gud") 2024-08-06 13:05:21 +02:00
Exter-N
1187efa243 Reinstate single-row CT highlight 2024-08-05 23:02:34 +02:00
Exter-N
0d1ed6a926 No, ImGui, these buttons aren't the same. 2024-08-05 09:51:52 +02:00
N. Lo.
f68e919421 Fix LiveCTPreviewer instantiation 2024-08-05 08:41:49 +02:00
Exter-N
a36f9ccec7 Improve ResourceTree display with new function 2024-08-05 03:45:02 +02:00
Exter-N
dba85f5da3 Sanity check ShPk mods, ban incompatible ones 2024-08-05 03:43:18 +02:00
Exter-N
700fef4f04 Move hook to MaterialResourceHandle.Load (inlining my beloathed) 2024-08-05 03:41:35 +02:00
Actions User
1b5553284c [CI] Updating repo.json for testing_1.2.0.21 2024-08-04 22:44:50 +00:00
Ottermandias
2534f119e9 Make StainService deal with early-loading. 2024-08-05 00:42:04 +02:00
Ottermandias
a585976190 Make ImcChecker threadsafe. 2024-08-05 00:42:04 +02:00
Actions User
0064c4c96e [CI] Updating repo.json for testing_1.2.0.20 2024-08-04 21:23:13 +00:00
Ottermandias
e91e0b23f8 Unused usings. 2024-08-04 23:18:18 +02:00
Ottermandias
0dbf718340 Merge remote-tracking branch 'Exter-N/support-info-mdf' 2024-08-04 22:54:02 +02:00
Ottermandias
6d42673aa4 Update GameData. 2024-08-04 22:52:53 +02:00
Ottermandias
f2094c2c58 Merge branch 'dtme' 2024-08-04 22:48:41 +02:00
Ottermandias
f8b034c42d Auto-formatting, generous application of ImUtf8, minor cleanups. 2024-08-04 22:48:15 +02:00
Exter-N
f3ab1ddbb4 Add game data file status to support info 2024-08-04 22:32:31 +02:00
Ottermandias
c8e859ae05 Fixups. 2024-08-04 15:41:40 +02:00
Ottermandias
728a081419 Merge remote-tracking branch 'Exter-N/dtme' into dtme 2024-08-04 15:38:32 +02:00
Ottermandias
30d10d5a26 Merge branch 'master' into dtme
# Conflicts:
#	Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.ColorTable.cs
#	Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.MtrlTab.cs
2024-08-04 15:36:17 +02:00
Ottermandias
d90c3dd1af Update row type names. 2024-08-04 15:35:27 +02:00
Exter-N
ee086e3e76 Update GameData 2024-08-04 00:57:39 +02:00
Ottermandias
a6ee4c96ea Merge branch 'rt-dt' 2024-08-04 00:00:49 +02:00
Ottermandias
da3f3b8df3 Start rework of identified objects. 2024-08-03 22:45:44 +02:00
Exter-N
75e3ef72f3 RT: Fix Facewear 2024-08-03 20:27:41 +02:00
Exter-N
243593e30f RT: Fix VPR offhand material paths 2024-08-03 20:27:38 +02:00
Exter-N
c849e31034 RT: Use SpanTextWriter to assemble paths 2024-08-03 19:48:42 +02:00
Exter-N
c01aa000fb Optimize I/O of ShPk for ResourceTree generation 2024-08-03 17:55:19 +02:00
Exter-N
5323add662 Improve ShPk tab 2024-08-03 17:55:19 +02:00
Exter-N
f4fe3605f0 DT material editor, new color tables 2024-08-03 17:55:19 +02:00
Exter-N
36ab9573ae DT material editor, main part 2024-08-03 17:55:19 +02:00
Exter-N
450751e43f DT material editor, supporting components 2024-08-03 17:55:19 +02:00
Exter-N
e8182f285e Update StainService for DT 2024-08-03 17:55:15 +02:00
Exter-N
59b3859f11 Minor upgrades to follow dependencies 2024-08-03 17:51:13 +02:00
Exter-N
60986c78f8 Update GameData 2024-08-03 17:51:11 +02:00
Exter-N
069b28272b Add TextureArraySlicer 2024-08-03 17:51:11 +02:00
Actions User
4454ac48da [CI] Updating repo.json for testing_1.2.0.19 2024-08-03 13:18:14 +00:00
Ottermandias
6241187431 Fix span constructors going over boundaries for ByteStrings. 2024-08-03 15:15:27 +02:00
Actions User
f3e7271157 [CI] Updating repo.json for testing_1.2.0.18 2024-08-02 12:48:57 +00:00
Ottermandias
d903f1b8c3 Update GetResourceSync and GetResourceAsync function signatures for testing, including unused stack parameters. 2024-08-01 22:18:26 +02:00
Ottermandias
5e9c7f7eac Fix IMC import sanity check. 2024-08-01 22:17:56 +02:00
Ottermandias
1e1637f0e7 Test IMC group toggling off. 2024-08-01 17:14:46 +02:00
Ottermandias
7579eaacbe Meh. 2024-08-01 17:14:28 +02:00
Ottermandias
73b9d1fca0 Meh. 2024-08-01 16:49:22 +02:00
Ottermandias
a308fb9f77 Allow hook overrides. 2024-08-01 16:40:37 +02:00
Ottermandias
9e15865a99 Fix some further issues with empty byte strings. 2024-08-01 16:37:31 +02:00
Ottermandias
67a220f821 Add context menu to change mod state from Collections tab. 2024-07-31 23:24:59 +02:00
Ottermandias
4b9870f090 Fix some OtterGui changes. 2024-07-31 23:09:37 +02:00
Ottermandias
d247f83e1d Use CiByteString for anything path-related. 2024-07-30 18:54:08 +02:00
Ottermandias
9d128a4d83 Fix potential threading issue on launch. 2024-07-30 18:54:08 +02:00
Ottermandias
70281c576e Update Submodules. 2024-07-30 18:54:08 +02:00
Ottermandias
5270ad4d0d Update ImageSharp 2024-07-30 18:54:08 +02:00
Ottermandias
8518240bf9 Improve knowledge window somewhat. 2024-07-30 18:54:08 +02:00
Ottermandias
7ceaeb826f Move Material Reassignment to the back (and shoot it) 2024-07-30 18:54:08 +02:00
Actions User
ee801b637e [CI] Updating repo.json for testing_1.2.0.17 2024-07-29 07:46:20 +00:00
Ottermandias
5512e0cad2 Revert no-lowercasing for the moment. 2024-07-29 09:43:15 +02:00
Ottermandias
3754f57132 Reinstate spacebar heating 2024-07-29 09:43:15 +02:00
Actions User
e52b027545 [CI] Updating repo.json for testing_1.2.0.16 2024-07-28 22:03:19 +00:00
Ottermandias
d30d418afe Remove a ToLower when resolving paths. 2024-07-28 23:56:28 +02:00
Actions User
5d50523c72 [CI] Updating repo.json for testing_1.2.0.15 2024-07-28 12:12:56 +00:00
Ottermandias
af0bbeb8bf Force saving to be synchronous. 2024-07-28 13:48:11 +02:00
Ottermandias
4806f8dc3e Do not force loaded game paths to lowercase. 2024-07-28 13:43:01 +02:00
Ottermandias
bb4665c367 Remove unused params. 2024-07-28 12:58:17 +02:00
Ottermandias
d0c4d6984c Dispose collection caches on plugin disposal. 2024-07-28 12:57:59 +02:00
Ottermandias
19166d8cf4 Add rudimentary start for knowledge window. 2024-07-28 01:11:52 +02:00
Ottermandias
a1a7487897 Remove Update Bibo Button. 2024-07-28 01:11:20 +02:00
Ottermandias
cbf5baf65c Remove Material Reassignment Tab from advanced editing due to being obsolete. 2024-07-28 01:09:48 +02:00
Ottermandias
6f3d9eb272 Fix bug in EquipmentSwap 2024-07-28 01:09:11 +02:00
Ottermandias
f143601aa0 Do not replace paths when mods are not enabled. 2024-07-28 01:08:58 +02:00
Ottermandias
3ffe6151ff Add ToString for InternalEqpEntry. 2024-07-28 01:08:41 +02:00
Ottermandias
246f4f65f5 Make items changed in a mod sort before other items for item swap, also color them. 2024-07-26 18:42:48 +02:00
Ottermandias
72f2834dfd Add some resource flags. 2024-07-24 15:11:59 +02:00
Ottermandias
c501d0b365 Fix card actor identification. 2024-07-24 15:11:59 +02:00
Actions User
30f9233862 [CI] Updating repo.json for testing_1.2.0.14 2024-07-22 18:56:35 +00:00
Ottermandias
df33557477 Cleanup. 2024-07-22 20:54:24 +02:00
Ottermandias
528e3226b5 Merge remote-tracking branch 'refs/remotes/pmgr/master' 2024-07-22 20:43:58 +02:00
Ottermandias
a4cd5695fb Fix some stuff. 2024-07-22 20:41:57 +02:00
pmgr
ca648f98a1 Fix for pap weirdness, hopefully 2024-07-22 19:37:57 +01:00
Actions User
29dce8f3ab [CI] Updating repo.json for testing_1.2.0.13 2024-07-22 13:16:22 +00:00
Ottermandias
1501bd4fbf Fix negative matching on folders with no matches. 2024-07-22 12:41:20 +02:00
Ottermandias
cec28a1823 Provide actual hook names. 2024-07-21 23:49:27 +02:00
Ottermandias
07382537a0 Merge branch 'refs/heads/pmgr/master' 2024-07-21 23:26:21 +02:00
Ottermandias
ceaa9ca29a Some further cleanup. 2024-07-21 23:26:09 +02:00
Ottermandias
ee5a21f7a2 Add pap requested event, some cleanup. 2024-07-21 22:58:24 +02:00
Ottermandias
0db70c89b1 Some cleanup of PeSigScanner. 2024-07-21 22:58:03 +02:00
Ottermandias
8351b74b21 Update GameData and packages. 2024-07-21 22:23:31 +02:00
pmgr
8c34c18643 Add scuffed pap handling 2024-07-21 16:34:27 +01:00
Actions User
48ab98bee6 [CI] Updating repo.json for testing_1.2.0.12 2024-07-20 22:53:37 +00:00
Ottermandias
c3b7ddad28 Create newly added mods in import folder instead of moving them. 2024-07-21 00:48:48 +02:00
Ottermandias
5b1c0cf0e3 Fix direction of furniture redrawing. 2024-07-21 00:21:41 +02:00
Ottermandias
e2313ba925 Fix field. 2024-07-21 00:21:27 +02:00
Ottermandias
258f7e9732 Reinstate the inlined ApricotSoundPlay hook one layer hup. 2024-07-21 00:13:43 +02:00
Ottermandias
a4548bbf04 Apply unprioritized mod groups in reverse order. 2024-07-21 00:11:13 +02:00
Ottermandias
f533ae6667 Some cleanup. 2024-07-19 17:33:54 +02:00
Ottermandias
1a8d194e05 Merge branch 'master' of github.com:xivDev/Penumbra 2024-07-19 14:24:41 +02:00
Ottermandias
f978b35b76 Make ResourceTrees work with UseNoMods. 2024-07-19 14:24:29 +02:00
Actions User
defba19b2d [CI] Updating repo.json for testing_1.2.0.11 2024-07-17 16:36:04 +00:00
Ottermandias
5abbd8b110 Hook UpdateRender despite per-frame calls. 2024-07-17 18:34:03 +02:00
Ottermandias
9bba1e2b31 Remove log spamming. 2024-07-17 18:06:05 +02:00
Actions User
6d0562180a [CI] Updating repo.json for testing_1.2.0.10 2024-07-17 16:05:28 +00:00
Ottermandias
e7c786b239 Add and rework hooks around EST entries. 2024-07-17 18:02:48 +02:00
Ottermandias
1922353ba3 Update GameData. 2024-07-17 02:01:59 +02:00
Actions User
c9379b6d60 [CI] Updating repo.json for testing_1.2.0.9 2024-07-16 23:38:11 +00:00
Ottermandias
4824a96ab0 Enable Mtrl Restore and Cleanup again. 2024-07-17 01:36:04 +02:00
Actions User
ad877e68e6 [CI] Updating repo.json for testing_1.2.0.8 2024-07-16 22:51:33 +00:00
Ottermandias
67a35b9abb stupid 2024-07-17 00:49:40 +02:00
Ottermandias
eb784dddf0 Fix missing file display. 2024-07-17 00:40:49 +02:00
Ottermandias
89cbb3f60d Update GameData. 2024-07-16 23:02:17 +02:00
Ottermandias
519b3d4891 Update GameData. 2024-07-16 22:37:01 +02:00
Actions User
78af40d507 [CI] Updating repo.json for testing_1.2.0.7 2024-07-16 20:27:30 +00:00
Ottermandias
c98bee67a5 Update GameData. 2024-07-16 22:25:28 +02:00
Ottermandias
d952d83adf Fix redrawing while fishing while sitting. 2024-07-16 22:06:09 +02:00
Ottermandias
12dfaaef99 Fix broken mods being deleted instead of removed. Fix tags crashing when null instead of empty. 2024-07-16 22:06:09 +02:00
Ottermandias
9c781f8563 Disable material migration for now 2024-07-16 22:06:09 +02:00
Actions User
07c3be641d [CI] Updating repo.json for testing_1.2.0.6 2024-07-14 18:41:39 +00:00
Ottermandias
e46fcc4af1 Gracefully deal with invalid offhand IMCs. 2024-07-14 20:38:54 +02:00
Ottermandias
d6f61f06cb Add TestingDalamudApiLevel 2024-07-12 22:15:00 +02:00
Actions User
d815266ed7 [CI] Updating repo.json for testing_1.2.0.5 2024-07-12 15:56:58 +00:00
Ottermandias
94a05afbe0 Make the import popup closeable by clicking outside if it is finished. 2024-07-12 17:54:47 +02:00
Ottermandias
22af545e8d Add image strings to groups and mods to keep them in the json on saves. 2024-07-12 17:37:55 +02:00
Ottermandias
40be298d67 Add automatic reduplication for ui files in pmps, test. 2024-07-12 17:37:19 +02:00
Ottermandias
6beb2416dc Merge branch 'master' of github.com:xivDev/Penumbra 2024-07-12 16:24:56 +02:00
Ottermandias
24597d7dc0 Fix mod normalization skipping the default submod. 2024-07-12 16:20:17 +02:00
Actions User
34cbf37c32 [CI] Updating repo.json for testing_1.2.0.4 2024-07-10 23:43:04 +00:00
Ottermandias
380dd0cffb Fix texture writing. 2024-07-11 01:40:45 +02:00
Ottermandias
1be75444cd Update BNPC Names 2024-07-10 12:51:55 +02:00
Actions User
e2112202a0 [CI] Updating repo.json for testing_1.2.0.3 2024-07-09 16:38:39 +00:00
Ottermandias
37ffe52869 Fix issue with file substitutions. 2024-07-09 18:36:45 +02:00
Ottermandias
baa439d246 Fix enable/disable draw offsets. 2024-07-09 18:31:31 +02:00
Actions User
806e001bad [CI] Updating repo.json for testing_1.2.0.2 2024-07-09 16:11:41 +00:00
Ottermandias
3b980c1a49 Fix two import bugs. 2024-07-09 18:09:44 +02:00
Actions User
1efd493834 [CI] Updating repo.json for testing_1.2.0.1 2024-07-09 15:52:40 +00:00
Ottermandias
3c417d7aec Fix extraction of pmp failing 2024-07-09 17:48:42 +02:00
Ottermandias
c2517499f2
Update repo.json 2024-07-09 17:04:27 +02:00
Actions User
56502f19f9 [CI] Updating repo.json for testing_1.2.0.0 2024-07-09 14:55:15 +00:00
Ottermandias
a0a3435918 Remove not-yet-existing CS requirement. 2024-07-09 16:50:48 +02:00
Ottermandias
b677a14cef Update. 2024-07-09 16:34:31 +02:00
Ottermandias
710f39768b Disable the required ShadersKnown for the time being. 2024-07-08 15:00:37 +02:00
Ottermandias
f89eea721f Update game data. 2024-07-08 14:59:35 +02:00
Ottermandias
585601efd4 Merge branch 'refs/heads/Exter-N/srf7'
# Conflicts:
#	Penumbra/Interop/Hooks/HookSettings.cs
2024-07-08 14:57:37 +02:00
Ottermandias
56e284a99e Add some migration things. 2024-07-08 14:55:49 +02:00
Ottermandias
0d939b12f4 Add model update button. 2024-07-07 16:17:12 +02:00
Ottermandias
4f0f3721a6 Update animation hooks. 2024-07-07 16:16:58 +02:00
Ottermandias
68135f3757 Update Gamedata 2024-07-07 16:15:51 +02:00
Exter-N
41d271213e Update ShaderReplacementFixer for 7.0 2024-07-05 23:59:22 +02:00
Ottermandias
1284037554 Fix some hooks. 2024-07-05 14:39:03 +02:00
Ottermandias
4026dd5867 Change texture handling. 2024-07-05 12:14:31 +02:00
Ottermandias
9fb8090781 Current state. 2024-07-03 17:29:49 +02:00
Ottermandias
431933e9c1 Revert repo API version. 2024-07-02 18:27:53 +02:00
Ottermandias
221b18751d Some updates. 2024-07-02 17:08:27 +02:00
Ottermandias
c2e74ed382 Improve signatures. 2024-06-25 22:50:52 +02:00
Ottermandias
b07af32dee Fix doubled hook. 2024-06-22 23:04:16 +02:00
Actions User
045abc787d [CI] Updating repo.json for testing_1.1.1.5 2024-06-20 12:53:23 +00:00
Ottermandias
ab1e11aba1 Improve support info a bit. 2024-06-20 14:51:17 +02:00
Ottermandias
8cd8efa723 Fix RSP scaling for NPC values. 2024-06-20 14:24:43 +02:00
Actions User
f686a0ff09 [CI] Updating repo.json for testing_1.1.1.4 2024-06-19 20:37:08 +00:00
Ottermandias
29f8c91306 Make meta hooks respect Enable Mod setting and fix EQP composition. 2024-06-19 22:34:59 +02:00
Ottermandias
a90e253c73
Update repo.json 2024-06-19 13:54:17 +02:00
Actions User
90124e83df [CI] Updating repo.json for testing_1.1.1.3 2024-06-19 11:51:55 +00:00
Ottermandias
819afc518c Merge branch 'meta_rework' 2024-06-18 22:05:15 +02:00
Ottermandias
e05dbe9885 Make everything services. 2024-06-18 21:59:04 +02:00
Ottermandias
cf1dcfcb7c Improve Path preprocessing. 2024-06-18 18:33:37 +02:00
Ottermandias
f9c45a2f3f Clean unused functions. 2024-06-18 17:57:12 +02:00
Ottermandias
03d3c38ad5 Improve Imc Handling. 2024-06-18 17:52:34 +02:00
Ottermandias
d7a8c9415b Use specific counter for Imc. 2024-06-17 23:11:42 +02:00
Ottermandias
be729afd4b Some cleanup 2024-06-17 16:39:10 +02:00
Ottermandias
91d9e465ed Improve eqdp. 2024-06-17 14:51:10 +02:00
Ottermandias
600fd2ecd3 Get rid off EQDP files 2024-06-17 14:51:10 +02:00
Ottermandias
9ecc4ab46d Remove CMP file. 2024-06-17 14:51:10 +02:00
Ottermandias
ebef4ff650 No EST files anymore. 2024-06-17 14:51:10 +02:00
Ottermandias
943207cae8 Make GMP independent of file, cleanup unused functions. 2024-06-17 14:51:10 +02:00
Ottermandias
c53f29c257 Fix unnecessary EST file creations. 2024-06-17 14:51:10 +02:00
Ottermandias
a7b90639c6 Some fixes. 2024-06-17 14:51:10 +02:00
Ottermandias
a61a96f1ef Make GmpEntry readonly. 2024-06-17 14:51:10 +02:00
Ottermandias
30b32fdcd2 Fix EQDP bug. 2024-06-17 14:51:10 +02:00
Ottermandias
ad0c64d4ac Change Eqp hook to not need eqp files anymore. 2024-06-17 14:51:10 +02:00
Ottermandias
e33512cf7f Fix issue, remove IMetaCache. 2024-06-17 14:51:10 +02:00
Ottermandias
4ca49598f8 Small improvement. 2024-06-17 14:51:09 +02:00
Ottermandias
3170edfeb6 Get rid off all MetaManipulation things. 2024-06-17 14:51:09 +02:00
Ottermandias
361082813b tmp 2024-06-17 14:51:09 +02:00
Ottermandias
196ca2ce39 Remove all usages of Add(MetaManipulation) 2024-06-17 14:51:09 +02:00
Ottermandias
d9b63320f0 Some small fixes, parse directly into MetaDictionary. 2024-06-17 14:51:09 +02:00
Ottermandias
0445ed0ef9 Remove TryGetValue(MetaManipulation) from MetaDictionary. 2024-06-17 14:51:09 +02:00
Ottermandias
5ca9e63a2a Use internal entries. 2024-06-17 14:51:09 +02:00
Ottermandias
e0339160e9 Start removing MetaManipulation functions. 2024-06-17 14:51:09 +02:00
Ottermandias
13156a58e9 Remove unused functions. 2024-06-17 14:51:09 +02:00
Ottermandias
94fdd848b7 Expand on MetaDictionary to use separate dictionaries. 2024-06-17 14:51:09 +02:00
Ottermandias
d7b60206d7 Improve meta manipulation handling a bit. 2024-06-17 14:51:09 +02:00
Ottermandias
250c4034e0 Improve root directory behavior and AddMods. 2024-06-17 14:50:49 +02:00
Ottermandias
b3f8762494 Fix some crash handler issues 2024-06-17 14:50:44 +02:00
Ottermandias
ec207bdba2 Force saves independent of manipulations for swaps and merges. 2024-06-15 20:49:15 +02:00
Ottermandias
b1a0590382 Make modmerger file lookup case insensitive. 2024-06-15 20:49:15 +02:00
Actions User
532e8a0936 [CI] Updating repo.json for 1.1.1.1 2024-06-14 12:11:16 +00:00
Ottermandias
2346b7588a Fix GMP bug. 2024-06-14 13:39:25 +02:00
Ottermandias
447735f609 Add a configuration to disable showing mods in the lobby and at the aesthetician. 2024-06-11 16:32:38 +02:00
Actions User
c8ea33f8dd [CI] Updating repo.json for 1.1.1.0 2024-06-11 10:52:49 +00:00
Ottermandias
863a7edf0e Add changelog. 2024-06-11 12:50:13 +02:00
Ottermandias
30a87e3f40 Update valid world check. 2024-06-11 12:28:33 +02:00
Ottermandias
ecd5752d16 Make imc attribute letter tooltip appear on disabled. 2024-06-11 12:27:33 +02:00
Ottermandias
f7adc83d63 Fix issue with preview of file in advanced model editing. 2024-06-11 12:27:22 +02:00
Ottermandias
f6b35497c5 Change path comparison for AddMod. 2024-06-11 12:27:10 +02:00
Ottermandias
f51fc2cafd Allow root directory overwriting with case sensitivity. 2024-06-11 12:27:03 +02:00
Ottermandias
159942f29c Add by-name identification in the lobby. 2024-06-11 12:26:54 +02:00
Ottermandias
e884b269a9 Add a version field to mod group files. 2024-06-11 12:26:11 +02:00
Ottermandias
de0309bfa7 Fix issue with ImcGroup settings and IPC. 2024-06-11 12:25:21 +02:00
Actions User
102e7335a7 [CI] Updating repo.json for testing_1.1.0.3 2024-06-07 14:40:25 +00:00
Ottermandias
50a7e7efb7 Add more filter options. 2024-06-07 16:34:09 +02:00
Ottermandias
2e9f184454 Introduce Identifiers and strong entry types for each meta manipulation and use them in the manipulations. 2024-06-06 17:26:25 +02:00
Ottermandias
ceed8531af Fix GMP Entry edit. 2024-06-06 16:49:39 +02:00
Ottermandias
03bfbcc309 Fidx wrong group 2024-06-05 10:36:38 +02:00
Ottermandias
afdffa4f2c Merge branch 'master' of github.com:xivDev/Penumbra 2024-06-04 15:53:43 +02:00
Ottermandias
48dd4bcadb Bleh. 2024-06-04 15:53:35 +02:00
ackwell
87fec7783e Fix blend weight adjustment getting stuck on near-bounds values 2024-06-04 09:36:46 +02:00
Ottermandias
699ae8e1fb Fix issue with collection settings being set to negative value for some reason. 2024-06-03 17:47:11 +02:00
Ottermandias
aeb2db9f5d Add tooltip to global eqp condition. 2024-06-03 17:46:45 +02:00
Ottermandias
63b3a02e95 Fix issue with crash handler and collections not saving on rename. 2024-06-03 17:45:22 +02:00
Ottermandias
b63935e81e Fix issue with accessory vfx hook. 2024-06-02 12:09:05 +02:00
Ottermandias
05d010a281 Add some functionality to allow an IMC group to add apply to all variants. 2024-06-02 12:08:49 +02:00
Ottermandias
137b752196 Fix Dye Preview not applying. 2024-06-02 01:53:29 +02:00
Ottermandias
3b81dd89c8 Merge branch 'restree-blurb' 2024-06-02 01:04:08 +02:00
Ottermandias
3deda68eec Small updates. 2024-06-02 01:03:51 +02:00
Exter-N
3b26e97231 Merge branch 'master' into restree-blurb 2024-06-02 00:05:16 +02:00
Actions User
2e6473dc09 [CI] Updating repo.json for 1.1.0.2 2024-06-01 21:44:53 +00:00
Ottermandias
ef9d81c061 Fix mod merger. 2024-06-01 23:42:17 +02:00
Ottermandias
cfa58ee196 Fix global EQP rings checking bracelets instead. 2024-06-01 23:42:04 +02:00
Ottermandias
e7cf9d35c9 Add GetChangedItems for Mods. 2024-06-01 23:33:21 +02:00
Ottermandias
5101b73fdc Fix issue with creating unnamed collections. 2024-06-01 20:28:50 +02:00
Actions User
aba68cfb92 [CI] Updating repo.json for 1.1.0.1 2024-06-01 16:18:13 +00:00
Ottermandias
331b7fbc1d Fix other options displaying the same option multiple times. 2024-06-01 18:14:21 +02:00
Ottermandias
24d4e9fac6 Fix collections not being added on creation. 2024-06-01 18:14:21 +02:00
Actions User
b79600ea14 [CI] Updating repo.json for 1.1.0.0 2024-06-01 09:54:26 +00:00
Ottermandias
ce11bec985 Use strings for global eqp. 2024-05-31 22:55:22 +02:00
Ottermandias
f61bd8bb8a Update Changelog and improve metamanipulation display in advanced editing. 2024-05-31 19:37:24 +02:00
Ottermandias
67bb95f6e6 Update submodules. 2024-05-31 17:00:40 +02:00
Ottermandias
81fdbf6ccf Small cleanup. 2024-05-31 16:59:51 +02:00
Exter-N
c7046ec006 ResourceTree: Add name/path filter 2024-05-31 01:20:50 +02:00
Exter-N
f4bdbcac53 Make Resource Trees honor Incognito Mode 2024-05-30 23:09:26 +02:00
Exter-N
a6661f15e8 Display the additional path data in ResourceTree 2024-05-30 20:46:04 +02:00
Ottermandias
b2e1bff782 Consolidate path-data encoding into a single file and make it neater. 2024-05-30 17:18:46 +02:00
Actions User
09742e2e50 [CI] Updating repo.json for testing_1.0.3.2 2024-05-28 10:56:23 +00:00
Ottermandias
8891ea0570 Fix imc identifiers setting equip slot to something where they should not. 2024-05-28 12:53:02 +02:00
Ottermandias
f5d6ac8bdb Fix Remove Assignment being visible for base and interface. 2024-05-28 12:51:28 +02:00
Ottermandias
5d1b17f96d Fix mdl imports not being savable. 2024-05-28 12:51:12 +02:00
Ottermandias
255d11974f Fix IMC Stupid. 2024-05-28 11:20:36 +02:00
Actions User
eb2a9b8109 [CI] Updating repo.json for testing_1.0.3.1 2024-05-27 22:13:55 +00:00
Ottermandias
b30de460e7 Fix ColorTable preview with legacy color tables. 2024-05-28 00:12:01 +02:00
Ottermandias
f11cefcec1 Fix best match fullpath returning broken FullPath instead of nullopt. 2024-05-28 00:12:01 +02:00
Actions User
fe266dca31 [CI] Updating repo.json for testing_1.0.3.0 2024-05-27 15:45:29 +00:00
Ottermandias
ca777ba1bf Update GameData. 2024-05-27 17:43:13 +02:00
Ottermandias
1d230050c2 Fix typo and rename geqp options. 2024-05-26 15:41:27 +02:00
Ottermandias
cd133bddbb Update Changelog. 2024-05-26 14:50:22 +02:00
Ottermandias
ed083f2a4c Add support for Global EQP Changes. 2024-05-26 13:30:35 +02:00
Ottermandias
f9527970cb Fix missing ID push for imc attributes. 2024-05-25 00:43:02 +02:00
Ottermandias
e32e314863 Add initial changelog for next release. 2024-05-24 17:51:47 +02:00
Ottermandias
bad1f45ab9 Use different hooking method for EQP entries. 2024-05-24 17:34:56 +02:00
Ottermandias
4743acf767 Make IMC handling even better. 2024-05-24 16:15:04 +02:00
Ottermandias
65627b5002 Fix a weird age-old bug apparently? 2024-05-24 16:14:48 +02:00
Ottermandias
125e5628ec Fix fuckup. 2024-05-23 23:24:37 +02:00
Ottermandias
992cdff58d Improve some IMC things. 2024-05-23 22:31:39 +02:00
Ottermandias
fca1bf9d94 Add ImcIdentifier. 2024-05-23 22:30:42 +02:00
Ottermandias
7df9ddcb99 Re-Add button to open default mod json. 2024-05-23 17:25:27 +02:00
Ottermandias
dfdd5167a8 Remove auto descriptions from newly generated option groups. 2024-05-23 17:24:42 +02:00
Ottermandias
c06d5b0871 Update for new gamedata. 2024-05-23 16:57:16 +02:00
Ottermandias
2585de8b21 Cleanup group drawing somewhat. 2024-05-21 22:01:20 +02:00
Ottermandias
e85b84dafe Add the option to omit mch offhands from changed items. 2024-05-21 18:24:21 +02:00
Ottermandias
bb56faa288 Improvements. 2024-05-20 18:28:40 +02:00
Ottermandias
df6eb3fdd2 Add some early support for IMC groups. 2024-05-16 18:30:40 +02:00
Ottermandias
d47d31b665 achieve feature parity... I think. 2024-05-14 18:10:59 +02:00
Ottermandias
32dbf419e2 Fix single group default options not applying. 2024-05-09 19:58:35 +02:00
Ottermandias
bbbf65eb4c Fix bug preventing deduplication. 2024-05-09 15:49:49 +02:00
Ottermandias
1f2f66b114 Meh. 2024-05-04 11:34:02 +02:00
Ottermandias
d8dad91e89 Oop, not yet. 2024-05-04 11:24:01 +02:00
Ottermandias
97166379a7 Merge remote-tracking branch 'ackwell/mdl-io-triage-6' 2024-05-04 11:20:59 +02:00
Ottermandias
d72008b4b9 Merge remote-tracking branch 'ackwell/mdl-io-triage-6' 2024-05-04 11:19:17 +02:00
Ottermandias
2a5df2dfb0 Create permanent backup before migrating collections. 2024-05-04 11:19:07 +02:00
ackwell
7553b5da8a Fix float imprecision on blend weights 2024-05-04 04:01:28 +10:00
Ottermandias
36fc251d5b Fix a bunch of issues. 2024-05-03 18:52:52 +02:00
ackwell
46a111d152 Fix marker 2024-05-03 22:42:28 +10:00
ackwell
9063d131ba Use validation logic for new material field 2024-05-03 21:41:35 +10:00
ackwell
078688454a Show an invalid material count 2024-05-03 21:17:54 +10:00
ackwell
c96adcf557 Prevent saving invalid files 2024-05-03 21:15:00 +10:00
ackwell
2e76148fba Ensure materials end in .mtrl 2024-05-03 21:14:40 +10:00
Ottermandias
9084b43e3e Update GameData. 2024-04-28 12:01:54 +02:00
Ottermandias
cff6172453 Move editors into folder. 2024-04-27 00:07:10 +02:00
Ottermandias
1e5ed1c414 Now that was a lot of work. 2024-04-26 18:43:45 +02:00
ackwell
616db0dcc3 Add mesh vertex element readout 2024-04-26 21:23:31 +10:00
Ottermandias
297be487b5 More cleanup on groups. 2024-04-26 10:57:09 +02:00
Ottermandias
e40c4999b6 Improve collection migration maybe. 2024-04-26 10:56:36 +02:00
Ottermandias
a72be22d3b Make sure HS image does not displace the settings entirely. 2024-04-26 10:56:06 +02:00
Ottermandias
06953c175d mooooore 2024-04-25 18:18:57 +02:00
Ottermandias
0fd14ffefc More cleanup. 2024-04-25 18:14:21 +02:00
Ottermandias
72db023804 Some cleanup. 2024-04-25 17:58:32 +02:00
ackwell
c1472d5f65 Merge remote-tracking branch 'upstream/master' into mdl-io-triage-6 2024-04-25 21:30:38 +10:00
Ottermandias
cd76c31d8c Fix stack overflow. 2024-04-24 23:41:55 +02:00
Ottermandias
514121d8c1 Reorder stuff. 2024-04-24 23:28:12 +02:00
Ottermandias
6b1743b776 This sucks so hard... 2024-04-24 23:04:04 +02:00
Ottermandias
07afbfb229 Rework options, pre-submod types. 2024-04-23 17:41:55 +02:00
Ottermandias
792a04337f Add a try-catch when scanning for mods. 2024-04-23 15:50:09 +02:00
Ottermandias
e21c9fb6d1 Fix some IPC stuff. 2024-04-23 15:11:09 +02:00
Ottermandias
b34114400f Fix Havok ANSI / UTF8 Issue. 2024-04-23 15:10:16 +02:00
Ottermandias
c276f922a5 Update API. 2024-04-22 18:22:03 +02:00
ackwell
1bc3bb17c9 Fix havok parsing for non-ANSI user paths
Also improve parsing because otter is better at c# than me
2024-04-22 23:45:30 +10:00
ackwell
cc2f72b73d Use bg/ for absolute path example 2024-04-20 20:55:04 +10:00
ackwell
11acd7d3f4 Prevent import failure when no materials are present 2024-04-20 20:53:55 +10:00
ackwell
4a6d94f0fb Avoid inclusion of zero-weighted bones in name mapping 2024-04-20 20:02:43 +10:00
Ottermandias
b99a809eba Remove OptionPriority from general option groups. 2024-04-20 11:26:12 +02:00
Ottermandias
f86f29b44a Some fixes. 2024-04-20 11:03:50 +02:00
Ottermandias
2d5afde612 Fix group priority writing. 2024-04-20 11:02:30 +02:00
Ottermandias
9f4c6767f8 Remove ISubMod. 2024-04-19 18:28:25 +02:00
Ottermandias
8fc7de64d9 Start group rework. 2024-04-19 17:55:28 +02:00
ackwell
ceb3d39a9a Normalise _FFXIV_COLOR values
Fixes xivdev/Penumbra#411
2024-04-20 01:22:03 +10:00
Ottermandias
75cfffeba7 Oops. 2024-04-19 15:51:23 +02:00
Ottermandias
624dd40d58 Handle writing. 2024-04-19 15:46:52 +02:00
Ottermandias
ef1bbb6d9d I don't know what I'm doing 2024-04-19 15:40:12 +02:00
ackwell
aeb7bd5431 Ensure materials contain at least one / 2024-04-19 00:34:08 +10:00
ackwell
dbfaf37800 Export to .glb 2024-04-18 21:47:07 +10:00
ackwell
fd1f9b95d6 Add Single2 support for UVs 2024-04-18 21:23:18 +10:00
Ottermandias
1641166d6e Disable IPC listeners by default. 2024-04-17 18:15:26 +02:00
Ottermandias
0fa62f40d7 Add Versions provider. 2024-04-17 16:57:23 +02:00
Ottermandias
aeccf2b1c6 Update Submodules. 2024-04-14 15:38:14 +02:00
Ottermandias
d4183a03c0 Fix bug with new empty collections. 2024-04-14 15:00:48 +02:00
Ottermandias
94b53ce7fa Meh. 2024-04-13 16:06:04 +02:00
Ottermandias
42ad941ec2 Add GetCollectionsByIdentifier. 2024-04-13 16:05:44 +02:00
Ottermandias
6b5321dad8 Test subscription like before but without primary constructor. 2024-04-12 14:46:26 +02:00
Ottermandias
d9bd05c9ec Fix issue with Hex viewer. 2024-04-12 14:45:25 +02:00
Ottermandias
d5ed4a38e4 Fix2? 2024-04-12 14:39:12 +02:00
Ottermandias
e4f9150c9f Fix? 2024-04-12 14:30:47 +02:00
Ottermandias
791583e183 Silence readme warnings. 2024-04-12 12:51:33 +02:00
Ottermandias
1ef9346eab Allow renaming of collection. 2024-04-12 12:42:16 +02:00
Ottermandias
ba8999914f Rework API, use Collection ID in crash handler, use collection GUIDs in more places. 2024-04-12 12:33:57 +02:00
Ottermandias
793ed4f0a7 With explicit null-termination, maybe? 2024-04-12 00:02:09 +02:00
Ottermandias
eb0e7e2f5f Update ocealots code #1. 2024-04-11 21:25:00 +02:00
ocealot
45b1c55b67 Add accessory vfxs 2024-04-11 21:06:13 +02:00
Ottermandias
c0ee80629d Allow right click to paste into filter as long as it is unfocused. 2024-04-09 16:05:55 +02:00
Ottermandias
7280c4b2f7 Selector improvements. 2024-04-09 15:20:38 +02:00
Ottermandias
21a55b95d9 Add rename mod field. 2024-04-09 15:19:44 +02:00
Ottermandias
e94cdaec46 Some more. 2024-04-05 23:19:41 +02:00
Ottermandias
b1ca073276 Turn Settings and Priority into their own types. 2024-04-05 16:35:55 +02:00
Ottermandias
77bf441e62 Update Open Settings and Main UI. 2024-04-05 15:29:19 +02:00
Ottermandias
6e7512c13e Add Punchline. 2024-04-05 14:53:30 +02:00
Ottermandias
a65009dfb0 Fix issue with merging and deduplicating. 2024-04-01 13:59:09 +02:00
Ottermandias
5cebddb0ab Update OtterGui. 2024-03-30 14:59:31 +01:00
Ottermandias
e830a6b180 Merge remote-tracking branch 'Exter-N/adv-edit-improvements' 2024-03-30 14:06:18 +01:00
Exter-N
b4b813fe5e Advanced Editing minor improvements 2024-03-29 20:05:25 +01:00
Ottermandias
8fa49137b1 Merge branch 'master' of github.com:xivDev/Penumbra 2024-03-29 14:44:25 +01:00
Ottermandias
de239578cc Fix weird exception. 2024-03-29 14:44:08 +01:00
Actions User
a39419288c [CI] Updating repo.json for testing_1.0.2.6 2024-03-28 18:36:31 +00:00
Ottermandias
47c5187ad9 Derp. 2024-03-28 19:34:30 +01:00
Ottermandias
b04cb343dd Make setting for crash handler. 2024-03-28 19:32:10 +01:00
Ottermandias
a31bdb66c8 Merge branch 'pbd-modding' 2024-03-28 18:14:36 +01:00
Ottermandias
1ba5011bfa Small changes. 2024-03-28 18:13:07 +01:00
Ottermandias
e793e7793b Fix model import resetting dirty flag. 2024-03-28 15:24:37 +01:00
Exter-N
3066bf84d5 Where did these usings even come from? 2024-03-26 02:32:33 +01:00
Exter-N
efdd5a824b Make human.pbd moddable 2024-03-26 02:29:07 +01:00
Actions User
12532dee28 [CI] Updating repo.json for testing_1.0.2.5 2024-03-22 14:17:47 +00:00
Ottermandias
4175a582b8 Add IPC Providers because I'm still a fucking moron. 2024-03-22 15:15:50 +01:00
Actions User
78a3ff177f [CI] Updating repo.json for testing_1.0.2.4 2024-03-22 13:46:45 +00:00
Ottermandias
6792ed4f94 Remove the fucking test I'm a fucking moron. 2024-03-22 14:44:48 +01:00
Ottermandias
8dcd0451ba
Update repo.json 2024-03-22 13:43:38 +01:00
Ottermandias
d0d35a7938
Update repo.json 2024-03-22 13:39:46 +01:00
Actions User
aabd988a77 [CI] Updating repo.json for testing_1.2.1.2 2024-03-22 12:38:55 +00:00
Ottermandias
d171cea627 Remove DI Reference. 2024-03-22 13:36:55 +01:00
Ottermandias
26730efc1b Update OtterGui 2024-03-22 13:31:38 +01:00
Ottermandias
ca95b9c14c PackageVersion. 2024-03-22 00:55:53 +01:00
Ottermandias
5ea140db98 Add new draw events. 2024-03-22 00:45:52 +01:00
Ottermandias
8fded88813 Update. 2024-03-21 23:50:55 +01:00
Ottermandias
1a9239a358 Test improving object manager. 2024-03-21 23:02:16 +01:00
Actions User
cc88738e3e [CI] Updating repo.json for testing_1.0.2.2 2024-03-21 20:33:37 +00:00
Ottermandias
0436a3705c Merge branch 'refs/heads/AeAstralis/vertCount' 2024-03-21 21:31:44 +01:00
Ottermandias
72979a9743 Remarked changes. 2024-03-21 21:31:30 +01:00
Ottermandias
f3ceb9034e Make crashhandler somewhat more stable, support multi boxing maybe? 2024-03-21 20:27:55 +01:00
Ottermandias
c55d6966cd Update Internal csproj. 2024-03-21 14:29:05 +01:00
Exter-N
739b5b5ad6 CS-ify ShaderReplacementFixer and ModelRenderer 2024-03-21 12:41:24 +01:00
AeAstralis
0ad769e08e
Add tri count per LoD to models tab
Adds the tri count of each LoD on the selected model to the Models tab under
Advanced Editing.
2024-03-20 22:40:01 -04:00
Actions User
384b8fd489 [CI] Updating repo.json for testing_1.0.2.1 2024-03-20 21:40:54 +00:00
Ottermandias
978f41a4d9 Make some stuff safer maybe. 2024-03-20 22:38:53 +01:00
Actions User
fda77b49cd [CI] Updating repo.json for testing_1.0.2.0 2024-03-20 17:22:25 +00:00
Ottermandias
e8fffa47a0 1.0.2.0 2024-03-20 18:20:08 +01:00
Ottermandias
6cb4f7ac8a Update GameData. 2024-03-20 17:35:45 +01:00
Ottermandias
26f3742b31 Update GameData. 2024-03-20 15:27:51 +01:00
Ottermandias
c8216b0acc Use ObjectManager, Actor and Model. 2024-03-19 22:52:20 +01:00
Ottermandias
5b9309a311 Merge branch 'refs/heads/AeAstralis/tagging' 2024-03-19 21:02:04 +01:00
Ottermandias
0e50a8a9e5 More future proof structure for tags. 2024-03-19 21:01:48 +01:00
Ottermandias
52c1708dd2 Change predefined tag handling. 2024-03-19 20:45:16 +01:00
Ottermandias
2e2d3e173e Merge branch 'master' into AeAstralis/tagging 2024-03-19 18:31:45 +01:00
Ottermandias
fe6e1edecc Fix a game object table warning. 2024-03-19 18:31:02 +01:00
Ottermandias
a6e08a1865 Rename ServiceManagerA. 2024-03-19 18:30:00 +01:00
Ottermandias
02f2bf1bc1 Update actions. 2024-03-19 17:38:02 +01:00
Ottermandias
43d356967c Merge branch 'master' into AeAstralis/tagging 2024-03-19 17:35:34 +01:00
Ottermandias
05b7234748 Update to .net8. 2024-03-19 17:35:05 +01:00
Ottermandias
0f89e24377 Improve tag button positioning. 2024-03-18 17:12:30 +01:00
Ottermandias
c34cc4638c Merge branch 'master' into AeAstralis/tagging 2024-03-18 16:51:21 +01:00
Ottermandias
9f7b95746d Use default task scheduler. 2024-03-18 16:50:26 +01:00
Ottermandias
0ec440c388 Fix reloading mods not fixing settings, add some messages on IPC. 2024-03-18 16:41:40 +01:00
Ottermandias
19526dd92d Help marker position. 2024-03-17 14:09:01 +01:00
Ottermandias
814aa92e19 Move tags before advanced. 2024-03-17 14:08:14 +01:00
Ottermandias
038c230427 Rename to Predefined. 2024-03-17 13:59:40 +01:00
Ottermandias
b725d919bb Merge branch 'master' into AeAstralis/tagging 2024-03-17 13:36:15 +01:00
Ottermandias
ceb3bbc5c3 Fix issue with compressed file sizes. 2024-03-17 13:34:49 +01:00
Ottermandias
7cb50030f9 Fix some stuff, add versions. 2024-03-16 16:58:02 +01:00
Ottermandias
c1a9c798ae auto format. 2024-03-16 16:22:07 +01:00
Ottermandias
50f81cc889 Skip locals init. 2024-03-16 16:21:23 +01:00
Ottermandias
4fc763c9aa Keep first empty option in its desired location. 2024-03-16 16:21:06 +01:00
Ottermandias
8318a4bd84 Compatibility fix. 2024-03-16 16:20:46 +01:00
Ottermandias
e08e9c4d13 Add crash handler stuff. 2024-03-16 16:20:34 +01:00
Ottermandias
312fc1df9a Merge branch 'master' into AeAstralis/tagging 2024-03-09 22:50:29 +01:00
Ottermandias
9ba6e4d0af Update API. 2024-03-09 22:49:28 +01:00
Exter-N
29c93f46a0 Add characterglass.shpk to the skin.shpk fixer 2024-03-09 22:32:41 +01:00
Ottermandias
da423b7464 Make lack of Root Directory louder. 2024-03-08 18:13:51 +01:00
ackwell
5c6e0701d9 Simplify EID handling because IDFK at this point 2024-03-02 23:48:55 +01:00
ackwell
a4bd015836 Fix index offset mis-cast causing overflow 2024-03-02 23:48:55 +01:00
ackwell
1cee1c24ec Skip degenerate triangles targeted by shape keys 2024-03-02 23:48:55 +01:00
ackwell
c1cdb28bb5 Use named enum values for vertex decl mismatch error 2024-03-02 23:48:55 +01:00
AeAstralis
282f6d4855
Migrate shared tag to own config, address comments
Migrates the configuration for shared tags to a separate config file, and
addresses CR feedback.
2024-03-01 21:03:34 -05:00
AeAstralis
334be441f8
Update OtterGui to 97ac353
Updates OtterGui to 97ac3538536a17e980027f783ec5e5167b371f71 to include change
that xivdev/Penumbra#399 is dependent on.
2024-03-01 18:04:03 -05:00
AeAstralis
7128326ab9
Add shared tag system for tagging individual mods
Adds a new system of shared tags that are saved in the Penumbra config, and can
then be 1-click added or removed to/from mods via a popup menu. The use case for
this new system is to allow users to more easily re-use tags and to allow them
to quickly tag individual mods.

Shared tags can be added/removed/modified via a new Tags section of the main
Penumbra Settings tab. Once any shared tags have been saved, they can be added
via a new tags button that shows up in the Description and Edit Mod tabs, to the
right of the existing + button that already existed for typing in new tags.

Shared tags have the same restrictions as regular mod tags, and the application
of shared tags should respect the same limits as application of normal tags.

Signed-off-by: AeAstralis <causal_inverse@fastmail.com>
2024-03-01 17:40:45 -05:00
Actions User
0220257efa [CI] Updating repo.json for 1.0.1.0 2024-03-01 13:37:51 +00:00
Ottermandias
af6100dfe4 Mawp. 2024-03-01 14:35:51 +01:00
Actions User
1d74001281 [CI] Updating repo.json for testing_1.0.0.8 2024-02-24 13:27:11 +00:00
Ottermandias
a89aafb310 Let's do this one more time. 2024-02-24 14:24:50 +01:00
Ottermandias
7b0be25f6e Add even more setting changed events and add auto-player-redraw on saving files. 2024-02-24 14:04:46 +01:00
Actions User
883580d465 [CI] Updating repo.json for 1.0.0.7 2024-02-24 11:09:19 +00:00
Ottermandias
1000841f69 Add some settingchanged events. 2024-02-24 12:06:57 +01:00
Ottermandias
add4b8aa83 Misc. 2024-02-23 21:44:49 +01:00
Ottermandias
529788d2e5 Change font pushes. 2024-02-20 16:36:12 +01:00
Actions User
90665c615f [CI] Updating repo.json for 1.0.0.6 2024-02-18 13:48:18 +00:00
Ottermandias
a2bf477481 Cleanup. 2024-02-18 14:46:22 +01:00
Ottermandias
31bc5ec6f9 Make settings change invoke on Temporary Mods. 2024-02-18 13:03:56 +01:00
Ottermandias
80ce6fe21f Use new font functionality. 2024-02-18 12:59:06 +01:00
Ottermandias
2e0e125913 Fix issue with renaming mods with open advanced window. 2024-02-17 13:35:49 +01:00
Actions User
06cf6ce752 [CI] Updating repo.json for 1.0.0.5 2024-02-13 12:42:23 +00:00
Ottermandias
a0ac0dfcfa Bit more update. 2024-02-13 00:22:44 +01:00
Ottermandias
d2bfcefb89 Update Combos. 2024-02-12 23:32:45 +01:00
Ottermandias
95d5d6c4b0 Update submodules. 2024-02-12 23:30:41 +01:00
Ottermandias
a5f0c2f943 Fix exception with empty option groups. 2024-01-29 13:27:12 +01:00
Actions User
4610686a70 [CI] Updating repo.json for 1.0.0.4 2024-01-27 18:10:34 +00:00
Ottermandias
e321cbdf96 Update API minor Version. 2024-01-27 19:07:51 +01:00
Ottermandias
8a0d217977 Merge branch 'mdl-io-triage-4' 2024-01-27 19:05:14 +01:00
Ottermandias
1e4570bd79 Slight cleanup. 2024-01-27 19:05:05 +01:00
Ottermandias
076dab924f Reuse same list for warnings and exceptions. 2024-01-27 18:58:26 +01:00
Ottermandias
5a80e65d3b Add SetCutsceneParentIndex. 2024-01-27 18:51:16 +01:00
ackwell
e9628afaf8 Include specular factor in character material export 2024-01-27 18:35:26 +11:00
ackwell
1649da70a8 Fix Blender 3.6 support for custom colour attribute 2024-01-27 17:56:32 +11:00
ackwell
72775a80bf Ignore invalid attributes on export 2024-01-27 14:08:56 +11:00
ackwell
6693a1e0ba Clear errors/warnings before starting IO 2024-01-27 13:53:56 +11:00
Ottermandias
b543d9fc1d Merge branch 'mdl-io-triage-3' 2024-01-23 23:23:17 +01:00
Ottermandias
a159ef28c3 Cleanup 2024-01-23 23:23:03 +01:00
ackwell
b2bd31a166 Import all primitives of a mesh 2024-01-24 01:54:56 +11:00
ackwell
8487661bc8 Increase imported vertex precision 2024-01-23 21:27:44 +11:00
Ottermandias
6c93cc20df Woops2 2024-01-22 17:24:08 +01:00
Ottermandias
7b1e28c2cf Woops. 2024-01-22 17:08:38 +01:00
Ottermandias
77762734d7 Some Cleanup. 2024-01-22 16:59:51 +01:00
Ottermandias
056d734c4a Merge remote-tracking branch 'Exter-N/csify' 2024-01-22 16:55:11 +01:00
Ottermandias
a2eaa3ed7f Merge remote-tracking branch 'ackwell/mdl-io-color-attribute' 2024-01-22 16:54:52 +01:00
Ottermandias
b79fbc7653 Some cleanup. 2024-01-22 16:54:05 +01:00
Ottermandias
363b220613 Some improvements of the cutscene service. 2024-01-22 16:54:05 +01:00
ackwell
d76411e66c Fix bounding box calculations for models offset from the origin 2024-01-23 00:40:29 +11:00
Exter-N
c2e5499aef ClientStructs-ify some stuff 2024-01-22 01:51:42 +01:00
Exter-N
b5a71ed7b3 Add locale environment variables to support info 2024-01-21 22:25:27 +01:00
Exter-N
4183e29249 Increase version. (Cargo Cult'd from Glamourer) 2024-01-21 22:25:01 +01:00
ackwell
8167907d91 The other two 2024-01-22 00:34:16 +11:00
ackwell
ca393267f6 Spike custom vertex attribute handling 2024-01-22 00:04:11 +11:00
Actions User
38d855684b [CI] Updating repo.json for 1.0.0.3 2024-01-20 15:09:07 +00:00
Ottermandias
964ddc2572 Merge branch 'mdl-io-warnings' 2024-01-20 16:07:01 +01:00
Ottermandias
7db9599511 Auto-format and stuff. 2024-01-20 16:06:33 +01:00
Ottermandias
fff9fb00f8 Merge branch 'master' into mdl-io-warnings
# Conflicts:
#	Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs
2024-01-20 15:39:25 +01:00
Ottermandias
edad7d9ec9 Update persistent links. 2024-01-20 13:47:03 +01:00
Ottermandias
3debd47064 stupid. 2024-01-20 13:33:51 +01:00
Ottermandias
1e33a8bb22 Merge branch 'mdl-io-triage-2' 2024-01-20 13:17:23 +01:00
Ottermandias
153b1e0d83 Meep 2024-01-20 13:17:12 +01:00
Ottermandias
c752835d2c Make CopySettings save even for unused settings. 2024-01-20 13:12:15 +01:00
ackwell
de08862a88 Add documentation links 2024-01-20 21:28:36 +11:00
ackwell
655d1722c1 Fail soft on invalid attribute masks 2024-01-20 19:16:19 +11:00
ackwell
c11519c95e Fix further content expanding other sections 2024-01-20 15:32:44 +11:00
ackwell
ae409c2cd1 Fix shape value offset handling 2024-01-20 15:31:27 +11:00
ackwell
0d3dde7df3 Tweaks 2024-01-20 01:09:26 +11:00
ackwell
cbd99f833a Allow export of missing bones with warnings 2024-01-20 00:03:58 +11:00
ackwell
0486d049b0 Make material export fallible 2024-01-19 22:20:54 +11:00
ackwell
aa01acd76a Move off messager 2024-01-19 19:41:00 +11:00
ackwell
6da725350a Wire up notifier through import 2024-01-19 02:56:01 +11:00
ackwell
6f3be39cb9 Wire up notifications through export 2024-01-19 02:11:50 +11:00
ackwell
5c15a3a4ff Set up notifier infrastructure 2024-01-19 02:09:43 +11:00
Actions User
8c763d5379 [CI] Updating repo.json for 1.0.0.2 2024-01-17 16:24:57 +00:00
Ottermandias
65af4267f0 Fix variant path generation (2), file was unsaved :/ 2024-01-17 14:46:44 +01:00
ackwell
202f6e4728 Fix file path resolution for file swaps 2024-01-17 14:44:05 +01:00
ackwell
2c5f22047a Generate white vertex colours if missing 2024-01-17 14:44:05 +01:00
ackwell
107f6706d3 i am immensely vain 2024-01-17 14:44:05 +01:00
ackwell
b089bbca37 Merge element ids and flags 2024-01-17 14:44:05 +01:00
ackwell
4b81b065aa Calculate primary BoundingBox 2024-01-17 14:44:05 +01:00
Ottermandias
eb02d2a7a7 Fix variant path generation. 2024-01-17 14:37:49 +01:00
Ottermandias
fdd0145975 Update OtterGui. 2024-01-17 12:14:58 +01:00
Ottermandias
bbac3daf01 Update BNPCs. 2024-01-17 12:14:29 +01:00
Actions User
0b50593acd [CI] Updating repo.json for 1.0.0.1 2024-01-16 17:24:05 +00:00
Ottermandias
e5ddae585c Update Submodules. 2024-01-16 18:21:52 +01:00
Actions User
8509ccba30 [CI] Updating repo.json for 1.0.0.0 2024-01-16 15:39:56 +00:00
Ottermandias
da1b9e9e90 Cleanup, fix tangent/normal mixup. 2024-01-16 16:34:57 +01:00
Ottermandias
47e6e70272 Merge remote-tracking branch 'ackwell/mdl-import-tangent-calc' 2024-01-16 16:16:28 +01:00
Ottermandias
0ff7e49e4d Prepare changelog. 2024-01-16 16:16:11 +01:00
ackwell
5e794b73ba Consider normal morphs for morphed bitangent calculations 2024-01-17 01:40:19 +11:00
ackwell
ea04cc554f Fix up export a little 2024-01-17 00:22:13 +11:00
ackwell
9fae88934d Calculate missing tangents on import, convert all to bitangents for use 2024-01-17 00:17:22 +11:00
Ottermandias
b53a2f1def Some further cleanup. 2024-01-14 19:28:51 +01:00
Ottermandias
70e72f5790 Update OtterGui and maybe handle dependency issues. 2024-01-14 19:28:51 +01:00
Actions User
f147e66953 [CI] Updating repo.json for testing_0.8.3.7 2024-01-14 13:17:42 +00:00
Ottermandias
965f8efd80 Add function that handles prepending DX11 dashes. 2024-01-14 14:15:13 +01:00
Actions User
574a129772 [CI] Updating repo.json for testing_0.8.3.6 2024-01-14 12:37:57 +00:00
Ottermandias
4749769dd4 Merge branch 'mdl-export-materials' 2024-01-14 13:35:57 +01:00
Ottermandias
3b9d841014 Minor cleanup. 2024-01-14 13:35:46 +01:00
Ottermandias
7c83e30e9f Merge branch 'master' into mdl-export-materials 2024-01-14 13:17:36 +01:00
Ottermandias
65fbf13afe Parsing... 2024-01-14 13:16:30 +01:00
ackwell
ec92f93d22 Improve material and texture path resolution 2024-01-14 22:36:33 +11:00
ackwell
0e50cc9c47 Handle character mask R same as other shaders 2024-01-14 22:27:23 +11:00
ackwell
9ff3227cf4 Cleanup pass 2024-01-14 20:12:17 +11:00
ackwell
5e6ca8b22c Improve fallback handling 2024-01-14 17:37:34 +11:00
ackwell
a6788c6dd3 Add hair and iris support 2024-01-14 17:30:13 +11:00
ackwell
b5d4b31301 Add skin.shpk support 2024-01-14 12:35:26 +11:00
ackwell
f71096f8b0 Handle mask ambient occlusion 2024-01-14 00:38:28 +11:00
Ottermandias
2d8b7efc00 Some cleanup. 2024-01-13 14:10:30 +01:00
ackwell
ca58c81bce Add characterglass support 2024-01-14 00:06:16 +11:00
ackwell
2fa7272762 Add support for explicit diffuse + specular textures 2024-01-13 23:52:26 +11:00
ackwell
e8fd452b8f Improve file reading 2024-01-13 22:16:47 +11:00
ackwell
db2081f14d More refactors of operation, extract specular color 2024-01-13 21:36:42 +11:00
ackwell
4572cb83f0 Move table calcs into struct 2024-01-13 20:48:46 +11:00
ackwell
74ffc56d6c Fix errors with same name expanding together 2024-01-13 20:48:15 +11:00
ackwell
96f40b7ddc Expand to more general-purpose transform codepath 2024-01-13 20:33:45 +11:00
ackwell
509b4c8866 Wire up normals and opacity 2024-01-13 17:05:57 +11:00
ackwell
c8e58c08a0 Compose character diffuse 2024-01-13 16:11:51 +11:00
ackwell
c6642c4fa3 Spike material export workflow 2024-01-13 11:32:26 +11:00
Ottermandias
91cea50f02 Move more hooks in own classes. 2024-01-11 15:31:32 +01:00
Actions User
be588e2fa3 [CI] Updating repo.json for testing_0.8.3.5 2024-01-11 12:33:42 +00:00
Ottermandias
4a6e7fccec Fix scrolling weirdness. 2024-01-11 13:31:33 +01:00
Ottermandias
63fff56a60 Merge remote-tracking branch 'ackwell/mdl-name-io' 2024-01-11 12:47:23 +01:00
ackwell
b81f3f423c Cleanup pass 2024-01-11 21:54:52 +11:00
ackwell
2e473a62f4 Sneak a small one in 2024-01-11 21:28:52 +11:00
ackwell
edcffb9d9f Allow keeping existing mdl attributes 2024-01-11 21:26:34 +11:00
ackwell
a0e9e2ead3 Merge branch 'master' into mdl-name-io 2024-01-11 20:39:44 +11:00
ackwell
dada03905f Import attributes 2024-01-11 18:54:53 +11:00
Ottermandias
0c5d47e3d1 Make Save-in-Place require modifier. 2024-01-10 22:40:10 +01:00
Ottermandias
7f7b35f370 Fix issue with RSP values. 2024-01-10 21:20:19 +01:00
Ottermandias
401704712e Merge remote-tracking branch 'ackwell/mdl-error-messaging' 2024-01-10 16:28:34 +01:00
Ottermandias
4b198ef1e4 Misc 2024-01-10 16:27:11 +01:00
ackwell
182550ce15 Export attributes 2024-01-11 00:34:18 +11:00
ackwell
64aed56f7c Allow keeping existing mdl materials 2024-01-10 22:39:48 +11:00
ackwell
d2f93f8562 Import material names 2024-01-10 20:33:24 +11:00
ackwell
3cd438bb5d Export material names 2024-01-10 01:17:47 +11:00
ackwell
ec114b3f6a Prevent import of models with too many shape values 2024-01-10 00:03:32 +11:00
ackwell
c3ba8a2231 Improve error messaging 2024-01-09 01:24:31 +11:00
Ottermandias
36cbca4684 Add icons to import/export headers. 2024-01-08 13:52:52 +01:00
Actions User
025e3798a7 [CI] Updating repo.json for testing_0.8.3.4 2024-01-07 22:25:53 +00:00
Ottermandias
e344bd4258 Merge branch 'mdl-import' 2024-01-07 23:23:42 +01:00
Ottermandias
8c7c7e20a0 Update OtterGui 2024-01-07 23:23:25 +01:00
Ottermandias
b0f61e6929 Auto formatting, some cleanup, some initialization changes. 2024-01-07 23:22:48 +01:00
Ottermandias
a2b92f1296 Some rework, add drag & drop. 2024-01-07 23:03:52 +01:00
Ottermandias
4c18b747b1 Merge branch 'master' into mdl-import
# Conflicts:
#	Penumbra/Import/Models/ModelManager.cs
#	Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs
2024-01-07 15:30:54 +01:00
Ottermandias
2e935a6378 Update GameData. 2024-01-07 15:14:58 +01:00
Ottermandias
2f6905cf35 Minimal cleanup. 2024-01-07 14:42:16 +01:00
ackwell
3f8ac1e8d0 Add support for body and head slot EST 2024-01-07 21:56:21 +11:00
ackwell
8bc71fb1b3 Fix viera ears 2024-01-07 20:47:44 +11:00
ackwell
0440324432 Genericise est logic to handle face 2024-01-07 20:16:15 +11:00
ackwell
aa7f0bace9 Wire up hair EST resolution 2024-01-07 19:49:13 +11:00
ackwell
b62bc44564 Clean up model import UI/wiring 2024-01-07 11:29:31 +11:00
ackwell
1a88cefd52 Merge branch 'master' into mdl-import 2024-01-07 10:10:39 +11:00
Ottermandias
981721ae85 Fix issue with unloaded Message Service. 2024-01-06 23:26:59 +01:00
Actions User
9311f80455 [CI] Updating repo.json for testing_0.8.3.3 2024-01-06 17:49:43 +00:00
Ottermandias
fe92ac34f0 Merge branch 'mdl-export' 2024-01-06 18:41:50 +01:00
Ottermandias
677c9bd801 Some cleanup and using new features / intellisense recommendations. 2024-01-06 18:37:52 +01:00
Ottermandias
51bb9cf7cd Use existing game path functionality for sklb resolving, some cleanup. 2024-01-06 18:26:30 +01:00
ackwell
13d594ca87 Clean up models 2024-01-06 23:13:34 +11:00
ackwell
1a1c662364 Clean up meshes 2024-01-06 20:40:39 +11:00
ackwell
6de3077afa Clean up submeshes 2024-01-06 16:37:41 +11:00
ackwell
b5b3e1b1f2 Tidy up vertex attributes 2024-01-06 11:55:37 +11:00
Ottermandias
c33545acdf Rework game path selection a bit. 2024-01-05 19:02:50 +01:00
Ottermandias
55f38865e3 Memorize last selected mod and state of advanced editing window. 2024-01-05 18:13:03 +01:00
Ottermandias
306a9c217a Fix FileDialog being drawn multiple times. 2024-01-05 14:51:41 +01:00
Ottermandias
1b98626a61 Merge branch 'master' into mdl-export
# Conflicts:
#	Penumbra/Services/ServiceManagerA.cs
#	Penumbra/UI/AdvancedWindow/ModEditWindow.cs
#	Penumbra/packages.lock.json
2024-01-05 14:31:14 +01:00
Ottermandias
41d900ff51 Update GameData. 2024-01-05 14:27:05 +01:00
Ottermandias
3f1234373d Merge branch 'dev' 2024-01-05 14:25:21 +01:00
ackwell
70a09264a8 Bone table imports 2024-01-05 22:35:36 +11:00
ackwell
6641f5425b Add morph handling for normal/tangent 2024-01-05 20:13:39 +11:00
ackwell
acaa49fec5 Add shape key support 2024-01-05 15:32:31 +11:00
ackwell
4e8695e7a4 Spike submeshes 2024-01-05 01:03:54 +11:00
ackwell
79de6f1714 Basic multi mesh handling 2024-01-04 23:33:54 +11:00
ackwell
b3fe538219 Split vertex attribute logic into seperate file 2024-01-04 21:47:48 +11:00
ackwell
b7edf521b6 SuzanneWalker 2024-01-04 19:35:42 +11:00
ackwell
e8e87cc6cb Whoops 2024-01-02 14:36:24 +11:00
ackwell
655e2fd2ca Flesh out skeleton path resolution a bit 2024-01-02 14:36:18 +11:00
ackwell
d85cbd8051 Add UV2 support 2024-01-02 12:41:14 +11:00
ackwell
215f807483 Fix oversight in bone index mapping generation 2024-01-01 23:52:37 +11:00
ackwell
f71d922198 Merge remote-tracking branch 'upstream/master' into mdl-export 2024-01-01 14:47:57 +11:00
ackwell
bb9e7cac07 Clean up UI 2024-01-01 14:21:38 +11:00
ackwell
73ff3642fc Async game path resolution 2024-01-01 13:30:04 +11:00
ackwell
9f981a3e52 Render export errors 2024-01-01 13:10:50 +11:00
ackwell
a059942bb2 Clean up + docs 2024-01-01 12:57:56 +11:00
ackwell
08ed3ca447 Handle mesh skeleton edge cases 2024-01-01 11:31:38 +11:00
ackwell
518117b25a Add submeshless support 2024-01-01 11:01:31 +11:00
Ottermandias
bc068f9913 Fix offsets. 2024-01-01 00:48:20 +01:00
Ottermandias
68c782f0b9 Move all meta hooks to own classes. 2024-01-01 00:17:15 +01:00
Ottermandias
da019e729d Move all animation and game event hooks to own classes. 2023-12-31 15:10:30 +01:00
ackwell
dc845b766e Clean up top-level conversion utilities. 2024-01-01 00:57:27 +11:00
ackwell
f1379af92c Add UV export 2024-01-01 00:38:24 +11:00
ackwell
551c25a64c Move a few things to export subdir 2024-01-01 00:18:03 +11:00
ackwell
6a2b802196 Add shape key names 2023-12-31 17:11:08 +11:00
ackwell
989915ddbe Add initial shape key support 2023-12-31 16:10:20 +11:00
ackwell
309f0351fa Build indices for entire mesh 2023-12-31 15:33:37 +11:00
Ottermandias
81cdcad72e Improve automatic service detection. 2023-12-30 14:38:52 +01:00
ackwell
f7a2c17415 Quick submesh implementation 2023-12-30 18:31:15 +11:00
ackwell
727fa3c183 Initial pass on skinned mesh output 2023-12-30 17:07:34 +11:00
Actions User
697b5fac65 [CI] Updating repo.json for testing_0.8.3.2 2023-12-29 17:20:11 +00:00
Ottermandias
b5c69b2946 Merge branch 'dev'
# Conflicts:
#	Penumbra/UI/AdvancedWindow/ModEditWindow.cs
2023-12-29 18:14:38 +01:00
ackwell
695c18439d Hook up rudimentary skeleton resolution for equipment models 2023-12-30 02:41:19 +11:00
ackwell
18fd36d2d7 Bit of cleanup 2023-12-29 23:49:55 +11:00
ackwell
71fc901798 Resolve mdl game paths 2023-12-29 19:16:42 +11:00
ackwell
d7cac3e09a Clean up and refactor skeleton logic 2023-12-29 03:06:48 +11:00
ackwell
d646c5e4b5 Resolve skeleton path 2023-12-28 16:49:44 +11:00
ackwell
635d606112 Initial skeleton tests 2023-12-28 15:51:20 +11:00
ackwell
bc24110c9f Move mesh logic to new file, export all meshes 2023-12-28 02:15:14 +11:00
ackwell
ca46e7482f Flesh out geometry handling 2023-12-28 00:44:19 +11:00
ackwell
81425b458e Use vertex element enums 2023-12-27 17:25:14 +11:00
ackwell
b7472f722e poc submesh position export 2023-12-27 16:17:39 +11:00
ackwell
ed283afe2c async is a great idea lets do more of that 2023-12-27 01:44:24 +11:00
ackwell
df43083101 export per example 2023-12-27 01:21:26 +11:00
Ottermandias
f8331bc4d8 Fix the mod panels header not resetting data when a selected mod updates. 2023-12-24 14:36:21 +01:00
Ottermandias
28752e2630 Fix issues with EQDP files for invalid characters. 2023-12-24 14:35:59 +01:00
Ottermandias
19866c5638 Merge branch 'mdl-attributes' 2023-12-22 15:19:01 +01:00
Ottermandias
a001fcf24f Some cleanup. 2023-12-22 15:18:43 +01:00
Ottermandias
dc583cb8e2 Update gamedata. 2023-12-22 14:24:41 +01:00
Ottermandias
4aa19e49d5 Add filtering mods by changed item categories. 2023-12-22 14:22:03 +01:00
ackwell
b22470ac79 Finish up mesh material combos 2023-12-23 00:12:59 +11:00
ackwell
a581495c7e Flesh out material wiring 2023-12-22 23:49:50 +11:00
ackwell
829016a1c4 Spike improved UI 2023-12-22 23:16:23 +11:00
ackwell
72f57d292b group meshes by lod 2023-12-22 03:34:20 +11:00
Ottermandias
2051197c65 Change again. 2023-12-21 15:20:39 +01:00
Ottermandias
c138c39c06 Change CharacterWeapon names. 2023-12-21 15:12:41 +01:00
Ottermandias
bb382459eb Merge branch 'master' into dev 2023-12-21 13:27:14 +01:00
ackwell
17e6838422 Swap to tree nodes for more compact UX 2023-12-21 20:56:15 +11:00
ackwell
7ef50f7bb4 Add material list to further content 2023-12-21 20:56:14 +11:00
ackwell
28246244cd Move persitence logic to tab file 2023-12-21 20:56:14 +11:00
ackwell
f04b295989 Scaffold tab file 2023-12-21 20:56:14 +11:00
ackwell
27123f2a64 Inline submesh UI, fix visual offset 2023-12-21 20:56:14 +11:00
ackwell
8ba20218c6 whoops 2023-12-21 20:56:13 +11:00
ackwell
49b63d2208 Draw the rest of the owl 2023-12-21 20:56:13 +11:00
ackwell
2a0e6ce1aa WIP .mdl updates 2023-12-21 20:56:13 +11:00
Actions User
6d89ea5a71 [CI] Updating repo.json for 0.8.3.1 2023-12-21 09:43:40 +00:00
Ottermandias
969ba38ffe Prevent layer editing. 2023-12-21 10:41:09 +01:00
Ottermandias
f022d2be64 Rework DalamudServices, 2023-12-20 18:47:30 +01:00
Ottermandias
d8f5851e0c Move signatures and add Footsteps. 2023-12-20 16:43:30 +01:00
Ottermandias
5d28904bdf Update for GameData changes. 2023-12-20 16:39:26 +01:00
Ottermandias
6dc5916f2b Fix some issues. 2023-12-18 17:02:08 +01:00
Ottermandias
b494892d62 Update for changed GameData. 2023-12-18 16:50:52 +01:00
Ottermandias
7d612df951 Update for changed GameData. 2023-12-17 11:51:24 +01:00
Ottermandias
3305250482 Misc. 2023-12-14 14:24:05 +01:00
Ottermandias
0514e72d47 Update sizing for option groups. 2023-12-13 20:47:18 +01:00
Ottermandias
173b4d7306 Respect ascii setting for group names. 2023-12-12 21:05:16 +01:00
Actions User
76cb09b3b5 [CI] Updating repo.json for 0.8.3.0 2023-12-10 14:43:34 +00:00
Ottermandias
54776c45ea 0.8.3.0 2023-12-10 15:41:38 +01:00
Ottermandias
59ea1f2dd6 Add option to clear non-ascii symbols from paths again. 2023-12-10 15:41:26 +01:00
Ottermandias
b14cd26e4e Merge branch 'master' of github.com:xivDev/Penumbra 2023-12-10 15:10:47 +01:00
Ottermandias
bb742463e9 Wait for saves to finish when the file might be read immediately after saving. 2023-12-10 15:10:43 +01:00
Ottermandias
a9f36c6aef Fix inverted percentage, skill. 2023-12-10 15:09:37 +01:00
Actions User
7128f237da [CI] Updating repo.json for testing_0.8.2.3 2023-12-01 15:31:41 +00:00
Ottermandias
a0328aab35 Compile releases from release dalamud, not staging. 2023-12-01 16:29:44 +01:00
Actions User
e0fa8c9285 [CI] Updating repo.json for testing_0.8.2.2 2023-12-01 13:35:51 +00:00
Ottermandias
2c1ce66011 Update Penumbra.Api. 2023-12-01 14:32:13 +01:00
Ottermandias
222a0e5f77 Merge branch 'refs/heads/WorkingRobot/master' 2023-12-01 14:31:00 +01:00
Ottermandias
07e20fb670 Test not redrawing while the fishing rod is out. 2023-12-01 13:49:29 +01:00
Asriel Camora
b595a0da0f
Replace ResourceTree IEnumerables with lists 2023-11-30 10:01:49 -08:00
Asriel Camora
eb0e334437
Add ResourceTree ipc disposes 2023-11-30 09:53:16 -08:00
Ottermandias
e497414cb7 Auto-Formatting and some imgui layouting. 2023-11-29 18:00:30 +01:00
Ottermandias
e0749bb791 Merge remote-tracking branch 'Exter-N/rt-filtering' 2023-11-29 17:47:58 +01:00
Ottermandias
909778c5b4 Merge branch 'editor-highlight-player-paths' 2023-11-29 17:33:10 +01:00
Ottermandias
3d9f8355d2 Make braces clear for scoped using. 2023-11-29 17:32:59 +01:00
Ottermandias
4aa1388b34 Update actions for recursive submodules. 2023-11-29 17:30:57 +01:00
Ottermandias
b3c757c37b Merge branch 'master' into editor-highlight-player-paths 2023-11-29 17:29:09 +01:00
Ottermandias
b727220775 Update OtterGui. 2023-11-29 17:27:09 +01:00
Ottermandias
1101a7a986 Make sure the SubstitutionProvider is initialized before the interface. 2023-11-29 17:23:37 +01:00
Exter-N
18d38a9974 Mod Edit Window: Highlight paths of files on player 2023-11-29 14:31:47 +01:00
Asriel Camora
bb3d3657ed
Add ResourceTree ipc tests 2023-11-28 12:33:37 -08:00
Asriel Camora
d647a62e82
Add ResourceTree ipc structure 2023-11-28 12:33:19 -08:00
Asriel Camora
0f03e0484c
Add ipc GetPlayerResourceTrees, change ipc resource node to be nested 2023-11-28 10:46:03 -08:00
Asriel Camora
73af509885
Add GetGameObjectResourceTrees ipc method 2023-11-28 10:28:37 -08:00
Exter-N
5ebab472b8 Import from Screen: Add option selector 2023-11-26 21:26:35 +01:00
Exter-N
d05f369a94 ResourceTree: Adjustments on filtering 2023-11-26 20:56:35 +01:00
Exter-N
5e76ab3b84 ResourceTree: Add filtering to the UI 2023-11-26 20:18:36 +01:00
Ottermandias
a408b8918c Make hooks not leak. 2023-11-25 18:30:43 +01:00
Actions User
43c6b52d0b [CI] Updating repo.json for 0.8.2.1 2023-11-22 14:27:30 +00:00
Exter-N
a6f7fd623c ResourceTree: Fix model path resolving 2023-11-22 15:24:57 +01:00
Actions User
4204262236 [CI] Updating repo.json for 0.8.2.0 2023-11-22 14:20:25 +00:00
Ottermandias
8a67521511 0.8.2.0 2023-11-22 15:18:10 +01:00
Ottermandias
55ddafea4b 0.8.2.0 2023-11-22 15:16:13 +01:00
Actions User
357b11eb25 [CI] Updating repo.json for testing_0.8.1.10 2023-11-18 12:17:35 +00:00
Ottermandias
85500f0e9d Improve Multi Mod selection. 2023-11-18 13:15:33 +01:00
Ottermandias
3e6967002b Allow filtering for none in certain cases. 2023-11-18 13:15:14 +01:00
Exter-N
d84715ad27 Fix stain preview (√ + order) 2023-11-17 23:53:40 +01:00
Ottermandias
69c493b9d6 Morp. 2023-11-17 17:09:11 +01:00
Ottermandias
908239bf13 Meep. 2023-11-17 16:53:42 +01:00
Ottermandias
ea65296ab7 Add EphemeralConfig. 2023-11-17 16:51:09 +01:00
Ottermandias
dc1c8f42c0 Improve Color Preview. 2023-11-17 15:46:54 +01:00
Ottermandias
2fd8c98147 Add furniture to redraw bar and help, improve redraw bar slightly. 2023-11-17 15:25:44 +01:00
Ottermandias
c88f1a7b1c Add color preview for dye template selection. 2023-11-17 15:25:44 +01:00
Actions User
7e8cd719fd [CI] Updating repo.json for testing_0.8.1.9 2023-11-17 12:11:06 +00:00
Ottermandias
806561b95a Merge branch 'rt-rework' 2023-11-17 13:09:04 +01:00
Ottermandias
8caba8c339 Update GameData again. 2023-11-16 21:16:38 +01:00
Exter-N
63ca044586 Update GameData 2023-11-16 05:48:30 +01:00
Exter-N
3d38495f92 Merge branch 'master' into rt-rework 2023-11-15 20:06:28 +01:00
Exter-N
acfd5d2484 Update Penumbra.GameData
Also remove a check that, if it was still valid, would always be false with the new changes.
2023-11-15 20:04:30 +01:00
Ottermandias
aee942468e Only allow redrawing furniture inside. 2023-11-15 18:34:24 +01:00
Ottermandias
d026ca888f Add Furniture Redrawing despite crash. 2023-11-15 16:10:22 +01:00
Ottermandias
ab902cbe9e Fix issue with ring identification 2023-11-15 16:10:22 +01:00
Exter-N
13044763cb Redraw player ornament, allow redrawing by index 2023-11-15 15:46:10 +01:00
Exter-N
cb43fed9d3 ResourceTree: Handle monster MTRL 2023-11-14 21:14:01 +01:00
Exter-N
60551c8739 ResourceTree: Are we fast yet? 2023-11-14 20:38:21 +01:00
Exter-N
b2bf6eb0f7 ResourceTree: Handle weapon MTRL special cases 2023-11-13 07:44:48 +01:00
Exter-N
4e26f09109 Merge branch 'master' into rt-rework 2023-11-12 22:47:25 +01:00
Ottermandias
51dba221c4 Add option to open window at game start instead of coupling it with debug mode 2023-11-11 21:09:59 +01:00
Actions User
b5377a961f [CI] Updating repo.json for 0.8.1.8 2023-11-11 13:18:07 +00:00
HoloWise
da880bd76c Fix broken tooltips 2023-11-11 14:11:42 +01:00
Ottermandias
5a64eadb5c Update Stain Data. 2023-11-11 13:59:59 +01:00
Ottermandias
50a7015bc5 Update BNPC Data. 2023-11-11 13:58:14 +01:00
Exter-N
fd163f8f66 ResourceTree: WIP - Path resolution 2023-11-04 18:30:36 +01:00
Exter-N
2852562a03 ResourceTree: Use ResolveXXXPath where possible 2023-11-03 20:30:29 +01:00
Exter-N
c024d7e826 Merge branch 'master' into HEAD 2023-11-03 20:30:23 +01:00
Ottermandias
7dabb3c647 Add some Redrawing Debug UI. 2023-11-03 15:23:48 +01:00
Exter-N
79c43fe7b1 PathResolving: Better function signatures? (names + types + TMB param count + dedupe) 2023-11-03 13:22:07 +01:00
Ottermandias
69a4e2b52e Fix Linking changed items not working. 2023-11-03 12:59:42 +01:00
Exter-N
da54222bb1 ResourceTree: Add EID files 2023-11-02 20:59:09 +01:00
Exter-N
57f8587a43 ResourceTree: Use both game path and resource handle as keys for dedup 2023-11-02 01:18:20 +01:00
Exter-N
28a396470b ResourceTree: De-inline GlobalResolveContext in ResolveContext 2023-11-02 01:11:48 +01:00
Exter-N
3da20f2d89 ResourceTree: Rework Internal flag, improve null checks, simplify 2023-11-02 01:08:02 +01:00
Exter-N
db9bfb00a3 ResourceTree: Show SKP files out of Debug 2023-11-01 03:07:33 +01:00
Exter-N
5085aa500c ResourceTree: Use DrawObject as equipment source + CS-ify a bit more 2023-11-01 03:07:33 +01:00
Exter-N
00dc5f48b1 ClientStructs-ify a few things 2023-11-01 03:07:33 +01:00
Actions User
6375faa758 [CI] Updating repo.json for 0.8.1.7 2023-10-31 11:23:59 +00:00
Ottermandias
8e63452e84 Use some CS sigs. 2023-10-31 11:32:40 +01:00
Ottermandias
06e06b81e9 Support correct handling of offhands in changed items. 2023-10-28 01:13:18 +02:00
Ottermandias
c76a9ace24 Update API Nuget. 2023-10-22 15:40:39 +02:00
Actions User
8fd755c5e6 [CI] Updating repo.json for 0.8.1.6 2023-10-22 13:40:09 +00:00
Ottermandias
2cb92d817a Fix directory rename not updating paths in advanced window. 2023-10-22 15:37:52 +02:00
Ottermandias
25e9a99799 Fix portraits not respecting card settings. 2023-10-22 15:37:34 +02:00
Ottermandias
f910dcf1e0 Add ReverseResolvePlayerPathsAsync. 2023-10-22 15:36:47 +02:00
Ottermandias
f2ef0e15d3 Draw associated BNPCs in debug tab. 2023-10-17 19:42:55 +02:00
Actions User
23f46438a2 [CI] Updating repo.json for 0.8.1.5 2023-10-16 13:13:05 +00:00
Ottermandias
5e79a13708 Expand tooltip for Wait for Plugins on Startup. 2023-10-16 15:10:41 +02:00
Ottermandias
5d2fc72883 Update submodules. 2023-10-16 15:06:38 +02:00
Haselnussbomber
3c59a57ab0 Disable window sounds in ImportPopup 2023-10-10 22:00:05 +02:00
Ottermandias
c24a40fd9f Add support for middle-clicking mods. 2023-10-10 18:02:04 +02:00
Actions User
f5822cf2c8 [CI] Updating repo.json for 0.8.1.4 2023-10-09 22:18:33 +00:00
Ottermandias
4bdc8f126d Make ActorData great again. 2023-10-09 00:10:45 +02:00
Actions User
764ef76e1a [CI] Updating repo.json for 0.8.1.3 2023-10-08 13:08:02 +00:00
Ottermandias
3699923938 Improve save logging. 2023-10-08 15:03:24 +02:00
Ottermandias
4378c826f0 Add some log statements. 2023-10-08 13:19:01 +02:00
Actions User
717ddba8d2 [CI] Updating repo.json for 0.8.1.2 2023-10-07 13:34:11 +00:00
Ottermandias
9871421632 0.8.1.2 2023-10-07 15:31:18 +02:00
Ottermandias
7cdd8656ef Maybe fix issue with individual assignments sometimes getting eaten. 2023-10-06 16:21:39 +02:00
Ottermandias
2d007c189f Fix deletion in selector. 2023-10-06 16:21:39 +02:00
Actions User
9fda19d4c0 [CI] Updating repo.json for 0.8.1.1 2023-10-05 19:25:42 +00:00
Ottermandias
110298f280 Increment Changelog Version. 2023-10-05 21:23:02 +02:00
Ottermandias
422324b6d7 Revert stupid changes due to ResourceCategory change. 2023-10-05 21:22:09 +02:00
Ottermandias
48863c1b64 Merge branch 'master' of github.com:xivDev/Penumbra 2023-10-05 20:31:52 +02:00
Ottermandias
de3a74bbe8 Change SubModules to https 2023-10-05 20:29:34 +02:00
Actions User
3b593103ba [CI] Updating repo.json for 0.8.1.0 2023-10-05 16:33:35 +00:00
Ottermandias
dd587350ea Update changelog discord export. 2023-10-05 18:30:57 +02:00
Ottermandias
52d38eda3a 0.8.1.0 Changelog 2023-10-05 18:21:47 +02:00
Ottermandias
779d6b37a5 Improved messaging. 2023-10-05 18:21:16 +02:00
Ottermandias
19c4c3b50e Merge remote-tracking branch 'Exter-N/stuff-for-65' 2023-10-05 17:57:00 +02:00
Exter-N
c487fb12ec CS-ify LiveMaterialPreviewer and add new shpk name 2023-10-05 17:50:38 +02:00
Ottermandias
8b5437c2c7 Remove CS temp fix. 2023-10-05 17:39:38 +02:00
Ottermandias
73b4227310 Fix slash commands. 2023-10-05 16:23:17 +02:00
Actions User
30a55d401f [CI] Updating repo.json for testing_0.8.0.4 2023-10-04 20:06:28 +00:00
Ottermandias
0aeb407a01 Update CopyCharacterEvent. 2023-10-04 22:02:32 +02:00
Actions User
53f1efa88b [CI] Updating repo.json for testing_0.8.0.3 2023-10-04 18:33:22 +00:00
Ottermandias
069929ce24 Some updates. 2023-10-04 20:30:38 +02:00
Actions User
c21cbcdcd3 [CI] Updating repo.json for testing_0.8.0.2 2023-10-04 14:20:40 +00:00
Ottermandias
8e0877659f Update LoadCharacterSound. 2023-10-04 16:17:13 +02:00
Ottermandias
83ab8e8003 Temporarily fix ShaderPackage.MaterialElement. 2023-10-04 16:17:13 +02:00
Actions User
a18ace433a [CI] Updating repo.json for testing_0.8.0.1 2023-10-04 12:37:47 +00:00
Ottermandias
58b5c44157 Fix an issue with memory locations that suddenly caused issues? 2023-10-04 14:35:03 +02:00
Ottermandias
5fefdfa33b Fix error in log about existing command. 2023-10-04 14:03:51 +02:00
Ottermandias
fb591429d6 Update Sigs. 2023-10-04 03:19:05 +02:00
Ottermandias
e5427858e0 Add support for ActorIdentifier.FromUserString returning multiple identifiers. 2023-10-03 01:43:18 +02:00
Ottermandias
3d2ce1f4bb Use ClientStructs hook for CalculateHeight. 2023-10-02 23:25:15 +02:00
Ottermandias
95746e7450 Merge branch 'API9' 2023-10-02 23:23:56 +02:00
Ottermandias
5394bdc535 Update Submodules. 2023-10-02 23:23:26 +02:00
Ottermandias
8f16aa7ee9 Fix collection button ids. 2023-10-02 23:21:07 +02:00
Ottermandias
0dc06a1733 Add multi deletion to mod selector. 2023-09-29 02:18:44 +02:00
Ottermandias
4e40ed3be4 Merge remote-tracking branch 'Exter-N/allow-intentional-zero-gloss' into API9 2023-09-28 18:13:07 +02:00
Ottermandias
21d503a8cd Update for API 9 2023-09-28 18:12:27 +02:00
Ottermandias
50f6de7809 Update API Level. 2023-09-28 17:26:39 +02:00
Ottermandias
c09568e406 Oops. 2023-09-27 15:59:42 +02:00
Ottermandias
929db5c1a4 Make renaming search paths in context more clear. 2023-09-27 15:52:42 +02:00
Exter-N
6799bdbb03 Material Editor: Allow intentional 0 gloss 2023-09-27 03:15:18 +02:00
Actions User
6ca8ad2385 [CI] Updating repo.json for 0.8.0.0 2023-09-26 13:46:33 +00:00
Ottermandias
efdebeca54 Add 0.8.0.0 Changelog 2023-09-26 15:36:49 +02:00
Ottermandias
677d44442b Enable reset of substitutions. 2023-09-26 14:18:45 +02:00
Ottermandias
6130929985 Update API. 2023-09-26 14:16:09 +02:00
Actions User
4c73453b4c [CI] Updating repo.json for testing_0.7.3.13 2023-09-23 12:30:49 +00:00
Ottermandias
8aebd441a1 Add option to disable conflicts from conflict panel. 2023-09-23 14:25:47 +02:00
Ottermandias
2c0650614f Merge branch 'mtrl-preview-extension' 2023-09-21 02:16:22 +02:00
Ottermandias
3f439bacb2 Extract remaining global usings for System libs. 2023-09-21 02:15:23 +02:00
Ottermandias
11bf0d2998 Optimize ResourceTree somewhat. 2023-09-21 02:06:45 +02:00
Ottermandias
40b6c6022a Add automatic restore from backup for sort_order and active_collections for now. 2023-09-20 18:51:17 +02:00
Exter-N
69388689ac Material Editor: Extend live preview. 2023-09-20 01:53:10 +02:00
Actions User
5a24d9155b [CI] Updating repo.json for testing_0.7.3.12 2023-09-19 19:52:53 +00:00
Ottermandias
1760efb477 Fix ambiguous reference for no fucking reason. 2023-09-19 21:50:43 +02:00
Ottermandias
348480ed68 Update OtterGui. 2023-09-19 21:46:15 +02:00
Ottermandias
c29d0a5a4c Remove some allocations from resource tree. 2023-09-19 21:44:49 +02:00
Ottermandias
89c7095843 Merge branch 'restree-bulk-reverse' 2023-09-19 21:25:36 +02:00
Ottermandias
808d7ab017 Add CalculateHeight Hook 2023-09-19 20:18:53 +02:00
Exter-N
f02a37b939 ResourceTree: Reverse-resolve in bulk 2023-09-19 01:32:31 +02:00
Actions User
69012e5ecd [CI] Updating repo.json for testing_0.7.3.11 2023-09-18 17:51:30 +00:00
Ottermandias
5067ab2bb2 Add load state to resource watcher. 2023-09-18 18:18:23 +02:00
Ottermandias
5506dcc3f3 Api nuget version. 2023-09-18 17:20:19 +02:00
Ottermandias
fee99dd17e Fix params bug. 2023-09-18 17:18:11 +02:00
Ottermandias
4ffb69ea31 Remove enums folder from csproj?! 2023-09-18 17:14:42 +02:00
Ottermandias
3905d5b976 Rename ResourceType file. 2023-09-18 17:14:16 +02:00
Ottermandias
ea79469abd Move IPC Arguments around. 2023-09-18 17:06:16 +02:00
Exter-N
a241b933ca ResourceTree: Avoid enumerating the whole object table in some cases 2023-09-18 17:02:11 +02:00
Exter-N
22966e648d ResourceTree IPC: Remove mergeSameCollection. 2023-09-18 17:02:11 +02:00
Exter-N
d7205344eb ResourceTree improvements + IPC
- Moves ResourceType enum out of GameData as discussed on Discord ;
- Adds new color coding for local player and non-networked objects on On-Screen ;
- Adds ResourceTree-related IPC ;
- Fixes #342.
2023-09-18 17:02:08 +02:00
Ottermandias
2b4a01df06 Make line endings explicit in editorconfig and share in sub projects, also apply editorconfig everywhere and move some namespaces. 2023-09-18 17:01:21 +02:00
Ottermandias
53adb6fa54 Use System global usings. 2023-09-15 14:15:52 +02:00
Ottermandias
25cdc00404 Merge branch 'color-table' 2023-09-15 14:00:44 +02:00
Ottermandias
916ff0cbb2 Auto Formatting. 2023-09-15 14:00:30 +02:00
Ottermandias
50d7619dde GameData Commit. 2023-09-15 13:57:39 +02:00
Exter-N
28c2af4266 Material Editor: Split ColorTable apart from ColorSet 2023-09-15 13:38:47 +02:00
Ottermandias
652b2e99d2 Add key checks to restoring from backup or deleting backups. 2023-09-15 01:07:01 +02:00
Ottermandias
c5ef7bf46c Add Compacting to API AddMod. 2023-09-15 01:07:01 +02:00
Actions User
470c1317ed [CI] Updating repo.json for testing_0.7.3.10 2023-09-14 15:26:36 +00:00
Ottermandias
4e704770cb Add Filesystem Compression as a toggle and button. Also some auto-formatting. 2023-09-14 17:24:20 +02:00
Actions User
e26873934b [CI] Updating repo.json for testing_0.7.3.9 2023-09-13 18:11:56 +00:00
Ottermandias
7431db2a08 Fix click check for selectables. 2023-09-13 20:09:09 +02:00
Actions User
b352373a52 [CI] Updating repo.json for testing_0.7.3.8 2023-09-13 15:09:13 +00:00
Ottermandias
d21cba4669 Allow drag & drop of multiple mods or folders with Control. 2023-09-13 17:06:35 +02:00
Actions User
4fdb89ce62 [CI] Updating repo.json for testing_0.7.3.7 2023-09-11 14:26:51 +00:00
Ottermandias
8eaf14d932 Add Player and Interface to quick select collections and rework their tooltips and names slightly. 2023-09-11 16:24:07 +02:00
Ottermandias
569fa06e18 Fix CS update creating ambiguous reference. 2023-09-08 14:04:14 +02:00
Ottermandias
40eb0c81b8 Update GameData for new parsing. 2023-09-08 13:59:53 +02:00
Ottermandias
ed243df4f3 Fix changed item flags for emotes. 2023-09-08 13:59:53 +02:00
Actions User
4bd3fd357f [CI] Updating repo.json for testing_0.7.3.6 2023-09-06 19:19:57 +00:00
Ottermandias
6cc89f3e7c Add Emotes to Changed Items. 2023-09-06 21:15:12 +02:00
Actions User
a890258cf5 [CI] Updating repo.json for testing_0.7.3.5 2023-09-05 12:51:00 +00:00
Ottermandias
1cb74aeb9a Merge branch 'fix/skin-fixer-rc' 2023-09-05 14:48:18 +02:00
Ottermandias
0e0733dab0 Some formatting, use ConcurrentSet explicitly for clarity. 2023-09-05 14:48:06 +02:00
Exter-N
32608ea45b Skin Fixer: Switch to a passive approach.
Do not load skin.shpk for ourselves as it causes a race condition.
Instead, inspect the materials' ShPk names.
2023-09-05 12:53:53 +02:00
Exter-N
94a0a3902c Resource Tree: Use /s for game actual paths 2023-09-03 22:04:11 +02:00
Ottermandias
176956a1f8 Merge branch 'restree-less-io' 2023-09-03 13:13:54 +02:00
Ottermandias
2a2fa3bf1d Some auto-formatting and ROS iteration for lookups. 2023-09-03 13:13:35 +02:00
Exter-N
cca626449d Resource Tree: Fix shared model fold state 2023-09-03 05:50:51 +02:00
Exter-N
a17a1e9576 Resource Tree: Make skp child of sklb 2023-09-02 17:59:13 +02:00
Exter-N
30c622c085 Resource Tree: Add ChangedItem-like icons, make UI prettier 2023-09-02 17:59:13 +02:00
Exter-N
db521dd21c Resource Tree: Deduplicate nodes, add skp 2023-09-02 17:59:13 +02:00
Exter-N
ccc0b51a99 Resource Tree: Improve mtrl and sklb support 2023-09-02 17:59:09 +02:00
Actions User
ecfe88faa6 [CI] Updating repo.json for testing_0.7.3.4 2023-09-02 14:00:06 +00:00
Ottermandias
052811049e Merge remote-tracking branch 'Exter-N/material-editor-adjustments' 2023-09-02 15:56:08 +02:00
Ottermandias
0741ce0ce7 Fix variant gamepath. 2023-09-02 15:55:06 +02:00
Ottermandias
b985833aaa Check for drawObject != null before invoking draw object created event. 2023-09-02 15:54:11 +02:00
Exter-N
1b490510c7 Fix compiler warning 2023-09-01 19:09:38 +02:00
Exter-N
5899a59e06 Material editor: Vector field spacing 2023-09-01 18:46:40 +02:00
Exter-N
686c53d919 Material editor: Customizable highlight color 2023-09-01 18:46:31 +02:00
Exter-N
233a865c78 Material editor: use a SafeHandle for texture swapping 2023-09-01 17:57:01 +02:00
Ottermandias
0dbe9b59c2 Cleanup 2023-09-01 17:31:46 +02:00
Ottermandias
6f760426c7 Fix newtonsoft not playing well with records with strings. 2023-09-01 17:31:46 +02:00
Actions User
af4373ce50 [CI] Updating repo.json for testing_0.7.3.3 2023-08-31 16:36:03 +00:00
Ottermandias
82cecdaf7d Merge branch 'feature/material-editor-2099' 2023-08-31 18:32:43 +02:00
Ottermandias
616a4635d1 Fix slash direction in material path. 2023-08-31 18:32:18 +02:00
Ottermandias
a768b039a8 Restructure Live Preview. 2023-08-31 18:25:29 +02:00
Ottermandias
e5e555b981 Auto-formatting and some cleanup. 2023-08-31 17:12:39 +02:00
Ottermandias
ff01276869 Small cleanup in ResolveContext. 2023-08-31 01:11:57 +02:00
Ottermandias
5023fafc19 Some formatting in Materials.Shpk. 2023-08-31 01:01:10 +02:00
Ottermandias
2ac997610d Remove Finalize from FileEditor. 2023-08-31 01:00:46 +02:00
Ottermandias
8695e89792 Merge branch 'master' into feature/material-editor-2099 2023-08-31 00:49:49 +02:00
Ottermandias
5ba993cd6f Merge branch 'feature/skin-shpk-fixer' 2023-08-30 20:53:07 +02:00
Ottermandias
6d3e930440 Use better event in SkinFixer and some cleanup. 2023-08-30 20:52:39 +02:00
Exter-N
f238049750 Skin Fixer: Fix potential ref leak + add SRH
`SafeResourceHandle` wraps a `ResourceHandle*` with auto `IncRef` / `DecRef`, to further help prevent leaks.
2023-08-30 19:16:22 +02:00
Ottermandias
600f5987cd Slight restructuring. 2023-08-30 17:25:26 +02:00
Exter-N
38a22c5298 Textures: Simplify away _targetPixels 2023-08-30 02:53:37 +02:00
Exter-N
5346abaadf Material editor: tear down previewers bound to a CharacterBase that goes away 2023-08-30 01:51:43 +02:00
Exter-N
bb8d9441f4 Material editor: tweak colorset highlighting
Make the frequency framerate-independent, set it to 1 Hz, and decrease the dynamic range.

Thanks @StoiaCode for feedback!
2023-08-30 01:14:20 +02:00
Exter-N
848e4ff8a6 Textures: Refactor resizing code 2023-08-29 03:25:54 +02:00
Exter-N
598f3db06a Textures: PR #327 feedback 2023-08-29 00:42:59 +02:00
Exter-N
f54146ada4 Textures: PR #327 feedback 2023-08-28 03:30:21 +02:00
Exter-N
ffb8f0e8d3 Material editor: Allow negatives again with R²G²B²
There seems to be people using it.
2023-08-28 03:06:18 +02:00
Exter-N
6c0864c8b9 Textures: Add a matrix preset that drops alpha 2023-08-28 01:54:14 +02:00
Exter-N
ec14efb789 Skin Fixer: Make resolving skin.shpk for new draw objects async 2023-08-27 04:04:14 +02:00
Exter-N
ead88f9fa6 Skin Fixer (fixes modding of skin.shpk) 2023-08-27 03:45:05 +02:00
Exter-N
99b43bf577 Textures: Automatic resizing 2023-08-26 13:49:48 +02:00
Exter-N
792707a6e3 Textures: Renumber CombineOps.
Positive values in this enum also double as indices into the labels and tooltip arrays.

(confirmed skill issue moment)
2023-08-26 13:44:34 +02:00
Ottermandias
4f71065d67 Merge remote-tracking branch 'refs/remotes/Exter-N/feature/texture-stuff'
# Conflicts:
#	Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs
2023-08-25 18:07:04 +02:00
Exter-N
781bbb3d26 Textures: Un-merge save buttons, make ignore unselectable 2023-08-25 17:59:18 +02:00
Ottermandias
87c5164367 Small cleanup, auto-formatting. 2023-08-25 17:57:57 +02:00
Exter-N
afd7aab37d Update GameData 2023-08-25 17:46:53 +02:00
Exter-N
42b874413d Add a few texture manipulation tools. 2023-08-25 06:37:24 +02:00
Exter-N
9364ecccd2 Material editor: better color constants 2023-08-25 06:17:54 +02:00
Exter-N
b8d09ab660 Material editor 2099 2023-08-25 01:19:39 +02:00
Exter-N
f64fdd2b26 Material editor: live-preview changes 2023-08-24 05:52:13 +02:00
Exter-N
ccca2f1434 Material editor: improve color accuracy 2023-08-24 05:52:13 +02:00
Ottermandias
3530e139d1 Add some unnamed mounts to actor identification. 2023-08-23 18:47:22 +02:00
Ottermandias
a6ae580b9f Explain comment. 2023-08-23 18:42:19 +02:00
Actions User
3a8bf5dfa1 [CI] Updating repo.json for 0.7.3.2 2023-08-22 13:21:39 +00:00
Ottermandias
00adaca32e Keep the texture alive during write. 2023-08-22 15:18:59 +02:00
Ottermandias
bc6e9d1d84 Update DirectXTex/OtterTex 2023-08-22 15:18:30 +02:00
Ottermandias
ad830dc56e Disable UI for textures when converting. 2023-08-22 14:18:36 +02:00
Actions User
ebaa42f311 [CI] Updating repo.json for 0.7.3.1 2023-08-18 18:37:27 +00:00
Ottermandias
4c611530f3 Temporarily not use dalamud function because it is not available in release yet. 2023-08-18 20:34:23 +02:00
Actions User
82ba2cd16a [CI] Updating repo.json for 0.7.3.0 2023-08-18 18:07:12 +00:00
Ottermandias
635d5e05ce 0.7.3.0 2023-08-18 20:05:15 +02:00
Ottermandias
53b36f2597 Add drag & drop to texture import. 2023-08-18 17:16:38 +02:00
Ottermandias
0c07d4bec6 Cleanup. 2023-08-16 20:13:34 +02:00
Ottermandias
04b76ddee1 Add support for the DalamudSubstitutionProvider for textures. 2023-08-16 17:25:06 +02:00
Ottermandias
cf3810a1b8 Add filter to texturedrawer. 2023-08-13 14:30:06 +02:00
Ottermandias
af536b3423 Update some Dalamud Services. 2023-08-10 18:10:33 +02:00
Ottermandias
09ca32f33d Add DalamudSubstitutionProvider 2023-08-10 18:10:33 +02:00
Actions User
df808187e2 [CI] Updating repo.json for testing_0.7.2.7 2023-08-10 15:07:04 +00:00
Ottermandias
e615ffcf3d Increment API 2023-08-10 17:01:55 +02:00
Ottermandias
6e11b36401 Add Texture Conversion IPC and use texture tasks. 2023-08-10 16:55:43 +02:00
Ottermandias
af93c2aca9 Revert CS change. 2023-08-10 14:32:02 +02:00
Ottermandias
e24a535a93 Initial Texture rework. 2023-08-08 01:10:57 +02:00
Ottermandias
2f836426d6 Temporary fix for broken CS offset. 2023-08-04 17:01:59 +02:00
Ottermandias
2a7ccb952d Fix missing scaling for item combos. 2023-08-04 17:01:59 +02:00
Ottermandias
0e252d489a Update SharpCompress. 2023-08-04 17:01:59 +02:00
Actions User
af0edf3002 [CI] Updating repo.json for testing_0.7.2.6 2023-08-01 18:40:28 +00:00
Ottermandias
01b88950bf Fix shit. 2023-08-01 20:38:03 +02:00
Actions User
622af4e7e9 [CI] Updating repo.json for testing_0.7.2.5 2023-08-01 15:45:07 +00:00
Ottermandias
930931a846 Fix ChangeCustomize not loading decals from collections. 2023-08-01 17:39:41 +02:00
Ottermandias
2da6a33a62 Some texfile formatting. 2023-08-01 13:12:07 +02:00
Ottermandias
a95877b9e4 Add priority display to mod selector. 2023-08-01 13:12:07 +02:00
Actions User
3738b5f8f0 [CI] Updating repo.json for testing_0.7.2.4 2023-07-30 11:27:53 +00:00
Ottermandias
4df616e4c0 Fix CustomItem recognition. 2023-07-30 13:23:03 +02:00
Ottermandias
1d5e050de6 Move some classes. 2023-07-29 02:46:43 +02:00
Ottermandias
ef916fc93c Add backup option for failure to load option groups. 2023-07-29 02:46:30 +02:00
Ottermandias
18b6b87e6b Use strongly typed ids in most places. 2023-07-29 02:22:31 +02:00
Ottermandias
dccd347432 Fix some EquipItem stuff. 2023-07-25 17:24:00 +02:00
Ottermandias
ec1dee8871 Add GameData as submodule. 2023-07-25 15:56:38 +02:00
Ottermandias
b8c9a98ba2 Remove local GameData 2023-07-25 15:52:39 +02:00
Ottermandias
8d7c779439 Fix dumb. 2023-07-25 15:41:38 +02:00
Ottermandias
a6d68ddd5a Add some known models to identification. 2023-07-24 01:40:56 +02:00
Ottermandias
96e6ff0fbf Fix potential crash in decref. 2023-07-24 01:40:56 +02:00
Ottermandias
4f2a14c9ee Change some item data. 2023-07-24 01:40:56 +02:00
Actions User
774f93f962 [CI] Updating repo.json for testing_0.7.2.3 2023-07-18 22:48:42 +00:00
Ottermandias
27c9523bd7 Small Changes. 2023-07-19 00:44:21 +02:00
Ottermandias
808dabf600 Add DragDropManager. 2023-07-19 00:44:21 +02:00
Ottermandias
344defca8e Typo. 2023-07-19 00:44:21 +02:00
Actions User
65fc029218 [CI] Updating repo.json for 0.7.2.2 2023-07-12 23:07:33 +00:00
Ottermandias
c3b106e359 Remove outdated file. 2023-07-13 00:45:01 +02:00
Ottermandias
62f71df28c Some further small Glamourer changes, increment versioning of gamedata stuff. 2023-07-13 00:43:41 +02:00
Ottermandias
0fb9e77c3c Fix gloss and bug. 2023-07-12 13:11:18 +02:00
Ottermandias
9e0c38169f Glamourer-related changes. 2023-07-12 02:45:40 +02:00
Ottermandias
a7ace8a8c8 Oops. 2023-07-07 00:51:57 +02:00
Ottermandias
64668e0e03 Improve save service. 2023-07-07 00:46:42 +02:00
Ottermandias
4626c2176f Fix multiple options with same label. 2023-07-07 00:46:42 +02:00
Ottermandias
2bc7eb165e Add toggle for the Changed Item category filter. 2023-07-07 00:46:42 +02:00
Actions User
0521cf0d18 [CI] Updating repo.json for 0.7.2.1 2023-07-05 22:53:38 +00:00
Ottermandias
6e7805d58f Fix handling of decals overall. 2023-07-06 00:51:16 +02:00
Ottermandias
e3a608fe0e Fix DecalReverter using wrong variables. 2023-07-06 00:51:16 +02:00
Ottermandias
a6b929c207 Remove GPose condition from timeline loading restrictions. 2023-07-06 00:51:16 +02:00
Actions User
869be0cb95 [CI] Updating repo.json for 0.7.2.0 2023-07-05 14:43:34 +00:00
Ottermandias
93e1b7acb9 Add Changelog. 2023-07-05 16:40:57 +02:00
Ottermandias
81dae22936 Fix some stupidly introduced ambiguities. 2023-07-05 16:18:49 +02:00
Ottermandias
823b195cb1 Update ONE new BNPC Name. 2023-07-05 16:14:53 +02:00
Ottermandias
00bc17c57a Move some stuff to shared things, improve some filesystem rename handling. 2023-07-05 16:13:11 +02:00
Ottermandias
8ea6893fc3 Fix some ToDos, parallelization problems. 2023-07-03 16:39:34 +02:00
Ottermandias
0ed94676ed Update BNPC names. 2023-06-23 01:10:45 +02:00
Ottermandias
0d343c3bab Fix crashes when drawing folders containing %. 2023-06-22 18:11:14 +02:00
Ottermandias
0690c0c53c Small Glamourer stuff. 2023-06-22 18:11:14 +02:00
Exter-N
f88b5761ba Remove null-terminator that was actually useless 2023-06-21 00:42:32 +02:00
Exter-N
22cb33e49e Fix -- texture prefixing in ResourceTree 2023-06-21 00:42:32 +02:00
Ottermandias
895e70555d Fix parameters of EnableDraw 2023-06-20 17:11:47 +02:00
Actions User
5805d5c798 [CI] Updating repo.json for testing_0.7.1.11 2023-06-18 21:51:31 +00:00
Ottermandias
fbd8a12f3a Add stuff that wasn't saved after rename for some reason... 2023-06-18 13:33:26 +02:00
Ottermandias
5f916efb13 Rename ChatService and move some support buttons to OtterGui. 2023-06-18 13:24:13 +02:00
Ottermandias
3f1d84343a Do not associate timeline resources with characters in cutscenes. 2023-06-17 01:25:05 +02:00
Ottermandias
306c2ffd10 Some glamourer related changes. 2023-06-17 01:25:05 +02:00
Ottermandias
208d8a11ff Move SaveService to OtterGui. 2023-06-17 01:25:05 +02:00
Ottermandias
d42a105687 Add some quick convert buttons to texture editing. 2023-06-17 01:25:05 +02:00
Ottermandias
8436455936 Make equipitems sharable again. 2023-06-17 01:25:05 +02:00
Actions User
323b4d6f21 [CI] Updating repo.json for testing_0.7.1.10 2023-06-13 14:03:57 +00:00
Ottermandias
b3a1a979eb Update BNPC Data. 2023-06-13 16:01:48 +02:00
Ottermandias
636f14a06d Change imc handling in caches slightly. 2023-06-13 16:01:38 +02:00
Ottermandias
03cb88be10 Add right-click to select only this current filter to changed items. 2023-06-12 16:17:36 +02:00
Actions User
3b68eca212 [CI] Updating repo.json for testing_0.7.1.9 2023-06-12 13:32:38 +00:00
Ottermandias
37798d93ba make testing tags testing_ instead of t. 2023-06-12 15:30:11 +02:00
Ottermandias
712dcf5782 Fix not being able to update option descriptions to empty. 2023-06-12 13:27:17 +02:00
Ottermandias
5a9f1385a2 Add All toggle for changed items. 2023-06-12 00:56:15 +02:00
Ottermandias
0999ab804a Fix ninja weapons. 2023-06-12 00:56:15 +02:00
Ottermandias
4f63e32df3 Move Dissolve Folder to the bottom. 2023-06-12 00:56:15 +02:00
Actions User
6b4e60e42e [CI] Updating repo.json for t0.7.1.8 2023-06-09 16:16:26 +00:00
Ottermandias
387b6da4d5 Fix changed item event. 2023-06-09 18:13:52 +02:00
Ottermandias
e72479c046 Make filter icons non-scaling. 2023-06-09 17:49:16 +02:00
Actions User
5aec508616 [CI] Updating repo.json for t0.7.1.7 2023-06-09 14:13:14 +00:00
Ottermandias
d9c5c053cf Use EquipItem in item management and add filters to changed item types. 2023-06-09 16:10:02 +02:00
Ottermandias
5fcb07487e Add SelectTab event, update new clientstructs. 2023-06-07 18:29:05 +02:00
Ottermandias
78e772dad9 Fix unknown animations not counting for changed items. 2023-06-05 18:25:07 +02:00
Actions User
878395e164 [CI] Updating repo.json for t0.7.1.6 2023-06-05 12:01:28 +00:00
Ottermandias
6ec60c9150 Improve changed items somewhat. 2023-06-05 13:58:44 +02:00
Ottermandias
b748e34917 Add ChangedItemDrawer and move it. 2023-06-05 01:31:51 +02:00
Ottermandias
b5f20c0ec8 Fix issue with collection count being wrong. 2023-06-05 01:31:20 +02:00
Ottermandias
52efacacd7 Fix issue with trimmed folder names being empty. 2023-06-05 01:31:08 +02:00
Ottermandias
d24e1576d3 Move EventWrapper, some Glamourer changes. 2023-06-04 15:41:02 +02:00
Ottermandias
c991eead89 More readonlys. 2023-06-03 15:50:20 +02:00
Ottermandias
0404ea6109 Resolve common/font paths from Interface Assignment 2023-06-03 15:10:48 +02:00
Ottermandias
9255f2bb2b Some readonlys. 2023-06-03 00:04:30 +02:00
Ottermandias
e02de6de5a Add key-check for file deletion in advanced editing. 2023-06-02 16:06:15 +02:00
Ottermandias
768016a897 Allow resetting text mod filter with right-click on arrow. 2023-06-02 15:59:02 +02:00
Actions User
3dc1553429 [CI] Updating repo.json for t0.7.1.5 2023-05-31 13:14:07 +00:00
Ottermandias
5320f43491 Make repo write out an array. 2023-05-31 15:11:13 +02:00
Ottermandias
77e76dd8a2 Remove now unneeded base-repo. 2023-05-31 14:45:44 +02:00
Actions User
071317e168 [CI] Updating repo.json for t0.7.1.5 2023-05-31 12:41:53 +00:00
Ottermandias
2bc8092cca Try revamping release actions. 2023-05-31 14:38:06 +02:00
Actions User
381ab4befe [CI] Updating repo.json for refs/tags/0.7.1.4 2023-05-30 21:43:18 +00:00
Ottermandias
81891cfe09 Add Sea of Stars as accepted repo. 2023-05-30 23:37:19 +02:00
Actions User
9fb5ac65d1 [CI] Updating repo.json for refs/tags/0.7.1.3 2023-05-29 16:51:14 +00:00
Ottermandias
02fe5a4fb3 Maybe fix the race condition and add more logging. 2023-05-29 18:48:53 +02:00
Actions User
f8d1fcf4e2 [CI] Updating repo.json for refs/tags/0.7.1.2 2023-05-27 14:23:42 +00:00
Ottermandias
312cb23615 Add Changelog. 2023-05-27 16:13:51 +02:00
Ottermandias
e28483d1ae Stop failure to load on broken configuration files. 2023-05-27 16:04:45 +02:00
Ottermandias
e98003eb09 Added some other task handling for collection caches. 2023-05-27 15:43:43 +02:00
Ottermandias
0243e7a633 Improve deduplicator and normalizer. 2023-05-27 15:43:43 +02:00
Ottermandias
f938531e21 Some small fixes/improvements. 2023-05-27 15:43:43 +02:00
Actions User
96aaefd3e2 [CI] Updating repo.json for refs/tags/0.7.1.1 2023-05-26 00:16:06 +00:00
Ottermandias
7244d63e1e Fix some bugs? 2023-05-26 02:11:14 +02:00
Ottermandias
9950604867 Fix LFinger not being valid for IMC files. 2023-05-26 02:11:14 +02:00
Ottermandias
edcfea5701 Allow giantess fetish rsp scaling values. 2023-05-26 02:11:14 +02:00
Actions User
575c1e2118 [CI] Updating repo.json for refs/tags/0.7.1.0 2023-05-24 23:17:40 +00:00
Ottermandias
f303b9e443 Add 0.7.1.0 Changelog. 2023-05-25 01:14:40 +02:00
Ottermandias
78aff2b9dc Fix some warnings. 2023-05-25 00:56:15 +02:00
Ottermandias
b51ced8cfb Fix Stain change. 2023-05-24 16:33:05 +02:00
Ottermandias
1a36b74557 Fix 6.4 sigs. 2023-05-24 15:24:28 +02:00
Ottermandias
e14fedf59e Add some Metadata validation. 2023-05-19 23:00:44 +02:00
Ottermandias
5567134a56 Fix inheritance save issues and sort mod settings on collection save. 2023-05-19 21:50:01 +02:00
Ottermandias
4298b46130 Remove soon unrestricted gear. 2023-05-19 21:49:29 +02:00
Ottermandias
0aa74692a8 Fix issue with reverted IMC edits and change counter on disabling mods. 2023-05-12 00:54:47 +02:00
Ottermandias
3f03712e24 Fix some issues with removing mods from collection caches. 2023-05-11 17:55:48 +02:00
Ottermandias
cbda4614a9 Make Merge Prettier 2023-05-10 21:17:04 +02:00
Ottermandias
c86d2eded5 Expand and name vfxWeaponHook to deal with more files. 2023-05-10 16:15:11 +02:00
Ottermandias
5d96f789fe Some more work on mod merging/splitting, still WIP. 2023-05-09 20:19:12 +02:00
Ottermandias
b50564f741 Use collection prefix for TMBs. 2023-05-09 20:18:38 +02:00
Ottermandias
654978dd64 Store last tab selected. 2023-05-09 20:04:23 +02:00
Ottermandias
f01b2f8754 Reorganize advanced editing tabs a bit. 2023-05-09 18:33:43 +02:00
Ottermandias
e4e74376fc Order right-click context collections by name. 2023-05-09 17:30:04 +02:00
Ottermandias
5ba43c1b19 Add some delayed saves and UI for that. 2023-05-09 17:15:19 +02:00
Ottermandias
e8eff51d84 Add hook for sounds loaded by weapons or something. Please cease this nonsense, modders! 2023-05-06 12:47:03 +02:00
Ottermandias
f9cc88cbb0 Add option to configure minimum window size. 2023-05-06 12:46:19 +02:00
Ottermandias
d403f44256 Add start of mod merger. 2023-05-05 16:19:19 +02:00
Ottermandias
8e5ed60c79 Add hook for dismount sounds..? 2023-05-05 16:19:19 +02:00
Ottermandias
6f6b72e7aa Move Creation of Caches to constructor thread. 2023-05-05 16:19:19 +02:00
Actions User
e5c4743374 [CI] Updating repo.json for refs/tags/0.7.0.10 2023-05-04 10:21:37 +00:00
Ottermandias
4d9c5bdb8d Add some more flags to the popup window. 2023-05-04 10:00:48 +02:00
Ottermandias
beb777e3cd Add some more debugging fuckery for the import popup fuckery. 2023-05-03 16:56:45 +02:00
Actions User
6316458613 [CI] Updating repo.json for refs/tags/0.7.0.9 2023-05-02 19:15:34 +00:00
Ottermandias
4b1443ec93 Fuck 2023-05-02 21:13:09 +02:00
Actions User
c911977b5e [CI] Updating repo.json for refs/tags/0.7.0.8 2023-05-02 18:52:24 +00:00
Ottermandias
4cd03f2198 Work around some file picker stuff for textures. 2023-05-02 20:50:10 +02:00
Ottermandias
58bd223a80 Make cache calculation thread safe(r) 2023-05-02 18:02:32 +02:00
Ottermandias
fb84b43d69 Use explicit priorities for all internal communication events. 2023-05-02 17:46:13 +02:00
Actions User
f46daf0f54 [CI] Updating repo.json for refs/tags/0.7.0.7 2023-05-02 14:36:08 +00:00
Ottermandias
314a1e0e8c Fix file selector not opening at right location. 2023-05-02 16:31:47 +02:00
Ottermandias
9c0406ec9d Fix some caching issues. 2023-05-02 16:31:47 +02:00
Actions User
94a0864556 [CI] Updating repo.json for refs/tags/0.7.0.6 2023-05-01 20:55:50 +00:00
Ottermandias
ee50994b39 Maybe sort race condition. 2023-05-01 22:50:51 +02:00
Actions User
a38a989fe7 [CI] Updating repo.json for refs/tags/0.7.0.5 2023-05-01 16:51:25 +00:00
Ottermandias
2167ddf9d9 Maybe fix window issues? Dunno wtf is going on. 2023-05-01 18:47:52 +02:00
Ottermandias
c2fb18ab53 Change CollectionCache handling. 2023-05-01 18:43:59 +02:00
Ottermandias
7ab5c7311c Fix multiple context menus for collections with identical identifier names. 2023-05-01 18:43:59 +02:00
Ottermandias
101a1b7392 Fix non-populating models for Update Bibo. 2023-05-01 18:43:59 +02:00
Actions User
f9b1e85c8f [CI] Updating repo.json for refs/tags/0.7.0.4 2023-04-30 11:00:51 +00:00
Ottermandias
340a35918c Add Changelog. 2023-04-30 12:57:59 +02:00
Ottermandias
1ccf3a4256 Improve delayed individual collection loading. 2023-04-30 12:36:48 +02:00
Ottermandias
23e553c88e Add options to BulkTag to not only check local tags. 2023-04-30 11:09:22 +02:00
Ottermandias
d9dc37c994 Disable Meta Edits when mods are disabled. 2023-04-30 10:48:07 +02:00
Ottermandias
361244385f Merge branch 'master' of github.com:xivDev/Penumbra 2023-04-30 10:46:22 +02:00
Ottermandias
60754012c2 Make Material Reassignment work again. 2023-04-30 10:46:05 +02:00
Actions User
ca0caebe84 [CI] Updating repo.json for refs/tags/0.7.0.3 2023-04-29 17:30:18 +00:00
Ottermandias
8d37c5ff06 Only Trim End on RelPath. 2023-04-29 19:18:11 +02:00
Ottermandias
127bbcb485 Prevent integer overflowing on 8k x 8k textures. 2023-04-29 19:12:26 +02:00
Actions User
42ef951b82 [CI] Updating repo.json for refs/tags/0.7.0.2 2023-04-29 16:37:45 +00:00
Ottermandias
d8597009a8 Trim created options. 2023-04-29 18:35:13 +02:00
Ottermandias
b1ab7e1cd0 Add mod state debugging. 2023-04-29 18:35:13 +02:00
Ottermandias
777c0cc69e Fix invalid state after mod deletion. 2023-04-29 18:35:13 +02:00
Ottermandias
46bf3d7391 Notification on invalid collection names. 2023-04-29 18:35:13 +02:00
Actions User
183b59305a [CI] Updating repo.json for refs/tags/0.7.0.1 2023-04-29 14:10:43 +00:00
Ottermandias
ef5cf14b2b Add some tracking of cached collections. 2023-04-29 16:08:16 +02:00
Ottermandias
a9ff6135b3 Add changelog. 2023-04-29 15:51:09 +02:00
Ottermandias
89b5877443 Allow 64 characters for collection names instead of 32. 2023-04-29 15:50:08 +02:00
Ottermandias
3d5765796e Hopefully fix issue with missing caches. 2023-04-29 15:49:33 +02:00
Ottermandias
c2933ba95c Merge branch 'services' 2023-04-29 15:16:16 +02:00
Ottermandias
73b4787a55 Add re-ordering of individual collections back. 2023-04-29 15:15:55 +02:00
Ottermandias
648286a923 Fix wrong Collection Unused Banner for individual assignments. 2023-04-29 15:04:30 +02:00
Ottermandias
cd94c73d93 Redundancy is stupid. 2023-04-29 14:49:06 +02:00
Ottermandias
d831b61c02 Fix redundancy check for Your Character. 2023-04-29 14:21:08 +02:00
Actions User
b8ad456ed3 [CI] Updating repo.json for refs/tags/0.7.0.0 2023-04-29 11:21:37 +00:00
Ottermandias
c2fe0d6ed1 Add Changelog. 2023-04-29 13:19:06 +02:00
Ottermandias
d649a3b1a7 Increment submodules and API. 2023-04-29 13:17:41 +02:00
Ottermandias
290912e7cd Add collection select headers to Changed Items and Effective Changes. 2023-04-29 13:12:58 +02:00
Ottermandias
2402d0aa6f Fix bug with deleting mods 2023-04-29 12:26:07 +02:00
Ottermandias
31338e43d6 Improve mod editor a bit 2023-04-29 12:25:43 +02:00
Ottermandias
7d1d6ac829 Some Glamourer stuff 2023-04-29 12:25:24 +02:00
Ottermandias
a293e7dfea Add Trim in String Constructors for byte strings. 2023-04-27 11:59:26 +02:00
Ottermandias
5ba455fe71 More stupid. 2023-04-22 22:01:39 +02:00
Ottermandias
3bd6b0ccea Stupid. 2023-04-22 21:58:51 +02:00
Ottermandias
fd3a066aee Some more touches. 2023-04-22 21:55:31 +02:00
Ottermandias
ce03fb59c8 Some more shuffling around. 2023-04-22 15:16:33 +02:00
Ottermandias
a94c5ae7af Some more reworking. 2023-04-22 13:30:14 +02:00
Ottermandias
e66d666d4d Get rid of last statics. 2023-04-22 00:35:48 +02:00
Ottermandias
826777b7ee Remove static Dalamud Services. 2023-04-22 00:28:05 +02:00
Ottermandias
c49454fc25 Move MetaList out of CharacterUtility and remove static CollectionManager. 2023-04-22 00:11:47 +02:00
Ottermandias
2c55701cbf Remove static ActorService. 2023-04-21 23:56:20 +02:00
Ottermandias
be3c1c85aa Remove static Config. 2023-04-21 23:52:31 +02:00
Ottermandias
aa4bc45641 Remove Mod.BasePath 2023-04-21 23:17:05 +02:00
Ottermandias
49c8afb72a Remove remaining static ModManager. 2023-04-21 23:12:26 +02:00
Ottermandias
10c0117402 Why does this not work, stupid Conditional. 2023-04-21 18:47:34 +02:00
Ottermandias
9c4f7b7562 Finish CollectionTab rework. 2023-04-21 18:42:54 +02:00
Ottermandias
25cb46525a Fix bug with shpk editing. 2023-04-20 09:35:23 +02:00
Ottermandias
1364b39f65 Bugfix for service creation stalling itself. 2023-04-19 11:00:53 +02:00
Ottermandias
e3c333dd22 Use filtered combos in file selectors. 2023-04-18 20:45:57 +02:00
Ottermandias
49ba771b26 Add unused debug hook for finding functions faster. 2023-04-18 18:45:21 +02:00
Ottermandias
fba5bc6820 Fix some bugs and start work on new collections tab. 2023-04-18 18:44:53 +02:00
Ottermandias
e9fc57022e Add new Mod Collections tab. 2023-04-18 18:42:33 +02:00
Ottermandias
835020229c Add hook for parasol animation loading. 2023-04-18 15:22:35 +02:00
Ottermandias
4972dd1c9f Untangling the mods. 2023-04-17 09:35:54 +02:00
Ottermandias
1d82e882ed Meta stuff is terrible. 2023-04-16 13:18:43 +02:00
Ottermandias
0186f176d0 meta tmp 2023-04-15 20:40:00 +02:00
Ottermandias
9037166d92 Add some logging, fix som bugs 2023-04-15 20:38:02 +02:00
Ottermandias
85fb98b557 tmp 2023-04-14 22:26:30 +02:00
Ottermandias
0108e51636 Some renaming 2023-04-14 16:22:06 +02:00
Ottermandias
828cd07df0 Fix an imgui clipping issue. 2023-04-13 16:00:49 +02:00
Ottermandias
e86899c943 tmp 2023-04-13 16:00:44 +02:00
Ottermandias
2bf80dfa6b Only search through non-hidden files in a few places. 2023-04-12 16:26:00 +02:00
Ottermandias
a88332d3bc Merge remote-tracking branch 'origin/master' into services 2023-04-12 16:11:15 +02:00
Jacob Keller
8c28f0c6e3 Ignore hidden files when generating the unused files list.
Don't include hidden files in the unused file list for a mod. These files are typically things such as .git or otherwise intended to be hidden and shouldn't be included as part of the list of files which could be used.
2023-04-12 11:01:01 +02:00
Ottermandias
3f33bab296 Lots of collection progress. 2023-04-11 11:28:56 +02:00
Caraxi
51ce6d1038 Fix double by in mod name display 2023-04-10 23:53:48 +02:00
Ottermandias
d908f22a17 Maybe prevent weird GetName crashes. 2023-04-10 15:06:16 +02:00
Ottermandias
c527d19117 Remove some static dependencies. 2023-04-10 00:31:29 +02:00
Ottermandias
4294b18bcb Fix issue with tutorial window and double necessary click 2023-04-09 02:42:27 +02:00
Ottermandias
bbfc9a0a6f Rework around a saner import popup and decouple logic from interface. 2023-04-08 22:29:43 +02:00
Ottermandias
bfb630d317 HTTP Api formatting. 2023-04-08 20:53:11 +02:00
Ottermandias
31c9b22b9b Add UnpackMod to API 2023-04-08 20:50:30 +02:00
Sebastina
19efd766fc Update ModFileSystemSelector.cs re-add forgotten reference during rebase. 2023-04-08 20:50:02 +02:00
Sebastina
2bfd5d138f Update PenumbraApi.cs Add method for unpacking mod. 2023-04-07 09:42:01 -05:00
Sebastina
3f4cd67dae Add ExternalModImporter.cs allows access to ModFileSystemSelector actions to HTTP API 2023-04-07 09:40:52 -05:00
Sebastina
eddbd2b14f Update HttpApi.cs allows external applications to tell penumbra about a mod package to unpack 2023-04-07 09:40:14 -05:00
Sebastina
69ce929c5e Update ModFileSystemSelector.cs add functions to allow penumbra to respond to requests to unpack mods. 2023-04-07 09:39:19 -05:00
Ottermandias
0ed1a81c29 Some stuff. 2023-04-07 00:29:19 +02:00
Ottermandias
f85fc46fb7 Now that's a collection manager. 2023-04-06 15:47:33 +02:00
Ottermandias
5a817db069 Make Individual Collection lookup thread-safe by locking. 2023-04-03 12:14:43 +02:00
Ottermandias
e9ab9a71a8 Fix Refresh Data and Reload Files. 2023-04-01 23:01:56 +02:00
Ottermandias
577669b21f Some mod movement. 2023-04-01 14:24:12 +02:00
Ottermandias
c12dbf3f8a Fix early loaded weapons blocking the identified collections cache with unprepared customize arrays. 2023-04-01 14:23:50 +02:00
Ottermandias
d4738934f8 Fix Resource Watcher crash 2023-04-01 14:22:17 +02:00
Ottermandias
113078af90 Namespace movement. 2023-03-31 20:44:17 +02:00
Ottermandias
a1e9c44697 Test different action. 2023-03-31 18:50:40 +02:00
Ottermandias
49f1f7020f Fix some bugs. 2023-03-31 18:35:45 +02:00
Ottermandias
a2fd070c86 Fix regex issue 2023-03-31 02:06:39 +02:00
Ottermandias
e79b110429 Remove cached data from mod and use ModCaches where required. 2023-03-31 01:27:47 +02:00
Ottermandias
2ffbd7beba Fix Item Swaps not updating when mod is changed. 2023-03-31 01:27:27 +02:00
Ottermandias
afa11f85e2 Use ModManager2 2023-03-30 23:51:13 +02:00
Ottermandias
70c1a2604f ModManager2 2023-03-30 23:29:01 +02:00
Ottermandias
1541cdb78d Add Mount animation hook. 2023-03-29 14:45:03 +02:00
Ottermandias
3f86698615 Make OnScreen a task. 2023-03-29 14:45:03 +02:00
Ottermandias
185be81e73 Fix some issues with ResourceWatcher. 2023-03-29 14:45:03 +02:00
Ottermandias
a8000fbf14 Add ExportManager. 2023-03-28 16:58:20 +02:00
Ottermandias
2b7292adb8 Fix issue with import state popup 2023-03-28 16:57:42 +02:00
Ottermandias
c31a2f5a42 Remove SaveDefaultMod. 2023-03-27 18:09:41 +02:00
Ottermandias
fbe2ed1a71 Bunch of work on Option Editor. 2023-03-27 17:09:19 +02:00
Ottermandias
1253079968 Move Mod.Manager and ModCollection.Manager to outer scope and required changes. 2023-03-27 15:22:39 +02:00
Ottermandias
ccdafcf85d More Stuff. 2023-03-26 18:02:32 +02:00
Ottermandias
ef9022a746 Stuff. 2023-03-26 12:37:22 +02:00
Ottermandias
e33f49e097 Merge branch 'master' into services
# Conflicts:
#	Penumbra/CommandHandler.cs
#	Penumbra/Penumbra.cs
2023-03-25 16:58:33 +01:00
Actions User
182546ee10 [CI] Updating repo.json for refs/tags/0.6.6.5 2023-03-25 15:58:18 +00:00
Ottermandias
c958935f40 Run command registration on framework. 2023-03-25 16:55:47 +01:00
Ottermandias
355206e0cf Cleanup 2023-03-25 16:12:14 +01:00
Ottermandias
d58a3e0fe7 Fix bug with default mod settings other than 0. 2023-03-25 12:53:47 +01:00
Actions User
348da38879 [CI] Updating repo.json for refs/tags/0.6.6.4 2023-03-25 11:36:14 +00:00
Ottermandias
fb2fe05409 Merge API changes. 2023-03-25 12:34:47 +01:00
Ottermandias
d32e777426 Merge tag '0.6.6.4' into services
# Conflicts:
#	Penumbra/Api/PenumbraApi.cs
#	Penumbra/Api/TempModManager.cs
2023-03-25 12:31:19 +01:00
Ottermandias
831990949f Add correct handling of forceAssignment and correct iteration while deleting. 2023-03-25 12:21:55 +01:00
Ottermandias
9ee8ab73ec Fix dissolving not working. 2023-03-25 11:46:21 +01:00
Ottermandias
45b26030cc Fix button sizing for collapsible groups, fix default tab to be settings, fix bug with item spacing style. 2023-03-24 18:33:22 +01:00
Ottermandias
5cad575c2e Fix FileSystemSelector bug and add notifications on failures. 2023-03-24 18:32:27 +01:00
Ottermandias
c8415e3079 Start ModManager dissemination.... 2023-03-24 00:28:36 +01:00
Ottermandias
174e640c45 Move TexTools around. 2023-03-23 21:50:28 +01:00
Ottermandias
f38a252295 More renaming... 2023-03-23 20:42:34 +01:00
Ottermandias
7bad131542 Start restructuring CharacterUtility 2023-03-23 20:35:33 +01:00
Ottermandias
56286e0123 Rename interop folders 2023-03-23 20:30:23 +01:00
Ottermandias
49f1e2dcde Hopefully merge the rest of the changes correctly. 2023-03-23 18:54:16 +01:00
Ottermandias
e6b17d536b Cleanup and fit ResourceTree to new paradigm. 2023-03-23 17:51:02 +01:00
Ottermandias
d28299f699 Merge branch 'pr/n292_feature/on-screen' into services
# Conflicts:
#	Penumbra/UI/Classes/ModEditWindow.FileEditor.cs
#	Penumbra/UI/Classes/ModEditWindow.Files.cs
#	Penumbra/UI/Classes/ModEditWindow.cs
#	Penumbra/UI/ConfigWindow.cs
2023-03-23 17:50:00 +01:00
Exter-N
046ef4d72d Deduplicate UI code 2023-03-23 17:01:09 +01:00
Ottermandias
7a6384bd22 Path Resolver unfiddled and somewhat optimized. 2023-03-23 16:39:29 +01:00
Exter-N
14eddac6f7 Better handling of sub-objects, better headers 2023-03-23 13:47:36 +01:00
Exter-N
045c84512f On-Screen resource tree & quick import 2023-03-23 02:52:51 +01:00
Ottermandias
b6d6993c9f tmp 2023-03-21 17:25:18 +01:00
Ottermandias
c5ac9f6f08 Derp 2023-03-21 16:44:07 +01:00
Ottermandias
21181370e7 Add rudimentary quick move support to folders. 2023-03-21 15:58:08 +01:00
Ottermandias
b92a3161b5 Why is this so much work? 2023-03-20 17:30:09 +01:00
Ottermandias
651c7410ac Wow, I accidentally the whole UI 2023-03-18 21:39:59 +01:00
Ottermandias
dd8c910597 Everything's a service. 2023-03-17 17:51:05 +01:00
Ottermandias
2670ba52c1 D 2023-03-17 14:05:05 +01:00
Ottermandias
3fc724b7ee Some further stuff. 2023-03-17 13:19:23 +01:00
Ottermandias
0df12a34cb Rework Interop/Loader Services. 2023-03-16 15:15:42 +01:00
Ottermandias
99fd4b7806 tmp 2023-03-13 10:05:48 +01:00
Ottermandias
bdaff7b781 This is going rather well. 2023-03-13 10:05:44 +01:00
Ottermandias
73e2793da6 tmp 2023-03-13 10:04:48 +01:00
Ottermandias
3c564add0e Fix missing stuff from defines. 2023-03-12 02:42:07 +01:00
Ottermandias
1b7360f8be Allow item swapping between from accessories and hats to other accessory types. 2023-03-12 02:27:55 +01:00
Ottermandias
19dde3cbc4 Add file name to exception. 2023-03-12 02:27:55 +01:00
Ottermandias
d8e2a5ba28 Move UI Building to thread. 2023-03-12 02:27:55 +01:00
Ottermandias
23c1ee9dc6 Move Validating outside of main class. 2023-03-12 02:27:55 +01:00
Ottermandias
8ce52b7028 Use OriginalDisposeSafe. 2023-03-12 02:27:55 +01:00
Actions User
f03584a057 [CI] Updating repo.json for refs/tags/0.6.6.3 2023-03-09 14:41:48 +00:00
Ottermandias
cd894e415d Switch CreateFileW hook to hooking from import table. 2023-03-09 15:38:27 +01:00
Ottermandias
5c6c96b6c0 Improve startup performance tracking 2023-03-09 15:38:27 +01:00
Actions User
9c6bcb2409 [CI] Updating repo.json for refs/tags/0.6.6.2 2023-03-08 12:28:29 +00:00
Ottermandias
8d38f73f52 Use TabBar, add OpenMainWindow and CloseMainWindow to API 2023-03-08 12:49:06 +01:00
Ottermandias
6a54d24634 Maybe fix UTF8 issues. 2023-03-08 12:49:06 +01:00
Actions User
4a8f0aac61 [CI] Updating repo.json for refs/tags/0.6.6.1 2023-03-05 14:22:23 +00:00
Ottermandias
6159f1e998 Add changelog. 2023-03-05 15:19:50 +01:00
Ottermandias
c2bb1407a9 Add option to make successful chat commands silent. 2023-03-05 15:19:50 +01:00
Ottermandias
6ee6e4a4ba Fix assign current player and assign current target buttons not triggering events. 2023-03-05 15:19:50 +01:00
Ottermandias
45075d5b27 Fix migration of old mods not working anymore 2023-03-05 15:19:50 +01:00
Ottermandias
64c8f29c47 Fix issue with assigning indexed npcs. 2023-03-05 15:19:50 +01:00
Actions User
009499cdf6 [CI] Updating repo.json for refs/tags/0.6.6.0 2023-03-04 18:57:48 +00:00
Ottermandias
3c78d6b695 Add Changelog. 2023-03-04 19:55:02 +01:00
Ottermandias
21be245c5c Add collapsing to big option groups. 2023-03-03 18:43:37 +01:00
Ottermandias
522fc832db Add handling of too large multi groups on import of pmp or adding mods via IPC. 2023-03-03 18:06:30 +01:00
Ottermandias
cdc4ee6991 Change phyb and sklb resolving for item swap. 2023-03-03 13:43:20 +01:00
Ottermandias
1f942491ac Move mod creation functions to own subclass. 2023-03-03 13:43:00 +01:00
Ottermandias
c2ac745d72 Fix an issue with retainer assignments using ownership wrongly for mannequins. 2023-03-01 23:11:05 +01:00
Ottermandias
e62b0155d4 Redesign Shpk tab. 2023-03-01 18:30:27 +01:00
Ottermandias
7ae6d0a348 Add collection groups for Children and Elderly. 2023-02-28 15:48:45 +01:00
Ottermandias
2e6cc73666 Rework Samplers. 2023-02-25 18:11:21 +01:00
Ottermandias
d175802bec Rework Material Constants 2023-02-25 15:10:38 +01:00
Ottermandias
397362caa5 More restructuring. 2023-02-24 18:45:04 +01:00
Ottermandias
c4a4aec221 Some more material shpk restructuring 2023-02-24 16:12:41 +01:00
Ottermandias
7619503a2b Split huge material shpk gui function. 2023-02-24 15:34:20 +01:00
Ottermandias
d4f1097eba Fix issue when extracting some textures. 2023-02-24 00:09:42 +01:00
Ottermandias
9cf69def7b Fix mistaken rename. 2023-02-23 20:44:06 +01:00
Ottermandias
b31c5fdd1f Fix accidentally reverted change. 2023-02-23 18:33:12 +01:00
Ottermandias
47ddca0506 Add option to display single select groups as radio buttons. 2023-02-23 18:29:54 +01:00
Ottermandias
ebbc3fed86 Some material shpk refactoring. 2023-02-23 17:47:59 +01:00
Ottermandias
7e56858bc6 Change handling of associated shpk for material. 2023-02-23 15:20:51 +01:00
Ottermandias
a2b62a8b6a Some formatting and naming changes, splitting files and some minor improvements. 2023-02-23 14:49:41 +01:00
Ottermandias
1e471551d4 Move IndexSet to OtterGui 2023-02-23 14:44:37 +01:00
Exter-N
e058b6e32b Fix wrong error message 2023-02-21 16:54:24 +01:00
Exter-N
86de28245d Auto-generate ID from a name through CRC when adding a param 2023-02-21 16:54:24 +01:00
Exter-N
33231959b2 More Mtrl and ShPk editing (thanks @aers) 2023-02-21 16:54:24 +01:00
Exter-N
0c17892f03 Mtrl shader resource editing, ShPk editing 2023-02-21 16:54:24 +01:00
Ottermandias
7ee80c7d48 Cleanup HTTP API, remove unused options. 2023-02-21 16:53:53 +01:00
Sebastian Lawe
0a47ae5b18 Update Penumbra.cs add ReloadController.cs to HTTP API 2023-02-21 15:45:59 +01:00
Sebastian Lawe
30fba90e9f Add ReloadController.cs allows the discovery and reloading of a specified mod via HTTP API 2023-02-21 15:45:59 +01:00
Ottermandias
738d62757c Fix ascii check in RelPath. 2023-02-18 00:14:09 +01:00
Ottermandias
579a9edca1 Fix culture. 2023-02-16 17:57:33 +01:00
Ottermandias
9098b5b3b3 Revamp resource logging. 2023-02-16 16:53:31 +01:00
Ottermandias
08519396a0 Allow Penumbra to use long and arbitrary UTF8 paths. 2023-02-14 16:39:40 +01:00
Actions User
68a787d125 [CI] Updating repo.json for refs/tags/0.6.5.2 2023-02-09 20:12:21 +00:00
Ottermandias
c3a71ab95e Update BannerInterfaceStorage for 6.31h 2023-02-09 20:54:41 +01:00
Ottermandias
f16c6363ab
Merge pull request #280 from Exter-N/fix-mipmaps-on-wine
Fix mipmap generation on Wine (Mac/Linux)
2023-02-07 21:30:54 +01:00
Exter-N
bdef7a5118 Fix mipmap generation on Wine (Mac/Linux) 2023-02-07 21:22:15 +01:00
Ottermandias
bb805345b1 Minimal cleanup on option descriptions. 2023-02-04 20:23:52 +01:00
Ottermandias
3dc04293eb Merge branch 'master' of github.com:xivDev/Penumbra 2023-02-04 19:16:18 +01:00
Ottermandias
92ddc250d0
Merge pull request #279 from Caraxi/OptionDescriptions
Add descriptions for SubMods
2023-02-04 19:16:22 +01:00
Ottermandias
41b88b036e Misc. 2023-02-04 19:15:48 +01:00
Ottermandias
b26923e504 Derp. 2023-02-04 15:40:35 +01:00
Ottermandias
ea66bd2e67 Allow Profiling in Release for now. 2023-02-04 15:38:57 +01:00
Ottermandias
f29bdee010 Try to improve launch times somewhat. 2023-02-04 14:58:07 +01:00
Ottermandias
98bc14882b Add some startup information in debug mode. 2023-02-04 14:57:34 +01:00
Caraxi
9f6a45041d Add descriptions for SubMods
Adjusted importing of TexTools mods to give each option its own description instead of combining all option descriptions into the group description.
2023-02-04 17:26:13 +10:30
Ottermandias
e34aca68aa Fix crash on mannequins with resource logging on, fix problem with temp collections not keeping count. 2023-02-03 21:23:45 +01:00
Actions User
60f54fa047 [CI] Updating repo.json for refs/tags/0.6.5.0 2023-02-02 11:08:35 +00:00
Ottermandias
41ddc451de Add changelog. 2023-02-02 12:04:11 +01:00
Ottermandias
c2b3e4dbaf Apply OtterGui changes. 2023-02-02 12:00:03 +01:00
Ottermandias
5997ddca02 Add even more handling for stupid banners and some debug info for them. 2023-02-02 11:35:59 +01:00
Ottermandias
fe561f39c2 Add ResolvePlayerPaths. 2023-01-31 16:12:57 +01:00
Ottermandias
58c74e839c Maybe improve error handling when unable to create mipmaps. 2023-01-30 22:22:39 +01:00
Ottermandias
960c936f8d Fix ItemSwap with changed target IMC material variant. 2023-01-30 22:17:16 +01:00
Actions User
20303a9416 [CI] Updating repo.json for refs/tags/0.6.4.0 2023-01-30 17:08:06 +00:00
Ottermandias
d5efe3f748 Add changelog. 2023-01-30 18:04:48 +01:00
Ottermandias
2ef9d3d56e Some Glamourer stuff. 2023-01-29 21:08:43 +01:00
Ottermandias
21e6a17d1c Some cleanup. 2023-01-25 16:45:23 +01:00
Ottermandias
e716bbbc01 Remove doubled skip from collection listing. 2023-01-25 09:57:36 +01:00
Ottermandias
0239c2f60b Glamourer changes 2023-01-25 09:57:24 +01:00
Ottermandias
5f63d4de38 Change sounds to be able to be resolved. 2023-01-23 16:26:54 +01:00
Ottermandias
e6d73971e9 Change resolving to consider every resource category to fix music resolving. 2023-01-22 16:39:15 +01:00
Ottermandias
a9a5f91c90 Maybe fix animation handling after redraws (esp. for PLD with shield), maybe break everything else. 2023-01-22 16:09:46 +01:00
Ottermandias
853fe8644c Add number of subfiles to debug tab. 2023-01-22 15:17:08 +01:00
Ottermandias
24fda725a2 Add GameEventManager, change cutscene character and subfile container resets. 2023-01-22 15:16:53 +01:00
Ottermandias
7ab1426a2c Change ResourceHandle strings a bit. 2023-01-22 15:16:19 +01:00
Ottermandias
471005b5b1 Fix group banner identification. 2023-01-22 15:15:49 +01:00
Ottermandias
deb630795d Move Frameworkmanager to OtterGui. 2023-01-22 12:56:42 +01:00
Actions User
513df2beac [CI] Updating repo.json for refs/tags/0.6.3.1 2023-01-20 09:42:37 +00:00
Ottermandias
a11e1d464b Test higher dotnet version. 2023-01-19 18:03:37 +01:00
Ottermandias
832b1163e0 More resourcehandler fixes. 2023-01-19 17:56:26 +01:00
Ottermandias
123dd3aacc Update to new resourcehandler handling. 2023-01-19 17:40:30 +01:00
Ottermandias
93840e30f0 Update gamepaths stuff. 2023-01-19 17:40:30 +01:00
Ottermandias
fe0e01b8fe Use compiled regex. 2023-01-19 17:40:30 +01:00
Actions User
b0370139ec [CI] Updating repo.json for refs/tags/0.6.3.0 2023-01-17 15:49:07 +00:00
Ottermandias
f9b8717582 Add changelog. 2023-01-17 16:46:17 +01:00
Ottermandias
bff99da585 Reformat buttons. 2023-01-17 16:46:09 +01:00
Ottermandias
b64a9a51f8 Add Target button to collection assignments. 2023-01-17 16:41:25 +01:00
Ottermandias
6b558c5940 Try to identify actors in banners correctly. 2023-01-17 15:04:27 +01:00
Ottermandias
23919d8083 Ensure permanent identifiers. 2023-01-17 13:19:54 +01:00
Ottermandias
7bb5a1ebe3 Update BNPCs 2023-01-17 13:16:53 +01:00
Ottermandias
e55ff791fe Fix pvp actor maybe. 2023-01-16 17:54:40 +01:00
Ottermandias
80f02e5377 Add API/IPC for collection handling. 2023-01-16 17:42:26 +01:00
Ottermandias
123ed256b1 Try to resolve banner players better. 2023-01-16 13:03:26 +01:00
Ottermandias
4059e0630a Fix companion identification, extract offsets and vtable indices to separate file. 2023-01-16 13:02:22 +01:00
Ottermandias
79eee0e2c7 Rename SpecialActor -> ScreenActor, add new ScreenActors. 2023-01-14 20:00:48 +01:00
Ottermandias
27fed7860d Make SubFiles threadlocal. 2023-01-14 19:59:13 +01:00
Ottermandias
ff2b9de93e Fix deleting active collections. 2023-01-14 19:58:55 +01:00
Ottermandias
efdece613a Add names, maybe fix combos. 2023-01-14 19:58:39 +01:00
Actions User
79b4415a44 [CI] Updating repo.json for refs/tags/0.6.2.0 2023-01-11 14:15:54 +00:00
Ottermandias
c7cb771992 Add changelog. 2023-01-11 15:12:39 +01:00
Ottermandias
9555b4eecb Fix RenderModel offsets. 2023-01-11 13:20:36 +01:00
Ottermandias
e00cb6cc6a Fix renderflags offset for redrawing. 2023-01-11 12:38:59 +01:00
Ottermandias
9ccbe10642 Fix signature and CS changes. 2023-01-10 09:42:59 +01:00
Ottermandias
889fc101a8 Add collection logging to resolve logging. 2023-01-10 08:52:36 +01:00
Ottermandias
58f86743eb API 8. 2023-01-09 14:03:22 +01:00
Ottermandias
aa15ff40e1 .net7 2023-01-09 14:01:37 +01:00
Ottermandias
a061ab9b8b Extract all signatures to a single file. 2023-01-09 13:59:24 +01:00
Ottermandias
40b7266c22 Add handling for left and right ring. 2023-01-08 13:35:59 +01:00
Ottermandias
ea2a411a2e Improve File Swap hint texts, also reorders them. 2023-01-08 13:35:59 +01:00
Ottermandias
f27d49f5d6 Try to handle Mahjong actors. 2023-01-08 13:35:59 +01:00
Ottermandias
b7408f15fb Fix some screen actors not respecting settings. 2023-01-08 13:35:59 +01:00
Ottermandias
d23eab0530 Fix collection caches not resetting correctly. 2023-01-08 13:35:59 +01:00
Ottermandias
a7d7f1523f
Merge pull request #272 from SoyaX/patch
Update bulktag labels
2023-01-08 10:56:11 +01:00
SoyaX
7b318c9ce4 Update bulktag labels 2023-01-08 15:21:16 +10:30
Ottermandias
28ab12c21b Add option to not use any mods when inspecting players. 2023-01-06 16:03:45 +01:00
Ottermandias
72408bf45c Add a hook for updating looped scds. 2023-01-06 14:34:28 +01:00
Ottermandias
baf3b06060 Remove gender-unlocked clothes 2023-01-06 14:33:46 +01:00
Ottermandias
6e983c8735 Add placeholder options to collection chat command, slightly refactor tag -> bulktag command by SoyaX. 2023-01-05 16:52:01 +01:00
Ottermandias
b43d0453e1 Merge branch 'master' of github.com:xivDev/Penumbra 2023-01-05 15:57:28 +01:00
Ottermandias
c684db3000 Small security check. 2023-01-05 15:57:18 +01:00
Ottermandias
ef962393c4
Merge pull request #271 from SoyaX/tagCommand
Add tag command to CommandHandler
2023-01-05 10:45:05 +01:00
SoyaX
2b8862e4a5 Add tag command to CommandHandler 2023-01-05 20:03:50 +10:30
Ottermandias
2f7b6e3d55 Add performance monitor in debug compilations. 2023-01-04 14:44:33 +01:00
Ottermandias
f2997102c7 Timing test. 2023-01-03 21:59:42 +01:00
Ottermandias
6bc0b77ad3 Formatting. 2023-01-03 21:56:09 +01:00
Actions User
b3b552235c [CI] Updating repo.json for refs/tags/0.6.1.3 2023-01-03 16:38:57 +00:00
Ottermandias
0158ff2074 Fix typo in color explanations. 2023-01-03 17:36:02 +01:00
Ottermandias
0b7b63a3a9 Do not force avfx files to load synchronously. 2023-01-03 17:35:50 +01:00
Ottermandias
2dda954806 Fix association of vfx game objects for ID-less objects. 2023-01-03 17:35:34 +01:00
Ottermandias
8df4bb0781 Add some Additional Information to Mdl display, discard some padding when reading mdl files. 2023-01-03 12:52:21 +01:00
Actions User
36c77034a4 [CI] Updating repo.json for refs/tags/0.6.1.2 2023-01-02 16:24:38 +00:00
Ottermandias
20e6baee0b Fix a problem when incorporating deduplicated meta files simultaneously over multiple options. 2023-01-02 17:21:03 +01:00
Actions User
1268344d04 [CI] Updating repo.json for refs/tags/0.6.1.1 2023-01-01 23:22:55 +00:00
Ottermandias
047f14a288 Add changelog. 2023-01-02 00:20:39 +01:00
Ottermandias
fbb8f48e49 Add toggle to use entire current collection for item swap. 2023-01-02 00:15:34 +01:00
Ottermandias
bc3a55eded Fix issues with accessories and with gender-locked gear. 2023-01-01 23:32:54 +01:00
Ottermandias
070d73a5a1 Fix affected item warning appearing on single item. 2023-01-01 23:30:22 +01:00
Ottermandias
45ec212b78 Change Item Swaps to use exceptions for actual error messages. 2023-01-01 22:06:01 +01:00
Ottermandias
29d01e698b Fix swapping universal hairstyles for midlanders breaking them for others. 2023-01-01 13:55:30 +01:00
Actions User
6493394256 [CI] Updating repo.json for refs/tags/0.6.1.0 2022-12-31 23:01:00 +00:00
Ottermandias
c590b7fb24 Add Changelog. 2022-12-31 22:35:49 +01:00
Ottermandias
5dd4701c4c Improve AVFX writing. 2022-12-31 21:56:39 +01:00
Ottermandias
ab53f17a7e Add equipment swaps and writing to option. 2022-12-31 21:56:25 +01:00
Ottermandias
33b4905ae2 Add some checks for valid variants in IMC Meta Edits. 2022-12-31 12:26:05 +01:00
Ottermandias
a01f73cde4 Add equipment swapping. 2022-12-31 12:19:03 +01:00
Ottermandias
6cd43aa304 Add AVFX parsing. 2022-12-31 01:18:58 +01:00
Ottermandias
3eb35c479e Fix dirty flag in Item Swap Window. 2022-12-29 21:11:54 +01:00
Ottermandias
cc55ebb28f Add a suffix of a stable hash of the original filename to texture paths. 2022-12-29 21:11:38 +01:00
Ottermandias
5b3d5d1e67 Add basic version of item swap, seemingly working for hair, tail and ears. 2022-12-29 18:53:31 +01:00
Ottermandias
e534ce37d5 Add display for subfile resources and clean up better. 2022-12-29 13:09:40 +01:00
Ottermandias
87b6fe6aa6 Change subfile handling to maybe retain associated game object for Mare. 2022-12-29 00:36:35 +01:00
Ottermandias
4df9ac4632 Possibly improve VFX association with character collections for ground effects and maybe some normal effects, too. 2022-12-28 14:09:09 +01:00
Ottermandias
743f449a49 Don't reflect the interface before it apparently exists. 2022-12-28 12:56:47 +01:00
Ottermandias
5cd4b49fee Add a toggle to advanced settings that uses reflection to change the Synchronous Load option in Dalamud. 2022-12-27 18:09:59 +01:00
Ottermandias
ef19af481b Add a toggle to keep metadata changes to the default value when importing TTMPs. 2022-12-26 18:36:52 +01:00
Ottermandias
707ae090bf Treat AVFX similar to MTRL, and ATEX similar to TEX. 2022-12-26 18:01:36 +01:00
Ottermandias
3e26972a15 Convert Unknown Equipslots to Head for DemiHuman IMC. 2022-12-25 14:40:32 +01:00
Ottermandias
8bca3d82f5 Probably fix some atex/avfx problems. 2022-12-25 14:04:29 +01:00
Ottermandias
d5e2fc3b05 Try to associate battle voices to characters. 2022-12-22 16:45:23 +01:00
Ottermandias
506f7d5824 Use correct variant on imc deserialization. 2022-12-21 14:25:53 +01:00
Ottermandias
347e4b2023 Add changed item for meta manipulations to effective changes. 2022-12-20 21:18:37 +01:00
Ottermandias
6a3d214e15 Consider manipulations in changed items. 2022-12-20 21:09:36 +01:00
Ottermandias
b40de0e125 Improve chat command help a bit. 2022-12-20 21:09:24 +01:00
Ottermandias
6f356105cc Add better chat command handling, help, and option to set basic mod state. 2022-12-20 20:17:18 +01:00
Ottermandias
1ae96c71a3 Use CreateIndividualCollection instead of Add in interface to trigger events. 2022-12-19 23:53:35 +01:00
Ottermandias
f872a14747 Merge branch 'master' of github.com:xivDev/Penumbra 2022-12-19 15:18:41 +01:00
Ottermandias
dc493268f8 Add CopyModSetting API. 2022-12-19 15:18:29 +01:00
Ottermandias
f63903e3e6 Add slots to demihuman imc. 2022-12-17 16:30:20 +01:00
Actions User
727cf7e313 [CI] Updating repo.json for refs/tags/0.6.0.6 2022-12-08 23:18:06 +00:00
Ottermandias
d0ed8abab8 Add a small hack to interpret BattleNPC as Players in some cases for Anamnesis. 2022-12-08 21:32:51 +01:00
Ottermandias
b65bef17b2 Small fixes for backup, respect export directory on load. 2022-12-06 15:59:57 +01:00
Ottermandias
c800f3191f Try to use player collection during aesthetician. 2022-12-05 19:48:41 +01:00
Actions User
cac6017392 [CI] Updating repo.json for refs/tags/0.6.0.5 2022-12-04 16:58:15 +00:00
Ottermandias
075f8bafa0 Add changelog. 2022-12-04 17:55:48 +01:00
Ottermandias
84b0fc3f69 Change aesthetician identification. 2022-12-04 17:01:26 +01:00
Ottermandias
9af7e9d948 Rework special actor identification again. 2022-12-04 16:26:01 +01:00
Actions User
63a22198aa [CI] Updating repo.json for refs/tags/0.6.0.4 2022-12-04 00:53:48 +00:00
Ottermandias
2b6275fe67 Handle ownership in gpose / cutscenes better. 2022-12-04 01:35:11 +01:00
Ottermandias
882a59c1bf Handle Chocobos and GPose Ownership. 2022-12-04 00:48:05 +01:00
Ottermandias
5f4351d4f1 Fix collection selectors not updating correctly. 2022-12-04 00:10:56 +01:00
Actions User
1887a785f5 [CI] Updating repo.json for refs/tags/0.6.0.3 2022-12-03 19:45:09 +00:00
Ottermandias
e221c275a2 Names can have a hyphen as last character apparently. 2022-12-03 20:42:26 +01:00
Actions User
2e272f8e3a [CI] Updating repo.json for refs/tags/0.6.0.2 2022-12-03 17:56:56 +00:00
Ottermandias
69ced1089c Add bugfix changelog. 2022-12-03 18:54:15 +01:00
Ottermandias
a6b3aab61a Add Mannequin-Handling for Retainer Individuals 2022-12-03 18:54:06 +01:00
Ottermandias
c06eb1ad3d Fix typo in imc path. 2022-12-03 18:25:43 +01:00
Ottermandias
bfddcdd7e2 Check Yourself assignment for special actors. 2022-12-03 17:07:09 +01:00
Ottermandias
114ed5954e Check aesthetician for Yourself collection. 2022-12-03 17:07:09 +01:00
Actions User
2606632533 [CI] Updating repo.json for refs/tags/0.6.0.1 2022-12-03 15:10:07 +00:00
Ottermandias
5df00b0c7f Fix the dumb 2022-12-03 16:07:32 +01:00
Actions User
972187d8ed [CI] Updating repo.json for refs/tags/0.6.0.0 2022-12-03 14:56:18 +00:00
Ottermandias
7db67fefa8 Update release action for build. 2022-12-03 15:53:48 +01:00
Ottermandias
7635c0c834 Update Changelog 2022-12-03 14:46:35 +01:00
Ottermandias
0534fecc0c Added button to immediately assign collection to current player. 2022-12-03 14:41:04 +01:00
Ottermandias
37a56c56af Fix bug with collections with inheritance saving on every launch. 2022-12-03 14:37:48 +01:00
Ottermandias
ca51c3b107 Add object-specific IPC for resolving paths and meta. 2022-12-03 14:06:47 +01:00
Ottermandias
f1b495dff4 Add some improvements to game path stuff, move the race inheritance tree to game data, etc. 2022-12-02 17:18:23 +01:00
Ottermandias
b50ed4b99a Add API events for mod deletion, addition or move. 2022-12-02 17:16:48 +01:00
Ottermandias
2900351b9a Fix Player Collection identification. 2022-12-02 17:15:24 +01:00
Ottermandias
69703ed97f Remove checking for negative values in colorset editing, show gamepath info in file selection. 2022-11-26 21:16:56 +01:00
Ottermandias
95d7bc0023 Save groups after incorporating meta files and do not delete meta files that may still be used for other redirections. 2022-11-26 11:52:04 +01:00
Ottermandias
4435bb035a Allow re-duplicating/normalizing even with no duplicates by hotkey. 2022-11-26 11:51:31 +01:00
Ottermandias
3391a8ce71 Add functions to re-export meta changes to TexTools .meta and .rgsp formats. 2022-11-26 01:54:09 +01:00
Ottermandias
7a09d561e9 Fix a bug with RSP changes on non-base collections. 2022-11-25 12:25:52 +01:00
Ottermandias
7033b65d33 Add an option to reduplicate and normalize a mod. 2022-11-24 21:54:02 +01:00
Ottermandias
eedd3e2dac Add Model Parsing and display them under Changed Items, also display variants there, and rework Data Sharing a bunch. 2022-11-24 18:25:51 +01:00
Ottermandias
a64273bd73 Fix chat command not working. 2022-11-22 16:57:40 +01:00
Ottermandias
776d993589 Trying to understand why test builds fail. 2022-11-21 17:31:11 +01:00
Ottermandias
29af320092 API update. 2022-11-21 17:14:51 +01:00
Ottermandias
74ed6edd6f Update IPC to use better mechanisms for temporary collections without breaking backwards compatibility. 2022-11-21 17:00:55 +01:00
Ottermandias
16a56eb5d0 Turn mods without names to warnings. 2022-11-21 15:33:51 +01:00
Ottermandias
304b75e7d2 Add support for retainer collections, fix deleted assignments not updating identifiers. 2022-11-21 15:33:33 +01:00
Ottermandias
e47ca842b2 Fix bug in temporary collection names and extend IPC Tester for temp mods. 2022-11-20 15:48:52 +01:00
Ottermandias
41ed873eaf Formatting... 2022-11-19 21:28:27 +01:00
Ottermandias
c8edd87df8 Add changelog, improve Support Info, fix bug with folder checking, remove obsolete ownership settings. 2022-11-19 21:16:38 +01:00
Ottermandias
893e0a13bd Change entirely backward compatible API functions to do reasonable things in new system. 2022-11-19 20:12:15 +01:00
Ottermandias
2fac923452 Cache collections instead of looking them up for every single file. 2022-11-18 19:53:51 +01:00
Ottermandias
f676bd1889 Do not check every identifier. 2022-11-18 19:53:51 +01:00
Ottermandias
03bbba6735 Use Path.Join and Path.GetFileName for adding mods to not allow arbitrary folder but only those in the penumbra root directory. 2022-11-18 19:53:43 +01:00
Ottermandias
353694177e Use Path.Join and Path.GetFileName for adding mods to not allow arbitrary folder but only those in the penumbra root directory. 2022-11-18 11:42:23 +01:00
Ottermandias
4309ae8ce2 Update everything except for IPC and temp collections to new system. 2022-11-17 18:17:23 +01:00
Ottermandias
6a6eac1c3b Use IndividualCollections in PathResolver. 2022-11-17 15:22:31 +01:00
Ottermandias
f8c0702432 Add Ornaments, further work. 2022-11-17 13:49:15 +01:00
Ottermandias
bda3c1f1ac Continued work on actor identification, migration seems to work. 2022-11-16 15:33:41 +01:00
Ottermandias
0444c28187 More Actor stuff. 2022-11-15 21:07:14 +01:00
Ottermandias
17a8e06c1d Finish work on dye previews. 2022-11-14 17:15:41 +01:00
Ottermandias
4df8f720f5 Fix small bug when using text commands to enable or disable Penumbra. 2022-11-14 13:33:14 +01:00
Ottermandias
b3a993a2bc Further work on Dye Template previews 2022-11-11 15:59:35 +01:00
Ottermandias
0b1a11132b Update to current state of ActorIdentification, add start of collection management. 2022-11-09 16:55:15 +01:00
Ottermandias
cbc27d31da Fix a missed OtterGui commit. 2022-11-09 14:17:18 +01:00
Ottermandias
e6fce32975 Fix dummy situation for group settings. 2022-11-09 14:07:07 +01:00
Ottermandias
76fc235cb7 Make collection selectors filterable. 2022-11-09 13:58:16 +01:00
Ottermandias
0e7c564d14 Misc. Changes. 2022-11-09 13:57:34 +01:00
Ottermandias
8d11e1075d Add some refactoring of data, Stains and STM files. 2022-11-09 13:55:02 +01:00
Ottermandias
7e167cf0cf Add export options to mdl and material editing. 2022-11-06 13:31:29 +01:00
Ottermandias
732ca561a1 Some stuff 2022-11-04 22:41:50 +01:00
Ottermandias
cbdac759b3 Some small fixes. 2022-11-04 17:09:01 +01:00
Ottermandias
68a725d51d Further Identification stuff. 2022-11-04 16:10:29 +01:00
Ottermandias
878f69fd91 Actor Stuff. 2022-11-04 16:09:29 +01:00
Ottermandias
1353e591b8 Use normalization before replacing symbols. 2022-11-04 16:08:08 +01:00
Ottermandias
8dab9407ad Fix bug in option dragging. 2022-11-04 15:16:50 +01:00
Ottermandias
ef3ffb5f10 Use DataShare in ObjectIdentifier 2022-10-30 12:42:09 +01:00
Ottermandias
52b2b66cd7 Update dotnet version for builds. 2022-10-29 16:23:22 +02:00
Ottermandias
1046c4e991 Add Penumbra.String. 2022-10-29 16:03:05 +02:00
Ottermandias
3c0cdc3d0a Oops. 2022-10-29 16:02:18 +02:00
Ottermandias
35baba18bf Extract Strings to separate submodule. 2022-10-29 15:53:53 +02:00
Actions User
bc901f3ff6 [CI] Updating repo.json for refs/tags/0.5.11.1 2022-10-20 12:41:49 +00:00
Ottermandias
a49e3312d3 Add Changelog. 2022-10-20 14:39:06 +02:00
Ottermandias
cb4f9f8131 Make migration and immediate file saving somewhat more stable, actually dispose Framework. 2022-10-20 14:25:28 +02:00
Ottermandias
ccfc05f2b2 Add local data, favorites and tags. 2022-10-19 01:01:40 +02:00
Ottermandias
b9662e39a9 Fix assigning current default to new character. 2022-10-16 13:00:13 +02:00
Ottermandias
e281843760 Add character collection setting via chat command. 2022-10-15 16:38:04 +02:00
Ottermandias
af3575a053 Add Changelogs 2022-10-12 17:25:36 +02:00
Ottermandias
1be3b06292 Improve IMC Exception Handling. 2022-10-12 17:23:11 +02:00
Ottermandias
b3814e61d1 Fix misidentification bug. 2022-10-12 12:51:36 +02:00
Ottermandias
847d8432ff Add backface and transparency handling as well as more info to Mtrl handling. 2022-10-12 12:16:21 +02:00
Ottermandias
febfa8836e Misc. 2022-10-11 15:11:15 +02:00
Ottermandias
6039be8685 Added Enabled State API. 2022-10-11 15:10:59 +02:00
Ottermandias
8b156c7d58 Update some tutorials. 2022-10-10 15:09:43 +02:00
Ottermandias
1a1cbb5404 Changelog. 2022-10-10 13:52:49 +02:00
Ottermandias
6eb2d9a9d2 Add Base64 to customizedata. 2022-10-09 22:56:53 +02:00
Ottermandias
bbfdc7fad2 Clarify disallowed paths. 2022-10-09 22:56:26 +02:00
Ottermandias
707f308fac Fix problems with manual meta edits not masking correctly. Add warning when not entering model Id. 2022-10-09 22:56:09 +02:00
Ottermandias
e226b20953 Better. 2022-10-08 19:06:09 +02:00
Ottermandias
8f93df533a Increment API version. 2022-10-08 02:04:00 +02:00
Ottermandias
918d5db6a6 Use external library for API interface and IPC. 2022-10-08 02:02:44 +02:00
Ottermandias
b3f048bfe6 Add Mixed Case options for byte strings. 2022-10-07 13:00:46 +02:00
Actions User
d6d4a0db4c [CI] Updating repo.json for refs/tags/0.5.10.0 2022-10-05 11:22:59 +00:00
Ottermandias
b7a09bd3bc 5.10 2022-10-05 13:20:38 +02:00
Ottermandias
8d597f9da5 Add buttons to export and import all colorset rows at once, changelog. 2022-10-05 12:31:40 +02:00
Ottermandias
31ac6187bc Extended export capabilities 2022-10-05 12:31:08 +02:00
Ottermandias
097923f5ff Several small fixes. 2022-10-03 11:36:17 +02:00
Ottermandias
6014d37bed Further fixes. 2022-09-29 13:50:52 +02:00
Ottermandias
fabe2a9a16 Fix some texture handling. 2022-09-29 13:00:43 +02:00
Ottermandias
fe4955f8fc Fix filtered combo width. 2022-09-29 13:00:43 +02:00
Actions User
1808d263da [CI] Updating repo.json for refs/tags/0.5.9.0 2022-09-28 21:17:03 +00:00
Ottermandias
80efa1ccb8 Add Changelog. 2022-09-28 23:13:20 +02:00
Ottermandias
7baed8d430 Add resetting to cutscene actors. 2022-09-28 23:02:42 +02:00
Ottermandias
bb06c27359 Use filterable combo in special collections selector. 2022-09-28 16:37:32 +02:00
Ottermandias
1d6d696cb7 Split special collections. 2022-09-27 15:43:05 +02:00
Ottermandias
ef418b6821 Fix default collection on fresh installs. 2022-09-27 14:12:31 +02:00
Ottermandias
c681f1533d Let meta incorporation look at both extensions. 2022-09-27 10:13:53 +02:00
Actions User
55c17c7845 [CI] Updating repo.json for refs/tags/0.5.8.7 2022-09-26 18:15:36 +00:00
Ottermandias
49b53b7a6a Maybe final meta fixes? 2022-09-26 20:13:10 +02:00
Actions User
6b6e686ee6 [CI] Updating repo.json for refs/tags/0.5.8.6 2022-09-26 12:44:04 +00:00
Ottermandias
5cdb13328c And another one. 2022-09-26 14:41:18 +02:00
Actions User
d7f8476e5b [CI] Updating repo.json for refs/tags/0.5.8.5 2022-09-26 12:13:30 +00:00
Ottermandias
9cfb85d1aa Changed changelog. 2022-09-26 14:10:56 +02:00
Ottermandias
ec0a6fa1b1 Merge branch 'master' of github.com:xivDev/Penumbra 2022-09-26 14:05:20 +02:00
Ottermandias
e98deab60c Set IMC files when character utility is ready. 2022-09-26 14:05:07 +02:00
Actions User
bb8d9f9ad9 [CI] Updating repo.json for refs/tags/0.5.8.3 2022-09-26 11:15:33 +00:00
Ottermandias
4f1106344a Merge branch 'master' of github.com:xivDev/Penumbra 2022-09-26 13:12:57 +02:00
Ottermandias
0ff851f717 Another try at fixing metadata, maybe. 2022-09-26 13:12:42 +02:00
Actions User
133a912941 [CI] Updating repo.json for refs/tags/0.5.8.2 2022-09-25 16:38:40 +00:00
Ottermandias
2ee64137a7 Add Changelog. 2022-09-25 18:36:14 +02:00
Ottermandias
566a5b1fd5 Merge branch 'master' of github.com:xivDev/Penumbra 2022-09-25 18:34:40 +02:00
Ottermandias
0d150cf19b Add reloading button to textures. 2022-09-25 18:34:22 +02:00
Ottermandias
62d3053d34 Fix some meta bugs. 2022-09-25 18:34:12 +02:00
Ottermandias
35c6e0ec88 Fix some unnecessary crashes on mtrl. 2022-09-25 18:33:54 +02:00
Actions User
020cdbb868 [CI] Updating repo.json for refs/tags/0.5.8.1 2022-09-24 13:20:42 +00:00
Ottermandias
b359c18360 Add debug tab for meta changes (pretty useless...) and maybe fix reset problem. 2022-09-24 15:16:09 +02:00
Actions User
b7b15532f8 [CI] Updating repo.json for refs/tags/0.5.8.0 2022-09-23 18:58:51 +00:00
Ottermandias
3158d3da8c Change mipmap handling again. 2022-09-23 20:48:59 +02:00
Ottermandias
cadaafb887 Fix stupid file existence check, improve texture file selector. 2022-09-23 18:01:49 +02:00
Ottermandias
5c81970558 Handle weird mip map/size inconsistencies, maybe? 2022-09-23 17:59:07 +02:00
Ottermandias
daaee4feb6 Work on Changelog. 2022-09-23 15:31:19 +02:00
Ottermandias
1f2d5246fe Fix some stuff, add Saving. 2022-09-23 15:21:30 +02:00
Ottermandias
47f5e14972 Merge branch 'master' into Textures 2022-09-23 12:46:59 +02:00
Ottermandias
ac52515e0d Add Changelog 2022-09-23 12:46:42 +02:00
Ottermandias
4b7315d364 Merge branch 'master' into Textures 2022-09-22 18:52:54 +02:00
Ottermandias
3ad811a1d0 Add default settings to mods. 2022-09-22 18:52:16 +02:00
Ottermandias
8d48fcff42 Add option for redraw bar. 2022-09-22 15:31:18 +02:00
Ottermandias
819264045b Add signal that a game path can not be added to file redirections, maybe fix some UsedGamePath bugs. 2022-09-22 14:25:19 +02:00
Ottermandias
fe8f2e2fc5 Blep 2022-09-22 14:24:35 +02:00
Ottermandias
aeb2e9facd Merge branch 'master' into Textures 2022-09-20 21:11:54 +02:00
Ottermandias
1c97b52179 Automatically incorporate all .meta and .rgsp files when adding mods via API. 2022-09-20 15:42:09 +02:00
Ottermandias
ea023ebb5c Add handling for the 1.0 Decal texture. 2022-09-19 18:52:49 +02:00
Ottermandias
57e66f9b66 Fix some problems with super early files and meta files. 2022-09-19 13:19:27 +02:00
Ottermandias
257c0d390b Let Eqdp change all files in the racial tree instead of just the own race code. 2022-09-18 13:40:56 +02:00
Ottermandias
358064cd5f Move redraw buttons to mod panel. 2022-09-18 02:39:16 +02:00
Ottermandias
5538c5704d Add IPC for Interface Collection. 2022-09-17 23:27:38 +02:00
Ottermandias
273111775c Add interface collection. 2022-09-17 22:53:14 +02:00
Ottermandias
8597070063 Add redraw buttons and tutorial. 2022-09-17 21:45:24 +02:00
Ottermandias
01c360416f Add file selection combo to textures. 2022-09-17 00:48:38 +02:00
Ottermandias
19e5e94c64 Merge branch 'master' into Textures
# Conflicts:
#	Penumbra/Import/TexToolsImporter.Archives.cs
#	Penumbra/UI/Classes/ModEditWindow.Textures.cs
2022-09-17 00:03:52 +02:00
Ottermandias
b34999a1a5 Use MetaReverter for all cases, improve Eqdp handling through this. 2022-09-16 21:14:57 +02:00
Ottermandias
af3a07c227 Switch CharacterUtility to use linked lists of changes. 2022-09-16 21:14:57 +02:00
Ottermandias
9753c14b32 Changelog Entry 2022-09-16 21:14:57 +02:00
Ottermandias
93b11fb705
Merge pull request #250 from Soreepeong/fix/sqpackstream-readfileblock
Fix ReadFileBlock (NotAdam/Lumina#42)
2022-09-16 11:53:46 +02:00
Soreepeong
d11b7e11aa Fix ReadFileBlock (NotAdam/Lumina#42) 2022-09-16 17:10:12 +09:00
Ottermandias
1dae7fe036 Add options for changelog display. 2022-09-15 18:31:35 +02:00
Ottermandias
ce73385333 Set changelog start. 2022-09-15 17:11:17 +02:00
Ottermandias
7c955cc236 Improve some things and fix bugs in Option editing. 2022-09-15 17:11:17 +02:00
Ottermandias
72ef666d51 Use custom logger everywhere. 2022-09-15 17:11:17 +02:00
Ottermandias
fabbeeae13 Fix Actor 201, maybe. 2022-09-15 17:11:17 +02:00
Actions User
2404002041 [CI] Updating repo.json for refs/tags/0.5.7.1 2022-09-10 14:43:57 +00:00
Ottermandias
e5ba2317ac Reworked Changelog display slightly. 2022-09-10 16:41:03 +02:00
Ottermandias
d89db756f3 Fix Changelog for GlobalScales. 2022-09-10 16:08:41 +02:00
Actions User
d820b886b3 [CI] Updating repo.json for refs/tags/0.5.7.0 2022-09-10 13:44:40 +00:00
Ottermandias
521c86d81d Revert stupid from last commit, add changelog, push update. 2022-09-10 15:42:23 +02:00
Ottermandias
d65488632a Fix Inspect Identification not working. 2022-09-10 15:08:22 +02:00
Ottermandias
aecb033537 Fix an exception on inspect identification 2022-09-09 22:23:51 +02:00
Ottermandias
cceab7d98d Add temp collection stuff. 2022-09-09 14:58:43 +02:00
Ottermandias
e9b12da97e Make identically named options selectable, fix crash after deleting options. 2022-09-08 16:39:55 +02:00
Ottermandias
f15c20a999 Add PMP changelog entries. 2022-09-08 16:20:33 +02:00
Ottermandias
7b7f241923 Add Penumbra Mod Pack file ending and migration. 2022-09-08 15:49:02 +02:00
Ottermandias
5eda2d3a23 Fix wonky line 2022-09-08 15:48:34 +02:00
Ottermandias
7b4654ce34 Bloop 2022-09-08 15:44:57 +02:00
Ottermandias
6e82242a72 Current Textures 2022-09-07 14:00:39 +02:00
Ottermandias
1fe334e33a Add Changelog, prevent UI category files from deduplicating, revert ui hash change. 2022-09-06 22:34:26 +02:00
Ottermandias
4beded8a7a Make Invalid ResolveData more definitive. 2022-09-05 14:43:14 +02:00
Ottermandias
1ba38a7704 Merge branch 'pr/n248_master' 2022-09-05 14:10:05 +02:00
Ottermandias
55de29e0ac Dispose fix and typo. 2022-09-05 14:09:50 +02:00
Ottermandias
0f35dd69f9 Add IPC test, optimize tester a little, only call event when game object available. 2022-09-05 14:01:12 +02:00
Ottermandias
d12a3dd152 Rework ResolveData. 2022-09-05 13:30:07 +02:00
Stanley Dimant
75182d094b changes to LinkedModCollection nullability 2022-09-05 12:40:00 +02:00
Stanley Dimant
e0000c9ef9 remove ottergui from material 2022-09-05 12:40:00 +02:00
Stanley Dimant
dcdc6d1be1 add LinkedModCollection to be able to retrospectively verify which gamepath was resolved for which game object 2022-09-05 12:40:00 +02:00
Ottermandias
07af64feed Small fixes. 2022-09-05 12:39:41 +02:00
Ottermandias
9ce948e238 Stop replacing UI hashes to test if that fixes crashes. 2022-09-04 02:51:42 +02:00
Ottermandias
ae842720ee More Evp stuff, update libs, fix archive extraction part 1. 2022-09-03 15:23:44 +02:00
Ottermandias
37ad1f68b0 Fix Adventurer Plates. 2022-09-02 18:37:35 +02:00
Ottermandias
df249618ab Merge branch 'master' of github.com:xivDev/Penumbra 2022-09-02 18:36:54 +02:00
Ottermandias
2010e02034 Add some start for Evp Data. 2022-09-02 18:36:34 +02:00
Ottermandias
8aec63d0be tmp 2022-09-01 17:05:20 +02:00
Actions User
83d1ced8f5 [CI] Updating repo.json for refs/tags/0.5.6.3 2022-08-31 15:12:55 +00:00
Ottermandias
6b76337ec4 Use custom initialization for native lib. 2022-08-31 14:36:21 +02:00
Actions User
301d3b2736 [CI] Updating repo.json for refs/tags/0.5.6.2 2022-08-30 22:11:53 +00:00
Ottermandias
9f3871eb6d Prepare Texture import rework with OtterTex. 2022-08-31 00:00:26 +02:00
Ottermandias
94096a8f3e Merge branch 'master' of github.com:xivDev/Penumbra 2022-08-27 00:47:22 +02:00
Ottermandias
f0b970c102 Rework some metastuff. 2022-08-27 00:47:03 +02:00
Actions User
f05529c7d2 [CI] Updating repo.json for refs/tags/0.5.6.1 2022-08-24 21:00:04 +00:00
Ottermandias
53818f3556 Fix against new client structs 2022-08-24 22:57:14 +02:00
Ottermandias
5c8471fe3f Fix edits not applying changes. 2022-08-24 22:39:33 +02:00
Ottermandias
62eb032765 Fix CharacterUtility. 2022-08-24 22:39:19 +02:00
Actions User
901b54805a [CI] Updating repo.json for refs/tags/0.5.6.0 2022-08-24 16:23:52 +00:00
Ottermandias
0f2be88706 Merge branch 'net6' 2022-08-24 18:16:07 +02:00
Ottermandias
e66ca7c580 Further fixes. 2022-08-24 17:53:25 +02:00
Ottermandias
674dc03f46 Fix some stuff. 2022-08-23 17:49:14 +02:00
Ottermandias
cfeb20a18e Hook ChangeCustomize to include RSP changes. 2022-08-20 16:06:07 +02:00
Ottermandias
4efdd6d834 Add improved WIP edit windows for materials and models 2022-08-20 16:05:52 +02:00
Ottermandias
e0a171051d Cleanup 2022-08-18 11:52:18 +02:00
Ottermandias
18df989420 Check folder names for '.' and '..' 2022-08-17 23:00:28 +02:00
Ottermandias
5a278d4424 Add disabled sections to selector in meta edit 2022-08-17 23:00:13 +02:00
Ottermandias
fee3f500c5 Some cleanup 2022-08-17 22:59:56 +02:00
Ottermandias
80edfe7804 Added an event when a newly created draw object finishes CharacterBase.Create. 2022-08-16 15:30:05 +02:00
Ottermandias
5b5a1e2fd8 Check path length on adding them to the cache and log error if a path is too long. 2022-08-15 13:15:26 +02:00
Ottermandias
09417bd6c1 Resolve actor 244 to player collection. 2022-08-15 12:58:44 +02:00
Ottermandias
6773fe0932 Set minimal model set Id values to 0 for a bunch of meta edits 2022-08-15 12:58:14 +02:00
Ottermandias
df9f791395 Check for too long paths when building cache. 2022-08-15 12:57:57 +02:00
Ottermandias
5e9cb77415 Add material file parsing and writing. 2022-08-13 21:20:40 +02:00
Ottermandias
5ac3a903f6 Update submod positions on group deletions. 2022-08-13 21:20:40 +02:00
Ottermandias
8aefdbd948 Rename some collection stuff. 2022-08-13 21:20:40 +02:00
Ottermandias
f264725c45 Add an Update Bibo Materials button. 2022-08-10 15:45:14 +02:00
Ottermandias
5b07245cd9 Add event for changing mod directory. 2022-08-09 22:50:54 +02:00
Ottermandias
c0542d0e94 Fix some texture stuff. 2022-08-09 22:12:40 +02:00
Ottermandias
8fdd173388 Add cutscene identification and IPC, reorder PathResolver stuff. 2022-08-09 22:12:15 +02:00
Ottermandias
dc61f362fd Fix bug in cutscene character identification 2022-08-04 14:49:14 +02:00
Ottermandias
4a008fbc3e Further net6 2022-08-03 11:38:13 +02:00
Ottermandias
7936c43b0b net6 2022-08-03 11:27:33 +02:00
Ottermandias
4881e4ef09 Remove Show Advanced, forbid ProgramFiles root folders. 2022-08-01 18:24:20 +02:00
Ottermandias
70a5ee9485 Small fix for Enable All. 2022-07-31 11:34:54 +02:00
Actions User
24aa7eac24 [CI] Updating repo.json for refs/tags/0.5.5.0 2022-07-30 16:54:53 +00:00
Ottermandias
2ca90f2518 Add try-catch and locking to framework manager. 2022-07-30 18:39:39 +02:00
Ottermandias
ff5e72e979 Add enable all for option groups 2022-07-30 18:38:58 +02:00
Ottermandias
f37ad11ab8 User protection. 2022-07-27 16:54:20 +02:00
Ottermandias
1af0517f36 Fix character utility loading not registering on first load. 2022-07-27 16:00:57 +02:00
Ottermandias
7305ad41ac Allow Penumbra to import regular archives for penumbra mods. 2022-07-27 16:00:37 +02:00
Ottermandias
ee48c7803c Some character equip changes. 2022-07-27 13:04:57 +02:00
Ottermandias
7a7093369f Add Overview mode to file redirection edit. 2022-07-27 12:59:33 +02:00
Ottermandias
d6c0362404 Let SubMods know their location in a mod. 2022-07-27 10:37:45 +02:00
Ottermandias
0b2b0d1beb Deal with multiple resource containers in resource manager. 2022-07-26 16:25:20 +02:00
Ottermandias
842b1c1fe5 Add some collected info to advanced edit tab. 2022-07-22 15:42:41 +02:00
Ottermandias
714e8e862f Change a bunch of names and tooltips. 2022-07-21 22:55:19 +02:00
Ottermandias
9cb6084d31 Fix an exception on broken mods. 2022-07-21 22:54:27 +02:00
Ottermandias
f1d9757077 Redraw mounts when redrawing actors. 2022-07-21 11:57:18 +02:00
Ottermandias
9dee0862cc Some early glamourer changes. 2022-07-21 10:07:52 +02:00
Ottermandias
c2bc8252f1 Allow only valid characters when creating collections. 2022-07-21 10:07:20 +02:00
Ottermandias
f808c8a471 Compare cutscene actors by customization. 2022-07-21 10:01:24 +02:00
Actions User
28e0affbb4 [CI] Updating repo.json for refs/tags/0.5.4.8 2022-07-18 10:17:01 +00:00
Ottermandias
39d339a3d8 Make IMC handler load temp collections correctly. 2022-07-17 16:51:10 +02:00
Ottermandias
00e1736d13
Update README.md 2022-07-17 12:49:52 +02:00
Actions User
cfc441b9b1 [CI] Updating repo.json for refs/tags/0.5.4.6 2022-07-16 21:37:47 +00:00
Ottermandias
3d7cf9fc93 Update Serenitys guide address. 2022-07-16 23:35:33 +02:00
Ottermandias
f915b73f8d Fix garbage IMC files 2022-07-16 01:34:01 +02:00
Actions User
4121baac33 [CI] Updating repo.json for refs/tags/0.5.4.4 2022-07-15 10:16:31 +00:00
Ottermandias
3e9f9289e5 Some fixes with collection inheritance. 2022-07-15 12:12:14 +02:00
Actions User
9508b8d9b3 [CI] Updating repo.json for refs/tags/0.5.4.1 2022-07-14 18:30:22 +00:00
Ottermandias
e261b4c0c5 Maybe improve IMC handling. 2022-07-14 20:27:47 +02:00
Ottermandias
23a08f30c4 Fix clipboard crashes 2022-07-14 17:32:24 +02:00
Ottermandias
08ae14222b Add filtering selecting single results 2022-07-14 17:32:13 +02:00
Ottermandias
e9a2744131 Fix crash. 2022-07-14 11:07:32 +02:00
Ottermandias
fff3f6d1cb Tutorial improvements. 2022-07-14 11:07:32 +02:00
Ottermandias
55b9531d93 Make both collection headers default open again 2022-07-14 11:07:32 +02:00
Ottermandias
3434c437ce Fix support info bug. 2022-07-14 11:07:32 +02:00
Actions User
438f18f1b8 [CI] Updating repo.json for refs/tags/0.5.4.0 2022-07-13 21:30:34 +00:00
Ottermandias
ac85c491fd Fix problem. 2022-07-13 23:27:19 +02:00
Ottermandias
a47a14fe95 Use correct collections on login screen. 2022-07-13 18:16:04 +02:00
Ottermandias
be2260dc51 Updates for Dalamud update. 2022-07-13 17:06:44 +02:00
Ottermandias
d1f0f4490c Fix tooltip when no advanced editing. 2022-07-13 16:58:52 +02:00
Ottermandias
1c60a61f79 Anonymized collection names for log 2022-07-13 16:58:27 +02:00
Ottermandias
b5698acebf Do a bunch of AggressiveInlining. 2022-07-12 17:41:21 +02:00
Ottermandias
57b9f60ba3 Some Tutorial updates. 2022-07-12 16:26:37 +02:00
Ottermandias
b3ee622396 Update workflows to use dalamud release. 2022-07-11 17:49:49 +02:00
Ottermandias
769f54e8dd Add Tutorials. 2022-07-11 17:27:39 +02:00
Ottermandias
2412e3be08 Change default values of AutoDeduplicate and EnableHttp to true. 2022-07-11 17:27:39 +02:00
Ottermandias
aed1474db8 Rephrase some settings. 2022-07-11 17:27:39 +02:00
Ottermandias
b09a736a85 Actually clear cache and restore imc files. 2022-07-11 17:27:39 +02:00
Actions User
4030487472 [CI] Updating repo.json for refs/tags/0.5.3.7 2022-07-10 17:40:52 +00:00
Ottermandias
e6a4cb9a58
Merge pull request #233 from rootdarkarchon/master
Add GetPlayerMetaManipulations to IPC
2022-07-10 18:00:14 +02:00
Stanley Dimant
b9ae348529 add GetPlayerMetaManipulations 2022-07-10 14:53:22 +02:00
Actions User
78bb869e67 [CI] Updating repo.json for refs/tags/0.5.3.6 2022-07-09 14:43:40 +00:00
Ottermandias
79e6a3b228 Merge branch 'master' of github.com:xivDev/Penumbra 2022-07-09 16:40:00 +02:00
Ottermandias
2fa0677869
Merge pull request #232 from rootdarkarchon/master
Add ResolvePlayerPath and rename ReverseResolvePathPlayer
2022-07-09 16:39:57 +02:00
Ottermandias
50d042c104 Fix metadata conflicts causing problems. 2022-07-09 16:39:22 +02:00
Ottermandias
4c90cc84f1 Do not delay IMC manipulations. 2022-07-09 15:23:39 +02:00
Stanley Dimant
da9969d3dd forgot to rename const string 2022-07-09 13:30:39 +02:00
Stanley Dimant
720a1dce7b add ReversePlayerPath to API, rename ReverseResolvePathPlayer to ReverseResolvePlayerPath 2022-07-09 13:29:10 +02:00
rootdarkarchon
acea5b1359
Merge branch 'xivdev:master' into master 2022-07-09 13:17:46 +02:00
Actions User
74cb08e551 [CI] Updating repo.json for refs/tags/0.5.3.5 2022-07-08 22:47:16 +00:00
Ottermandias
70bae7737e Move Collection Change Counter to Collection instead of Cache so it does not reset if cache is destroyed. 2022-07-08 10:52:34 +02:00
Ottermandias
f984283231 Increment the collection change counter when character utility is ready. 2022-07-08 10:52:34 +02:00
Ottermandias
9115cbaac1 Add ReverseResolvePlayer. 2022-07-08 10:52:34 +02:00
Actions User
95de9ea48a [CI] Updating repo.json for refs/tags/0.5.3.4 2022-07-05 21:37:08 +00:00
Ottermandias
f8f275a962 Merge branch 'master' of github.com:xivDev/Penumbra 2022-07-05 23:29:15 +02:00
Ottermandias
abce14dfdd Some fixes. 2022-07-05 23:28:14 +02:00
Actions User
477d61b6ab [CI] Updating repo.json for refs/tags/0.5.3.3 2022-07-04 09:13:09 +00:00
Ottermandias
8fd9569508 Add second ID to retainer check. 2022-07-04 11:10:35 +02:00
Ottermandias
0ce41f82a6 Fix invalid API call when manipulating multiple mods at once. 2022-07-04 11:10:35 +02:00
Actions User
7a3c23d8c9 [CI] Updating repo.json for refs/tags/0.5.3.2 2022-07-03 21:04:35 +00:00
Ottermandias
32e817d793 Some glamourer additions. 2022-07-03 23:01:45 +02:00
Ottermandias
6902ef48d1 Fix crash during migration 2022-07-03 23:01:45 +02:00
Actions User
c4607a718e [CI] Updating repo.json for refs/tags/0.5.3.1 2022-07-02 17:09:33 +00:00
Ottermandias
e1f1a7f378 Fixes. 2022-07-02 19:06:10 +02:00
Actions User
8133e5927f [CI] Updating repo.json for refs/tags/0.5.3.0 2022-07-02 14:47:51 +00:00
Ottermandias
f99bfae4bb Use temporary mechanism to add main menu button. 2022-07-02 16:37:44 +02:00
Ottermandias
d97e9f37a8 Add breaking and feature version with backwards compatibility and warning. 2022-07-02 16:34:20 +02:00
Ottermandias
062c69385f Add NPC special collections. 2022-07-02 15:35:51 +02:00
Ottermandias
d9418b6743 Add Serenity's guides to readme and guide button to main interface. 2022-07-02 15:35:39 +02:00
Ottermandias
f5e29b96e1 Merge branch 'master' of github.com:xivDev/Penumbra 2022-07-02 12:33:47 +02:00
Ottermandias
57a38aeb94 Potentially more secure version for mod loading. 2022-07-02 12:33:25 +02:00
Ottermandias
0af0449814
Merge pull request #227 from InitialDet/master
PluginLog inside OnFrameworkUpdate
2022-07-02 00:42:29 +02:00
Ottermandias
958ff5d803 Fix sorting. 2022-07-02 00:16:26 +02:00
Ottermandias
ab2ca472fc Another try with atomic increment/decrement? 2022-07-01 22:10:11 +02:00
Ottermandias
adfe681631 Fix early commit. 2022-07-01 21:09:51 +02:00
Ottermandias
885dcbdf04 Add Draw after setting up everything else to prevent exception. 2022-07-01 20:46:55 +02:00
Ottermandias
99eb08958c Add some API 2022-07-01 20:46:30 +02:00
Ottermandias
653e21e237 Add MaterialUI IPC and increase API Version to 6. No breaking changes, only additions. 2022-07-01 16:21:41 +02:00
Ottermandias
53ff9a8ab3 Fix Self-Actor for GPose. 2022-07-01 16:20:47 +02:00
Ottermandias
1d7829593e Add IncRef mode and debug DecRef check. 2022-07-01 16:20:09 +02:00
Ottermandias
d445df256b Add synchronous load to base_repo. 2022-07-01 16:18:40 +02:00
Det
c66a09dea3 PluginLog at OnFrameworkUpdate
When using Fixed Designs with Glamourer, this line goes really wild for being on OnFrameworkUpdate, so I thought it would be nice to have it only when debugging.
2022-06-30 19:06:54 -03:00
Ottermandias
f00fe54bb3 Add logging of destructed resource handles. 2022-06-30 13:35:27 +02:00
Ottermandias
71a7520e58 Lock, not fix the main window 2022-06-29 15:29:23 +02:00
Ottermandias
9ae843731d Make penumbra initialization before game code has run possible. 2022-06-29 11:42:55 +02:00
Ottermandias
f13893cf77 Rename EQDP checkmarks. 50/50 chance. 2022-06-28 13:27:13 +02:00
Ottermandias
60229f8b45 Rename EQP bit _2. 2022-06-28 12:28:51 +02:00
Ottermandias
5aeff6d40f Test Penumbra loading CharacterUtility. 2022-06-27 15:19:58 +02:00
Ottermandias
32cf729aa8 Load Atex based on the last loaded Avfx, testing? 2022-06-26 14:39:59 +02:00
Ottermandias
549f8ce4b4 Add special collections. 2022-06-26 13:44:15 +02:00
Ottermandias
ec91755065 Add date sort methods. 2022-06-25 00:05:48 +02:00
Actions User
c975336e65 [CI] Updating repo.json for refs/tags/0.5.2.0 2022-06-23 14:39:39 +00:00
Ottermandias
f0bdecd472 Let manipulations use a single string and re-add redrawing by object. 2022-06-23 16:36:47 +02:00
rootdarkarchon
14d8266d69
Merge branch 'xivdev:master' into master 2022-06-23 00:13:36 +02:00
Ottermandias
4381b9ef64 Add a function to obtain all meta manipulations for a given collection. 2022-06-22 22:34:10 +02:00
Ottermandias
95e7febd38 Let Material Path Resolver search through temporary collections. 2022-06-22 18:43:36 +02:00
rootdarkarchon
809422e8d3
Merge branch 'xivdev:master' into master 2022-06-22 17:17:17 +02:00
Ottermandias
311882948a Add last IPC tests, fix some problems with them, increment API Version to 5. 2022-06-22 17:02:31 +02:00
Ottermandias
f17e9be824 Fix incorrectly disposed VFX. 2022-06-22 15:26:01 +02:00
Ottermandias
8ecf7e2381 Change return value of LoadTimelineResources 2022-06-22 11:56:01 +02:00
Ottermandias
1b4eea4d1e Add IPC Test stuff, only temp missing. 2022-06-22 10:19:11 +02:00
rootdarkarchon
b4231d2a2a
Merge branch 'xivdev:master' into master 2022-06-20 18:42:10 +02:00
Ottermandias
00c11b49f0 Use file type enum for Crc64 handling. 2022-06-20 18:27:54 +02:00
Ottermandias
c4f82435bf Fix for temporary collections. 2022-06-20 18:27:54 +02:00
Ottermandias
6ebf550284 Use Ordinal comparisons 2022-06-20 18:27:54 +02:00
Ottermandias
47cfaf4da2
Merge pull request #220 from Soreepeong/fix/segmented-read
Fix music modding: calculate correct crc32 values for segmented read
2022-06-20 17:40:33 +02:00
Soreepeong
30335fb5d7 Remove redundant crc32 calc 2022-06-21 00:29:52 +09:00
Soreepeong
c49fce4487 Remove MusicManager/DisableSoundStreaming 2022-06-20 10:56:28 +09:00
Soreepeong
9dd12f4a71 Take segmented read into consideration when changing CRC32 values after path replacement 2022-06-20 10:46:39 +09:00
rootdarkarchon
81b3a12341
Merge branch 'xivdev:master' into master 2022-06-19 19:32:35 +02:00
Ottermandias
d6d13594e0 Add Mare Synchronos and MUI API/IPC functions for testing. Not tested myself because how. 2022-06-19 19:20:02 +02:00
Ottermandias
8422d36e4e Add another another animation hook. I hate animations. 2022-06-19 15:00:24 +02:00
Ottermandias
c64743ee98 actually reset test branch to master release, not before master release. 2022-06-19 11:47:14 +02:00
Ottermandias
c330859abc Add customizable hotkeys for mod deletion. 2022-06-19 11:42:38 +02:00
Ottermandias
9d43895f38 Destroy clippers to stop leaking. 2022-06-19 02:43:25 +02:00
Actions User
37dcd0ba55 [CI] Updating repo.json for refs/tags/0.5.1.2 2022-06-18 21:57:17 +00:00
Ottermandias
c6e6c0098c Emergency Fix 2022-06-18 23:54:59 +02:00
Actions User
8b7dc8fa5b [CI] Updating repo.json for refs/tags/0.5.1.1 2022-06-18 21:44:26 +00:00
Ottermandias
fc767589a2 Change everything in collection caches to use IMod and introduce TemporaryMod. 2022-06-18 16:00:20 +02:00
Ottermandias
c578bd3a49 Add mod setting API functions. 2022-06-18 15:18:35 +02:00
Ottermandias
df1a75b58a Add another hook for animations in character collections. 2022-06-18 14:23:28 +02:00
rootdarkarchon
f910211394
Merge branch 'xivdev:master' into master 2022-06-18 13:11:52 +02:00
Stanley Dimant
54f2e5c58f Revert "add Penumbra.ObjectIsRedrawn and Penumbra.ReverseResolvePath to API"
This reverts commit 45d8d58ce2.
2022-06-18 13:11:34 +02:00
Stanley Dimant
2103ae3053 Revert "formating fixes"
This reverts commit 1504af9f3d.
2022-06-18 13:11:25 +02:00
Ottermandias
58e46accae Add errors when loading from elsewhere than installedPlugins or devPlugins/Penumbra existing and containing dlls in Release mode 2022-06-18 12:46:06 +02:00
Ottermandias
27650708f0 Fix collection in use not updating on first load. 2022-06-18 12:45:04 +02:00
Ottermandias
c097b634ab Print a log message when Penumbra finished loading containing version and hash. 2022-06-18 11:11:55 +02:00
Ottermandias
018be13216 Use Current collection instead of Default for Effective Files and Changed Items. 2022-06-18 11:11:13 +02:00
Ottermandias
1ee4cb99d0 Add demihuman hooks for resolving. 2022-06-18 11:10:51 +02:00
Ottermandias
1fb2ddaa5e Fix advanced editing always showing. 2022-06-18 10:21:49 +02:00
Ottermandias
35e68e74f4 File Selector improvements. 2022-06-17 17:33:22 +02:00
Ottermandias
002778f4c1 Merge branch 'pr/n217_master' 2022-06-17 17:11:29 +02:00
Ottermandias
3c5cff1418 Some cleanup, slight changes. 2022-06-17 17:10:48 +02:00
Stanley Dimant
bcd62cbe69 formating fixes 2022-06-17 16:32:52 +02:00
Stanley Dimant
1c7037416c add Penumbra.ObjectIsRedrawn and Penumbra.ReverseResolvePath to API 2022-06-17 16:31:49 +02:00
Ottermandias
f579933dd7 Change planned API to use interfaces. 2022-06-17 16:30:11 +02:00
Ottermandias
61680f0afb Add warning if the currently edited collection is not in use anywhere. 2022-06-17 16:30:11 +02:00
Ottermandias
abd1fd14f5 Add config to use default or owner collection for housing retainers. 2022-06-17 16:30:11 +02:00
Stanley Dimant
1504af9f3d formating fixes 2022-06-16 23:51:16 +02:00
Stanley Dimant
45d8d58ce2 add Penumbra.ObjectIsRedrawn and Penumbra.ReverseResolvePath to API 2022-06-16 23:33:30 +02:00
rootdarkarchon
086f90171e
Merge pull request #1 from xivdev/master
merge from upstream
2022-06-16 19:06:45 +02:00
Actions User
eff6c2e9af [CI] Updating repo.json for refs/tags/0.5.1.0 2022-06-16 15:07:02 +00:00
Ottermandias
0f2266963d Add prototypes for advanced API. 2022-06-16 17:04:15 +02:00
Ottermandias
787c19a170 Add mod root directory max length and warnings on non-ascii characters. 2022-06-16 17:04:10 +02:00
Ottermandias
5f8eac0ec1 More parsing, mostly untested. 2022-06-16 12:28:31 +02:00
Ottermandias
cc9f8cc84e Small cleanup. 2022-06-14 18:03:42 +02:00
Ottermandias
f6772af246 Prevent a weird case of null crash. 2022-06-13 22:26:36 +02:00
Ottermandias
e994163637 Further work on texture importing. 2022-06-13 22:26:15 +02:00
Ottermandias
80c717c9bc No COM in OtterGui. 2022-06-12 15:41:12 +02:00
Ottermandias
b104bc3249 Rename tab buttons. 2022-06-12 15:18:20 +02:00
Ottermandias
1f46b4951e Reorder collections tab. 2022-06-12 15:15:03 +02:00
Ottermandias
46c5d52a92 Add some other AVFX collection identification. 2022-06-12 11:59:53 +02:00
Ottermandias
5f1dac98d6 Also reset test for actual releases 2022-06-12 01:11:50 +02:00
Ottermandias
7fea8d3854 Change test action yaml to reset correctly. 2022-06-12 00:57:22 +02:00
Ottermandias
28b7bf91bc fixup! Fix disabling a inheritance not removing the mod correctly. 2022-06-12 00:35:15 +02:00
Ottermandias
b29a362395 Add new test release action with separate handling. 2022-06-11 23:53:47 +02:00
Ottermandias
10f06e2715 Start texture import stuff. 2022-06-11 22:15:23 +02:00
Ottermandias
1d3a31db6f Fix changing file redirections manually not counting as applied changes. 2022-06-11 22:13:29 +02:00
Ottermandias
02f1a4cedd Add option to auto-deduplicate on import. 2022-06-11 22:12:54 +02:00
Ottermandias
d2eae54149 Fix disabling a inheritance not removing the mod correctly. 2022-06-11 22:11:59 +02:00
Ottermandias
bf58c6b098 Remove some further Unknown Unknown identifications. 2022-06-11 22:11:38 +02:00
Ottermandias
c3a3a2cd35 Add range check to index redrawing. 2022-06-10 15:32:25 +02:00
Ottermandias
1d935def58 Fix object reloading in GPose, also add index-based redraw to API/IPC. 2022-06-10 14:37:05 +02:00
Ottermandias
19e7d1bf50 Add tooltips to meta manipulation identifiers. 2022-06-09 12:58:25 +02:00
Ottermandias
a4a6a650c5 Merge branch 'master' of github.com:xivDev/Penumbra 2022-06-09 12:38:42 +02:00
Ottermandias
34445eb949 Another try at character collection animations. 2022-06-09 12:38:21 +02:00
Ottermandias
d45e98a254 Fix errors not being shown in the importer status window. 2022-06-09 12:37:49 +02:00
Ottermandias
d89c1abc3b Move advanced editing to a tab button. 2022-06-08 15:22:28 +02:00
Ottermandias
35cff163f8 Give error information on IMC problems. 2022-06-08 15:22:10 +02:00
Actions User
fcb29f23c6 [CI] Updating repo.json for refs/tags/0.5.0.5 2022-06-07 13:42:25 +00:00
Ottermandias
a37a8eb5aa Merge branch 'master' of github.com:xivDev/Penumbra 2022-06-07 15:38:01 +02:00
Ottermandias
765da6d518 Actually bind debug tab to debug mode toggle. 2022-06-07 15:37:36 +02:00
Actions User
ecedacfddb [CI] Updating repo.json for refs/tags/0.5.0.4 2022-06-07 10:42:10 +00:00
Ottermandias
2aed252820 Add default mod import folder. 2022-06-07 12:39:57 +02:00
Ottermandias
7390f97d81 Add free drive space to support info. 2022-06-06 22:24:16 +02:00
Ottermandias
c97b8e8e9a Add option to fix the penumbra window. 2022-06-06 22:23:51 +02:00
Ottermandias
caf19f24cb Fix removal of EST bones. 2022-06-06 21:17:27 +02:00
Ottermandias
d2b969d996 Add Weapon Reload resolving. 2022-06-06 20:35:29 +02:00
Ottermandias
10c4dbc1f8 Change default for preferring owners. 2022-06-05 19:28:52 +02:00
Ottermandias
f4ba14de3c Add Everything option and coloring to active filters. 2022-06-05 18:39:09 +02:00
Ottermandias
513a2780f1 Add updating of changed items to mod changes. 2022-06-05 18:39:09 +02:00
Actions User
40a6ec6010 [CI] Updating repo.json for refs/tags/0.5.0.3 2022-06-05 14:50:54 +00:00
Ottermandias
78b931ec44 Disallow docking for the penumbra main window. 2022-06-05 16:46:41 +02:00
Ottermandias
48a443921e Fix bug with expanding IMC files. 2022-06-05 15:38:45 +02:00
Ottermandias
a798eabf67 Select last imported mod. 2022-06-05 15:38:45 +02:00
Ottermandias
6dbf487c99 Fix collections wrongly becoming active on rediscover. 2022-06-05 15:38:45 +02:00
Actions User
82ac639854 [CI] Updating repo.json for refs/tags/0.5.0.2 2022-06-05 11:18:52 +00:00
Ottermandias
2746f7ea4f Add option to always open mod importer at default path. 2022-06-05 13:16:16 +02:00
Ottermandias
0a81e39690 fixup! Maybe fix migration for metadata containing mods, also fix negative values in mod collection settings causing problems. 2022-06-05 13:10:44 +02:00
Ottermandias
e52fca05d9 Add an option to allow file selector folders to be expanded or collapsed by default. 2022-06-05 13:10:29 +02:00
Ottermandias
3014f7b246
Update release.yml 2022-06-05 12:50:39 +02:00
Ottermandias
b71552607e Delete the penumbrametatmp folder on migration. 2022-06-05 12:38:11 +02:00
Ottermandias
3b2876a6e4 Maybe fix migration for metadata containing mods, also fix negative values in mod collection settings causing problems. 2022-06-05 12:34:00 +02:00
Ottermandias
7409d0bc2f Fix breaking on empty mod paths and add Url field 2022-06-05 12:34:00 +02:00
Ottermandias
8cfc605ed3 Fix face decal changed item identification. 2022-06-05 12:34:00 +02:00
Actions User
cca616bc15 [CI] Updating repo.json for refs/tags/0.5.0.1 2022-06-04 21:13:57 +00:00
Ottermandias
afb758e61a Disable additional pap handling for now. 2022-06-04 23:10:33 +02:00
Actions User
2d200bcabb [CI] Updating repo.json for refs/tags/0.5.0.0 2022-06-04 19:39:59 +00:00
Ottermandias
cd8523a75f Update Inheritance tooltips. 2022-06-04 21:35:27 +02:00
Ottermandias
6e7465aa99 Merge branch 'rework' 2022-06-04 21:12:50 +02:00
Ottermandias
d7215adbc3 Add dye preview actor and extend tooltip for UseCharacterCollectionInTryOn 2022-06-04 21:08:43 +02:00
Ottermandias
135c067fa7 Change debug tab ipc handling. 2022-06-04 20:56:28 +02:00
Ottermandias
be84b36319 Small pap resolving fixes. 2022-06-04 20:47:29 +02:00
Ottermandias
cf79f47e08 Extend some more IPC functions. 2022-06-04 20:46:50 +02:00
Ottermandias
f0131dd5ba Add preliminary pap handling to character collections. 2022-06-04 19:02:58 +02:00
Ottermandias
c0102368c3 Add some options to which special actors use which character collection and fix Inspect Actor recognition. 2022-06-04 00:54:01 +02:00
Ottermandias
4b58213597 Fixed colors in mod selector not automatically updating 2022-06-04 00:52:33 +02:00
Ottermandias
ea4d087ae9 Fix enable/disable/inherit all descendants. 2022-06-04 00:52:07 +02:00
Ottermandias
4ef8eeb042 Fix crash. 2022-06-03 11:04:11 +02:00
Ottermandias
6776a7fa7e Compute GameData on plugin initialize instead of on Penumbra initialize. 2022-06-02 15:35:42 +02:00
Ottermandias
eeaaecb855 Let companions, combat pets and mounts use their owners character collection. 2022-06-02 14:15:34 +02:00
Ottermandias
385ce4c7e9 Add file redirection editing. 2022-06-02 13:04:00 +02:00
Ottermandias
06deddcd8a Extend the item identification a bit to count unidentifiable items and handle icons, demihumans and monsters. 2022-06-02 11:54:53 +02:00
Ottermandias
1ad7787e4c Let the glamour plate actor use the players character collection. 2022-05-31 22:08:35 +02:00
Ottermandias
630469fc0e Stop window actors from redrawing/unloading. 2022-05-31 22:06:09 +02:00
Ottermandias
81435b4ff2 Change when the Deletion event is fired so that all mods are still there at invoke. 2022-05-30 16:19:43 +02:00
Ottermandias
3d7faad2ae Add options for UI hiding. 2022-05-30 16:11:35 +02:00
Ottermandias
50a3a20718 Make default submod lowest priority again. 2022-05-30 16:11:10 +02:00
Ottermandias
4b036c6c26 Change cache reloading and conflicts to actually keep the effective mod and not force full recalculations on every change. 2022-05-29 19:00:34 +02:00
Ottermandias
ee87098386 Fix all files getting added to the default option on import. 2022-05-28 16:18:47 +02:00
Ottermandias
0ff46fa860 blep 2022-05-26 15:23:46 +02:00
Ottermandias
e5c4ddf45d Move help button in mod selector to center. 2022-05-26 13:29:12 +02:00
Ottermandias
2500512a6a Cleanup 2022-05-26 13:28:57 +02:00
Ottermandias
46c8b811ad Add font reloading button. 2022-05-26 13:28:50 +02:00
Ottermandias
4189d240de Turn imc attributes to checkmarks, small fixes. 2022-05-25 19:36:28 +02:00
Ottermandias
c247446ba6 Add copy help text button. 2022-05-25 19:36:11 +02:00
Ottermandias
ced5e344cf Remove single object change functions and update from mod manager. 2022-05-21 20:50:13 +02:00
Ottermandias
4613461154 Add Initialized / Disposed IPC, start the rest of the plugin only after obtaining the default meta files, 2022-05-21 20:49:11 +02:00
Ottermandias
d15ebddf18 CharacterUtility bug test 2022-05-20 15:59:25 +02:00
Ottermandias
12837bbdca Order collections by name, order character collections by character. 2022-05-20 13:58:14 +02:00
Ottermandias
9bceed3d57 Fix some bugs, add clipboard options to meta changes. 2022-05-18 23:03:32 +02:00
Ottermandias
0c3c7ea363 Stop using windows forms, add extensive meta manipulation editing, fix a concurrency crash and a dumb crash. 2022-05-18 17:40:41 +02:00
Ottermandias
e5b739fc52 Add Join Discord button. 2022-05-13 17:58:42 +02:00
Ottermandias
1874de38d0 Fighting against debug info still... 2022-05-13 17:34:50 +02:00
Ottermandias
4a206a633e Add help for inheritance. 2022-05-13 17:27:31 +02:00
Ottermandias
1fd31f30ef Add help popup for mod selector. 2022-05-13 16:07:29 +02:00
Ottermandias
448a745a51 Use default collection as default if no active collections exist. 2022-05-13 16:06:05 +02:00
Ottermandias
f5591f2a4a Small delay fix. 2022-05-13 15:10:36 +02:00
Ottermandias
a86a73bbf5 Make the limit of 32 options for a multi-select group explicit and handle it better. 2022-05-13 15:01:50 +02:00
Ottermandias
976f7840cd Add right-click context to mod selector when not hovering items. 2022-05-13 15:01:05 +02:00
Ottermandias
856c1d089c Fix crash when changing state of multiple mods at once. 2022-05-13 14:29:50 +02:00
Ottermandias
aa0584078b Fix conflicts not sorting because dumb. 2022-05-12 18:42:53 +02:00
Ottermandias
17f9c2ee6f Fix right-click popups for things with identical names but different paths. 2022-05-12 17:57:40 +02:00
Ottermandias
67de0ccf45 Make saving files and recalculating effective files threaded/once per frame. 2022-05-12 17:33:54 +02:00
Ottermandias
b8210e094b Add zip backup options to mods. 2022-05-12 16:49:49 +02:00
Ottermandias
f3d4ffc40a Change Disable Audio Streaming checkbox to be clearer. 2022-05-12 16:49:29 +02:00
Ottermandias
e85d57b094 Allow extracting identically named options again. 2022-05-11 21:02:18 +02:00
Ottermandias
f0af9f1274 A bunch of filesystem fixes. 2022-05-11 20:58:21 +02:00
Ottermandias
0b9a48a485 Maybe fix a weird debugging crash? 2022-05-11 20:57:52 +02:00
Ottermandias
d8e3eafa7d Fix female Hrothgar fucking up eqdp accessories 2022-05-11 20:57:08 +02:00
Ottermandias
ba0ef577c5 Random cleanup. 2022-05-11 20:56:18 +02:00
Ottermandias
08b7f184e6 Simplify retargeting after reload, seems to work fine. 2022-05-11 20:56:02 +02:00
Ottermandias
3ed85b56b5 Remove mentions of forced collection. 2022-05-11 20:55:44 +02:00
Ottermandias
d2f84aa976 Maybe fix face decals not correctly reloading in character collections. 2022-05-11 20:55:26 +02:00
Ottermandias
fc1255661c Fix collapsed folders with unfiltered entries not showing. 2022-05-11 20:55:04 +02:00
Ottermandias
54460c39f3 Add more edit options, some small fixes. 2022-05-03 18:16:06 +02:00
Ottermandias
65bbece9cf Rename Mod BasePath to ModPath, add simple Directory Renaming and Reloading, some fixes, Cleanup EditWindow. 2022-05-02 16:19:24 +02:00
Ottermandias
c416d044a4 Add material changing. 2022-05-02 12:53:53 +02:00
Ottermandias
e2a6274b33 Add empty option for single select groups with empty options. More Editor stuff. 2022-05-01 18:06:21 +02:00
Ottermandias
81e93e0664 Let options keep visual ordering. 2022-04-30 23:04:39 +02:00
Ottermandias
f24ec8ebe2 Change ImGui.Text to ImGui.TextUnformatted. 2022-04-30 18:54:17 +02:00
Ottermandias
5e46f43d7d Make extracting mods cancelable, some fixes. 2022-04-30 16:26:39 +02:00
Ottermandias
cf54bc7f57 Correspond Adventurer Plate Actor to name. 2022-04-30 00:47:06 +02:00
Ottermandias
7795f9a691 Fix crash on startup if collection dir does not exist. 2022-04-30 00:46:43 +02:00
Ottermandias
c390b57b0f Force IsSync in certain situations. 2022-04-29 21:22:54 +02:00
Ottermandias
c9c4447f3d Fix dumbness (all of it, please) 2022-04-29 17:07:35 +02:00
Ottermandias
15602f5be5 Fix some enabling stuff. Always use PathResolver. Add counter to materials and imcs. 2022-04-29 15:59:41 +02:00
Ottermandias
e8ee729ec5 Added a bunch of logging, small fix. 2022-04-29 15:30:08 +02:00
Ottermandias
7b0935750a
Update README.md 2022-04-29 12:18:11 +02:00
Ottermandias
9af4406c8c Fixes... 2022-04-28 17:04:08 +02:00
Ottermandias
c1859ccb24 Update OtterGui. 2022-04-28 16:27:18 +02:00
Ottermandias
2e1a11d16c Skip non-existing directories when migrating sort order. 2022-04-28 16:12:40 +02:00
Ottermandias
60cf7e3c2e Fix crashes on file selector, default mod creation for simple mods, default file edit button not working. 2022-04-28 16:03:01 +02:00
Ottermandias
545536f66f Add memory of last mod path as well as default directory. Add default Author. Fix bugs. 2022-04-27 23:31:56 +02:00
Ottermandias
c2a030aa6b Small fixes. 2022-04-27 18:48:37 +02:00
Ottermandias
4d6d73abb6 Add an import date property to mod metas 2022-04-27 18:34:44 +02:00
Ottermandias
fdc84836c9 Fix some migration and deletion stuff. 2022-04-27 18:25:56 +02:00
Ottermandias
a13fccb9ac Test: Update build scripts to checkout submodules 2022-04-27 17:42:48 +02:00
Ottermandias
e8a0ac98ad Small fixes. 2022-04-27 17:36:04 +02:00
Ottermandias
c78725d7d5 A few comments, further cleanup. A few TODOs handled. 2022-04-27 17:19:33 +02:00
Ottermandias
dbb9931189 A lot of interface stuff, some more cleanup and fixes. Main functionality should be mostly fine, importing works. Missing a lot of mod edit options. 2022-04-26 21:35:09 +02:00
Ottermandias
8dd681bdda Even almoster... 2022-04-20 11:03:19 +02:00
Ottermandias
65bd1d1b52 Almost there... 2022-04-18 16:14:13 +02:00
Ottermandias
f3b906007d Blep 2022-04-15 14:39:23 +02:00
Ottermandias
da73feacf4 tmp 2022-04-15 14:39:23 +02:00
Ottermandias
48e442a9fd Tmp for Mod2 2022-04-15 14:39:23 +02:00
Ottermandias
069ae772a5 Working on the selector. 2022-04-15 14:39:23 +02:00
Ottermandias
8db54ef4f4 temp 2022-04-15 14:39:23 +02:00
Ottermandias
33db156544 test 2022-04-15 14:39:23 +02:00
Ottermandias
c210a4f10a Add backup mechanism and some collection cleanup. 2022-04-15 14:39:23 +02:00
Ottermandias
d906e5aedf tmp 2022-04-15 14:39:23 +02:00
Ottermandias
a806dd28c3 Add OtterGui reference for shared interface code. 2022-04-15 14:39:23 +02:00
Ottermandias
5bfcb71f52 Start for Mod rework, currently not applied. 2022-04-15 14:39:23 +02:00
Ottermandias
1861c40a4f Complete mod collection cleanup, initial stuff for inheritance. Some further cleanup. 2022-04-15 14:39:23 +02:00
Ottermandias
7915d516e2 Imc Fixes. 2022-04-15 14:39:23 +02:00
Ottermandias
ac70f8db89 tmp2 2022-04-15 14:39:23 +02:00
Ottermandias
9a0b0bfa0f tmp 2022-04-15 14:39:23 +02:00
Ottermandias
bc47e08e08 Collection inheritance start. 2022-04-15 14:39:22 +02:00
Ottermandias
2877e9f22f Small debugging changes. 2022-04-15 14:39:22 +02:00
Ottermandias
9f6729dd0b Only enable PathResolver if any character collections are set, fix mtrl staying in PathCollections. 2022-04-15 14:39:22 +02:00
Ottermandias
1e5776a481 Change resolving to possibly work correctly for all materials and load specific materials for each collection. 2022-04-15 14:39:22 +02:00
Ottermandias
b6ed27e235 Another ObjectReloader fix. 2022-04-15 14:39:22 +02:00
Ottermandias
519543772c Turn Collections to List instead of Dict. 2022-04-15 14:39:22 +02:00
Ottermandias
9c0fc8a8c7 Move CollectionManager out of ModManager 2022-04-15 14:39:22 +02:00
Ottermandias
0eff4e2e67 tmp 2022-04-15 14:39:22 +02:00
Ottermandias
4a4d93baf3 Change moddata to list. 2022-04-15 14:39:22 +02:00
Ottermandias
2cece9c422 Fix EQP Entries not deserializing correctly due to C# json bug. 2022-04-15 14:39:22 +02:00
Ottermandias
2b0844a21e Fix EST file resource reallocation. 2022-04-15 14:39:22 +02:00
Ottermandias
efc21c7882 Some more Redraw changes. 2022-04-15 14:39:22 +02:00
Ottermandias
b08bf388cc Change Redrawing to be simpler and not use a queue or settings. 2022-04-15 14:39:22 +02:00
Ottermandias
7540694050 Fix IMC adding new variants. 2022-04-15 14:39:22 +02:00
Ottermandias
98b4b29ff5 Add debug display for ResidentResources. 2022-04-15 14:39:22 +02:00
Ottermandias
ad55d178d4 Support Weapons in character collections. 2022-04-15 14:39:22 +02:00
Ottermandias
3ef3e75c6a Removed ActiveCollection since it is no longer needed. 2022-04-15 14:39:22 +02:00
Ottermandias
4888bc243f Misc fixes 2022-04-15 14:39:22 +02:00
Ottermandias
d03a3168b0 Fix IMC handling. 2022-04-15 14:39:20 +02:00
Ottermandias
d07355c0f8 Fix behaviour for non-main-map resources in debug. 2022-04-15 14:38:41 +02:00
Ottermandias
6949011acf Fix for EQDP not working on redraws due to resetting itself early. 2022-04-15 14:38:40 +02:00
Ottermandias
c7344efdc2 Female Hrothgar resource handle related fixes... 2022-04-15 14:38:40 +02:00
Ottermandias
5ed80c753f File fixes. 2022-04-15 14:38:40 +02:00
Ottermandias
0ba0c6d057 Change handling of tex and shpk files to be simpler. 2022-04-15 14:38:40 +02:00
Ottermandias
e6752ade04 More fixes, some cleanup. 2022-04-15 14:38:40 +02:00
Ottermandias
581b91b337 Fixes. 2022-04-15 14:38:40 +02:00
Ottermandias
8d2e84eecf Some object reloading changes. 2022-04-15 14:38:38 +02:00
Ottermandias
e7282384f5 Working on PathResolver 2022-04-15 14:37:39 +02:00
Ottermandias
6f527a1dbc Metamanipulations seemingly working. 2022-04-15 14:37:39 +02:00
Ottermandias
707570615c tmp2 2022-04-15 14:37:39 +02:00
Ottermandias
de082439a4 tmp 2022-04-15 14:37:39 +02:00
Ottermandias
46581780e0 tmp 2022-04-15 14:37:39 +02:00
Ottermandias
f5fccb0235 Change most things to new byte strings, introduce new ResourceLoader and Logger fully. 2022-04-15 14:37:39 +02:00
Ottermandias
5d77cd5514 Add function to replace all skin materials. 2022-04-15 14:37:39 +02:00
Ottermandias
c3454f1d16 Add Byte String stuff, remove Services, cleanup and refactor interop stuff, disable path resolver for the moment 2022-04-15 14:37:39 +02:00
Ottermandias
0e8f839471 tmp 2022-04-15 14:37:38 +02:00
Ottermandias
e15d844d4b Start 2022-04-15 14:37:38 +02:00
Actions User
1658102c34 [CI] Updating repo.json for refs/tags/0.4.8.2 2022-04-15 11:19:04 +00:00
Ottermandias
9130932a7f Fix VFunc index for GPose Redraw. 2022-04-15 13:17:19 +02:00
Actions User
53d19ff473 [CI] Updating repo.json for refs/tags/0.4.8.1 2022-04-14 14:44:24 +00:00
Ottermandias
1ee7a7cbf2 Use net5 build 2022-04-14 16:42:51 +02:00
Ottermandias
8865ff5e79 Fix 6.1 offsets. 2022-04-14 16:01:06 +02:00
Actions User
18384a9386 [CI] Updating repo.json for refs/tags/0.4.8.0 2022-03-20 12:07:45 +00:00
Ottermandias
6d3a4f08c5 Change HTTP API help text. 2022-03-20 13:04:37 +01:00
Ottermandias
c8293c9a6b Fix handling of weird TTMP files. 2022-03-20 13:02:49 +01:00
Ottermandias
4a9b08de98
Merge pull request #171 from Yuki-Walsh/master
Add redraw route to http api
2022-03-20 12:21:26 +01:00
Yuki
6fa79c62c4 Add redraw route to http api 2022-03-20 16:16:27 +11:00
Ottermandias
bf40c2a3cb Add option to expand or collapse all sub-folders in the right-click context for mod folders. 2022-03-08 10:09:34 +01:00
Ottermandias
6df82fdf18 Make recapitalizing internal mod folders possible without changing behaviour otherwise. 2022-02-24 14:02:45 +01:00
Ottermandias
7e7e74a534 Make renaming folders case-sensitive? 2022-02-22 22:05:39 +01:00
Ottermandias
689a4c73d9 Small improvements. 2022-02-22 22:05:39 +01:00
Actions User
564a4195cb [CI] Updating repo.json for refs/tags/0.4.7.7 2022-02-21 18:50:13 +00:00
Ottermandias
733b60faae Fix material change in mdls another time, this time with actual .mdl parsing and writing. 2022-02-21 19:47:58 +01:00
Actions User
7beee50fdf [CI] Updating repo.json for refs/tags/0.4.7.6 2022-02-20 00:11:42 +00:00
Ottermandias
f4f3a4dfc1 Fix material change in mdls for different suffix sizes., next try. 2022-02-20 01:09:37 +01:00
Actions User
38ad1eef96 [CI] Updating repo.json for refs/tags/0.4.7.5 2022-02-19 17:34:08 +00:00
Ottermandias
3d6d3ed2d6 Fix material change in mdls for different suffix sizes. 2022-02-19 18:31:58 +01:00
Ottermandias
25b65ce628 Fix redraw not working with names anymore. 2022-02-19 18:31:58 +01:00
Actions User
a9065d97d3 [CI] Updating repo.json for refs/tags/0.4.7.4 2022-02-16 20:40:42 +00:00
Ottermandias
aa180dcdf6 Change Skin Material Replacement to accept arbitrary suffix-strings for From and To. 2022-02-16 15:00:50 +01:00
Ottermandias
947e40b1eb Remove ClientStructs Initialize 2022-02-16 11:50:43 +01:00
Ottermandias
26258b7bcc Merge branch 'pr/n143_collection-command' 2022-02-03 23:53:41 +01:00
Ottermandias
09c92ef0b1 Small fixes. 2022-02-03 23:53:27 +01:00
Lucy Awrey
a62fa06b03 Implemented command for changing the current default and forced collections 2022-02-03 17:02:03 -05:00
Actions User
1d5ddb0590 [CI] Updating repo.json for refs/tags/0.4.7.3 2022-01-31 21:40:38 +00:00
Ottermandias
b6a7a59f92 Merge branch 'pr/n138_tsm' 2022-01-31 22:38:38 +01:00
Ottermandias
685772e6ac Slight modifications. 2022-01-31 22:38:28 +01:00
goaaats
b6817c47ed
Replace manage mods button with title screen menu 2022-01-31 22:08:33 +01:00
Actions User
40bb7567dd [CI] Updating repo.json for refs/tags/0.4.7.2 2022-01-29 18:15:24 +00:00
Ottermandias
75823d413e Allow changing skin materials in .mdls in edit mode. 2022-01-29 19:13:12 +01:00
Actions User
076be3925c [CI] Updating repo.json for refs/tags/0.4.7.1 2022-01-28 11:38:53 +00:00
Ottermandias
e18fcafc51 Add option to disable disabling sound streaming. 2022-01-28 12:36:27 +01:00
Ottermandias
ac2f2cf3b9
Merge pull request #124 from reiichi001/patch-1
Adjust the Installing section of README.md
2022-01-18 18:00:24 +01:00
Franz Renatus
7f60d47c0e
Adjust the Installing section of README.md
While a bit more hand-holdy, this should cut down on the number of people asking how to add the custom repo URL and dissuade people from thing to manually install the plugin as there are multiple users who were getting confused by the prior terminology used. (Like making assumptions that adding the URL was all they needed to do)
2022-01-18 08:57:30 -08:00
Ottermandias
203550f58d
Merge pull request #113 from Azurealistic/master
Update README.md
2022-01-11 11:36:11 +01:00
Muhammad Asavir
e435ec5893
Update README.md
Typo fix.
2022-01-10 18:12:26 -05:00
Muhammad Asavir
7495b3b10b
Merge pull request #1 from Azurealistic/readme-repo-update
Update README.md
2022-01-09 23:35:31 -05:00
Muhammad Asavir
6b509d66cb
Update README.md
Add the custom plugin repo URL to the readme to make it more obvious and deter people from installing manually.
2022-01-09 23:35:03 -05:00
Actions User
62459c058d [CI] Updating repo.json for refs/tags/0.4.7.0 2022-01-08 14:17:08 +00:00
Ottermandias
84fd0262c2 Fix metadata bug. 2022-01-08 15:15:23 +01:00
Actions User
c542c79923 [CI] Updating repo.json for refs/tags/0.4.6.9 2022-01-08 12:41:06 +00:00
Ottermandias
7f9ca5db76 Add a bunch of help texts and expand on information. 2022-01-08 13:36:47 +01:00
Actions User
3e5ea0d89c [CI] Updating repo.json for refs/tags/0.4.6.8 2022-01-06 10:50:49 +00:00
Ottermandias
aa7d71530d Change EST files to be sorted and thus work. 2022-01-06 11:47:01 +01:00
Actions User
19b295bbc3 [CI] Updating repo.json for refs/tags/0.4.6.7 2022-01-04 11:36:41 +00:00
Ottermandias
f601812666 More sophisticated fix against E4S crashes with working mods in E4S. 2022-01-04 00:30:27 +01:00
Actions User
a1f02975cb [CI] Updating repo.json for refs/tags/0.4.6.6 2022-01-03 10:21:30 +00:00
Ottermandias
59fa4c4fe4 Temporary fix for E4S crashes. 2022-01-03 11:15:22 +01:00
Actions User
0823423eda [CI] Updating repo.json for refs/tags/0.4.6.5 2021-12-13 22:46:42 +00:00
Ottermandias
8064376d3d Further tries to fix. 2021-12-13 23:43:01 +01:00
Actions User
7b5e5f6815 [CI] Updating repo.json for refs/tags/0.4.6.4 2021-12-13 16:34:35 +00:00
Ottermandias
cd1faf5860 Fix empty collection building cache after last change. 2021-12-13 17:31:17 +01:00
Actions User
ece034cafc [CI] Updating repo.json for refs/tags/0.4.6.3 2021-12-13 12:20:41 +00:00
Ottermandias
12d483e010 Fix collections not correctly building on first launch. 2021-12-13 13:16:56 +01:00
Actions User
2c22e37666 [CI] Updating repo.json for refs/tags/0.4.6.2 2021-12-10 15:57:20 +00:00
Ottermandias
3458a2920b Add Miera (and Hrothgals) to enums. 2021-12-10 16:54:59 +01:00
Ottermandias
dbcd847736 Fix GPose Redrawing 2021-12-10 16:54:36 +01:00
Actions User
393859232d [CI] Updating repo.json for refs/tags/0.4.6.1 2021-12-10 09:51:32 +00:00
Ottermandias
f66e08431c Increase Base Repo API 2021-12-10 10:49:37 +01:00
Actions User
63268512db [CI] Updating repo.json for refs/tags/0.4.6.0 2021-12-10 09:26:14 +00:00
Ottermandias
1d254e5856 Update for 6.0 2021-12-10 10:24:06 +01:00
Actions User
3bcd7a44da [CI] Updating repo.json for refs/tags/0.4.5.0 2021-11-29 16:07:41 +00:00
Ottermandias
fa12682da6 Increase used dotnet-version. 2021-11-29 17:04:10 +01:00
Ottermandias
0e552d24a6 Slight modifications to button offset PR. 2021-11-29 16:41:31 +01:00
Ottermandias
5802f6b6d7 Merge branch 'pr/n79_master' 2021-11-29 16:24:33 +01:00
Eternita-S
f79405e656 Title screen menu button offset 2021-11-29 18:17:02 +03:00
Ottermandias
3128c2017d Prevent some crashes when default values can't be obtained. 2021-11-16 16:06:38 +01:00
Ottermandias
87e8f2599c Remove currently unused window even from debug... 2021-11-16 16:06:16 +01:00
Ottermandias
88a1e9f2ae Add settings cleanup to Auto-Generate Groups. 2021-11-16 16:05:58 +01:00
Ottermandias
cd74a41453 Remove checking for available mods in the mod panel so you can create empty mods from the start. 2021-11-16 16:05:03 +01:00
Ottermandias
ba2ffcc790 Some more information in ResourceManager 2021-11-16 16:04:11 +01:00
Ottermandias
743f83d12e Add keeping track of seen players to player watcher. 2021-11-16 16:03:49 +01:00
Ottermandias
906e057943 Signify freshly added mods in the mod-selector. 2021-10-21 15:12:47 +02:00
Ottermandias
e488506cde Only compute changed items when necessary. 2021-10-21 15:09:26 +02:00
Ottermandias
702476f43c Fix folder name 2021-10-19 18:58:23 +02:00
Ottermandias
2b0d55bc69 Fix scrolling in Changed Items tab. 2021-10-19 18:53:20 +02:00
Ottermandias
572f4f5e6b Add filters to the effective files tab. 2021-10-19 18:47:17 +02:00
Ottermandias
0c0eeec158 Add some methods and interface to view currently Changed Items. 2021-10-19 18:45:22 +02:00
Ottermandias
982385ccbb Create enable/disable functions and clean up PR. 2021-10-19 11:30:08 +02:00
Ottermandias
5084bf603c Merge branch 'pr/n65_enable-disable-commands' 2021-10-19 11:08:55 +02:00
Brenden Reeves
b901c9e744 Added Enable and Disable Commands 2021-10-18 21:44:55 -05:00
Actions User
78bef3dec0 [CI] Updating repo.json for refs/tags/0.4.4.9 2021-10-18 14:14:23 +00:00
Ottermandias
4a82e6faf1 Add unsolved conflicts to both mods, add button to auto-generate groups based on folders, misc. fixes. 2021-10-18 16:12:32 +02:00
Ottermandias
795d605d3f Rename eqp entry 14 to DisableBreastPhysics. 2021-10-18 16:09:13 +02:00
Ottermandias
39fbf128c2 Change handling of setting root or temp path to accept button clicks. 2021-10-18 16:08:43 +02:00
Actions User
8ee15e4ea2 [CI] Updating repo.json for refs/tags/0.4.4.8 2021-10-12 11:07:30 +00:00
Ottermandias
be404c79b3 Allow Retainers to be watched with PlayerWatcher. 2021-10-12 13:05:47 +02:00
Actions User
0c135b574b [CI] Updating repo.json for refs/tags/0.4.4.7 2021-10-11 09:52:42 +00:00
Ottermandias
b6304d43db Update PlayerWatcher to deal with multiple actors with the same name. 2021-10-11 11:50:59 +02:00
Actions User
0b8a3d2d11 [CI] Updating repo.json for refs/tags/0.4.4.6 2021-10-10 13:53:09 +00:00
669 changed files with 84482 additions and 18680 deletions

File diff suppressed because it is too large Load diff

View file

@ -10,11 +10,15 @@ jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
- uses: actions/checkout@v5
with:
dotnet-version: 5.0.100
submodules: recursive
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
10.x.x
9.x.x
- name: Restore dependencies
run: dotnet restore
- name: Download Dalamud
@ -25,9 +29,9 @@ jobs:
run: |
dotnet build --no-restore --configuration Release --nologo
- name: Archive
run: Compress-Archive -Path Penumbra/bin/Release/net5.0-windows/* -DestinationPath Penumbra.zip
run: Compress-Archive -Path Penumbra/bin/Release/* -DestinationPath Penumbra.zip
- name: Upload a Build Artifact
uses: actions/upload-artifact@v2.2.1
uses: actions/upload-artifact@v4
with:
path: |
./Penumbra/bin/Release/net5.0-windows/*
./Penumbra/bin/Release/*

View file

@ -2,18 +2,22 @@ name: Create Release
on:
push:
tags:
- '*'
tags-ignore:
- testing_*
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
- uses: actions/checkout@v5
with:
dotnet-version: 5.0.100
submodules: recursive
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
10.x.x
9.x.x
- name: Restore dependencies
run: dotnet restore
- name: Download Dalamud
@ -22,22 +26,23 @@ jobs:
Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev"
- name: Build
run: |
$ver = '${{ github.ref }}' -replace 'refs/tags/',''
invoke-expression 'dotnet build --no-restore --configuration Release --nologo -p:Version=$ver -p:FileVersion=$ver'
- name: write version into json
$ver = '${{ github.ref_name }}'
invoke-expression 'dotnet build --no-restore --configuration Release --nologo -p:Version=$ver -p:FileVersion=$ver -p:AssemblyVersion=$ver'
- name: write version into jsons
run: |
$ver = '${{ github.ref }}' -replace 'refs/tags/',''
$path = './Penumbra/bin/Release/net5.0-windows/Penumbra.json'
$content = get-content -path $path
$content = $content -replace '1.0.0.0',$ver
$ver = '${{ github.ref_name }}'
$path = './Penumbra/bin/Release/Penumbra.json'
$json = Get-Content -Raw $path | ConvertFrom-Json
$json.AssemblyVersion = $ver
$content = $json | ConvertTo-Json
set-content -Path $path -Value $content
- name: Archive
run: Compress-Archive -Path Penumbra/bin/Release/net5.0-windows/* -DestinationPath Penumbra.zip
run: Compress-Archive -Path Penumbra/bin/Release/* -DestinationPath Penumbra.zip
- name: Upload a Build Artifact
uses: actions/upload-artifact@v2.2.1
uses: actions/upload-artifact@v4
with:
path: |
./Penumbra/bin/Release/net5.0-windows/*
./Penumbra/bin/Release/*
- name: Create Release
id: create_release
uses: actions/create-release@v1
@ -61,20 +66,24 @@ jobs:
- name: Write out repo.json
run: |
$ver = '${{ github.ref }}' -replace 'refs/tags/',''
$path = './base_repo.json'
$new_path = './repo.json'
$content = get-content -path $path
$content = $content -replace '1.0.0.0',$ver
set-content -Path $new_path -Value $content
$ver = '${{ github.ref_name }}'
$path = './repo.json'
$json = Get-Content -Raw $path | ConvertFrom-Json
$json[0].AssemblyVersion = $ver
$json[0].TestingAssemblyVersion = $ver
$json[0].DownloadLinkInstall = $json.DownloadLinkInstall -replace '[^/]+/Penumbra.zip',"$ver/Penumbra.zip"
$json[0].DownloadLinkTesting = $json.DownloadLinkTesting -replace '[^/]+/Penumbra.zip',"$ver/Penumbra.zip"
$json[0].DownloadLinkUpdate = $json.DownloadLinkUpdate -replace '[^/]+/Penumbra.zip',"$ver/Penumbra.zip"
$content = $json | ConvertTo-Json -AsArray
set-content -Path $path -Value $content
- name: Commit repo.json
run: |
git config --global user.name "Actions User"
git config --global user.email "actions@github.com"
git fetch origin master && git checkout master
git fetch origin master
git branch -f master ${{ github.sha }}
git checkout master
git add repo.json
git commit -m "[CI] Updating repo.json for ${{ github.ref }}" || true
git push origin master || true
git commit -m "[CI] Updating repo.json for ${{ github.ref_name }}" || true
git push origin master

87
.github/workflows/test_release.yml vendored Normal file
View file

@ -0,0 +1,87 @@
name: Create Test Release
on:
push:
tags:
- testing_*
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v5
with:
submodules: recursive
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
10.x.x
9.x.x
- name: Restore dependencies
run: dotnet restore
- name: Download Dalamud
run: |
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: |
$ver = '${{ github.ref_name }}' -replace 'testing_'
invoke-expression 'dotnet build --no-restore --configuration Debug --nologo -p:Version=$ver -p:FileVersion=$ver -p:AssemblyVersion=$ver'
- name: write version into json
run: |
$ver = '${{ github.ref_name }}' -replace 'testing_'
$path = './Penumbra/bin/Debug/Penumbra.json'
$json = Get-Content -Raw $path | ConvertFrom-Json
$json.AssemblyVersion = $ver
$content = $json | ConvertTo-Json
set-content -Path $path -Value $content
- name: Archive
run: Compress-Archive -Path Penumbra/bin/Debug/* -DestinationPath Penumbra.zip
- name: Upload a Build Artifact
uses: actions/upload-artifact@v4
with:
path: |
./Penumbra/bin/Debug/*
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Penumbra ${{ github.ref }}
draft: false
prerelease: false
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: ./Penumbra.zip
asset_name: Penumbra.zip
asset_content_type: application/zip
- name: Write out repo.json
run: |
$verT = '${{ github.ref_name }}'
$ver = $verT -replace 'testing_'
$path = './repo.json'
$json = Get-Content -Raw $path | ConvertFrom-Json
$json[0].TestingAssemblyVersion = $ver
$json[0].DownloadLinkTesting = $json.DownloadLinkTesting -replace '[^/]+/Penumbra.zip',"$verT/Penumbra.zip"
$content = $json | ConvertTo-Json -AsArray
set-content -Path $path -Value $content
- name: Commit repo.json
run: |
git config --global user.name "Actions User"
git config --global user.email "actions@github.com"
git fetch origin master
git branch -f master ${{ github.sha }}
git checkout master
git add repo.json
git commit -m "[CI] Updating repo.json for ${{ github.ref_name }}" || true
git push origin master

16
.gitmodules vendored Normal file
View file

@ -0,0 +1,16 @@
[submodule "OtterGui"]
path = OtterGui
url = https://github.com/Ottermandias/OtterGui.git
branch = main
[submodule "Penumbra.Api"]
path = Penumbra.Api
url = https://github.com/Ottermandias/Penumbra.Api.git
branch = main
[submodule "Penumbra.String"]
path = Penumbra.String
url = https://github.com/Ottermandias/Penumbra.String.git
branch = main
[submodule "Penumbra.GameData"]
path = Penumbra.GameData
url = https://github.com/Ottermandias/Penumbra.GameData.git
branch = main

1
OtterGui Submodule

@ -0,0 +1 @@
Subproject commit ff1e6543845e3b8c53a5f8b240bc38faffb1b3bf

1
Penumbra.Api Submodule

@ -0,0 +1 @@
Subproject commit 52a3216a525592205198303df2844435e382cf87

View file

@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Nodes;
namespace Penumbra.CrashHandler.Buffers;
/// <summary> The types of currently hooked and relevant animation loading functions. </summary>
public enum AnimationInvocationType : int
{
PapLoad,
ActionLoad,
ScheduleClipUpdate,
LoadTimelineResources,
LoadCharacterVfx,
LoadCharacterSound,
ApricotSoundPlay,
LoadAreaVfx,
CharacterBaseLoadAnimation,
}
/// <summary> The full crash entry for an invoked vfx function. </summary>
public record struct VfxFuncInvokedEntry(
double Age,
DateTimeOffset Timestamp,
int ThreadId,
string InvocationType,
string CharacterName,
string CharacterAddress,
Guid CollectionId) : ICrashDataEntry;
/// <summary> Only expose the write interface for the buffer. </summary>
public interface IAnimationInvocationBufferWriter
{
/// <summary> Write a line into the buffer with the given data. </summary>
/// <param name="characterAddress"> The address of the related character, if known. </param>
/// <param name="characterName"> The name of the related character, anonymized or relying on index if unavailable, if known. </param>
/// <param name="collectionId"> The GUID of the associated collection. </param>
/// <param name="type"> The type of VFX func called. </param>
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, Guid collectionId, AnimationInvocationType type);
}
internal sealed class AnimationInvocationBuffer : MemoryMappedBuffer, IAnimationInvocationBufferWriter, IBufferReader
{
private const int _version = 1;
private const int _lineCount = 64;
private const int _lineCapacity = 128;
private const string _name = "Penumbra.AnimationInvocation";
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, Guid collectionId, AnimationInvocationType type)
{
var accessor = GetCurrentLineLocking();
lock (accessor)
{
accessor.Write(0, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
accessor.Write(8, Environment.CurrentManagedThreadId);
accessor.Write(12, (int)type);
accessor.Write(16, characterAddress);
var span = GetSpan(accessor, 24, 16);
collectionId.TryWriteBytes(span);
accessor.SafeMemoryMappedViewHandle.ReleasePointer();
span = GetSpan(accessor, 40);
WriteSpan(characterName, span);
accessor.SafeMemoryMappedViewHandle.ReleasePointer();
}
}
public uint TotalCount
=> TotalWrittenLines;
public IEnumerable<JsonObject> GetLines(DateTimeOffset crashTime)
{
var lineCount = (int)CurrentLineCount;
for (var i = lineCount - 1; i >= 0; --i)
{
var line = GetLine(i);
var timestamp = DateTimeOffset.FromUnixTimeMilliseconds(BitConverter.ToInt64(line));
var thread = BitConverter.ToInt32(line[8..]);
var type = (AnimationInvocationType)BitConverter.ToInt32(line[12..]);
var address = BitConverter.ToUInt64(line[16..]);
var collectionId = new Guid(line[24..40]);
var characterName = ReadString(line[40..]);
yield return new JsonObject()
{
[nameof(VfxFuncInvokedEntry.Age)] = (crashTime - timestamp).TotalSeconds,
[nameof(VfxFuncInvokedEntry.Timestamp)] = timestamp,
[nameof(VfxFuncInvokedEntry.ThreadId)] = thread,
[nameof(VfxFuncInvokedEntry.InvocationType)] = ToName(type),
[nameof(VfxFuncInvokedEntry.CharacterName)] = characterName,
[nameof(VfxFuncInvokedEntry.CharacterAddress)] = address.ToString("X"),
[nameof(VfxFuncInvokedEntry.CollectionId)] = collectionId,
};
}
}
public static IBufferReader CreateReader(int pid)
=> new AnimationInvocationBuffer(false, pid);
public static IAnimationInvocationBufferWriter CreateWriter(int pid)
=> new AnimationInvocationBuffer(pid);
private AnimationInvocationBuffer(bool writer, int pid)
: base($"{_name}_{pid}_{_version}", _version)
{ }
private AnimationInvocationBuffer(int pid)
: base($"{_name}_{pid}_{_version}", _version, _lineCount, _lineCapacity)
{ }
private static string ToName(AnimationInvocationType type)
=> type switch
{
AnimationInvocationType.PapLoad => "PAP Load",
AnimationInvocationType.ActionLoad => "Action Load",
AnimationInvocationType.ScheduleClipUpdate => "Schedule Clip Update",
AnimationInvocationType.LoadTimelineResources => "Load Timeline Resources",
AnimationInvocationType.LoadCharacterVfx => "Load Character VFX",
AnimationInvocationType.LoadCharacterSound => "Load Character Sound",
AnimationInvocationType.ApricotSoundPlay => "Apricot Sound Play",
AnimationInvocationType.LoadAreaVfx => "Load Area VFX",
AnimationInvocationType.CharacterBaseLoadAnimation => "Load Animation (CharacterBase)",
_ => $"Unknown ({(int)type})",
};
}

View file

@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Nodes;
namespace Penumbra.CrashHandler.Buffers;
/// <summary> Only expose the write interface for the buffer. </summary>
public interface ICharacterBaseBufferWriter
{
/// <summary> Write a line into the buffer with the given data. </summary>
/// <param name="characterAddress"> The address of the related character, if known. </param>
/// <param name="characterName"> The name of the related character, anonymized or relying on index if unavailable, if known. </param>
/// <param name="collectionId"> The GUID of the associated collection. </param>
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, Guid collectionId);
}
/// <summary> The full crash entry for a loaded character base. </summary>
public record struct CharacterLoadedEntry(
double Age,
DateTimeOffset Timestamp,
int ThreadId,
string CharacterName,
string CharacterAddress,
Guid CollectionId) : ICrashDataEntry;
internal sealed class CharacterBaseBuffer : MemoryMappedBuffer, ICharacterBaseBufferWriter, IBufferReader
{
private const int _version = 1;
private const int _lineCount = 10;
private const int _lineCapacity = 128;
private const string _name = "Penumbra.CharacterBase";
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, Guid collectionId)
{
var accessor = GetCurrentLineLocking();
lock (accessor)
{
accessor.Write(0, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
accessor.Write(8, Environment.CurrentManagedThreadId);
accessor.Write(12, characterAddress);
var span = GetSpan(accessor, 20, 16);
collectionId.TryWriteBytes(span);
accessor.SafeMemoryMappedViewHandle.ReleasePointer();
span = GetSpan(accessor, 36);
WriteSpan(characterName, span);
accessor.SafeMemoryMappedViewHandle.ReleasePointer();
}
}
public IEnumerable<JsonObject> GetLines(DateTimeOffset crashTime)
{
var lineCount = (int)CurrentLineCount;
for (var i = lineCount - 1; i >= 0; --i)
{
var line = GetLine(i);
var timestamp = DateTimeOffset.FromUnixTimeMilliseconds(BitConverter.ToInt64(line));
var thread = BitConverter.ToInt32(line[8..]);
var address = BitConverter.ToUInt64(line[12..]);
var collectionId = new Guid(line[20..36]);
var characterName = ReadString(line[36..]);
yield return new JsonObject
{
[nameof(CharacterLoadedEntry.Age)] = (crashTime - timestamp).TotalSeconds,
[nameof(CharacterLoadedEntry.Timestamp)] = timestamp,
[nameof(CharacterLoadedEntry.ThreadId)] = thread,
[nameof(CharacterLoadedEntry.CharacterName)] = characterName,
[nameof(CharacterLoadedEntry.CharacterAddress)] = address.ToString("X"),
[nameof(CharacterLoadedEntry.CollectionId)] = collectionId,
};
}
}
public uint TotalCount
=> TotalWrittenLines;
public static IBufferReader CreateReader(int pid)
=> new CharacterBaseBuffer(false, pid);
public static ICharacterBaseBufferWriter CreateWriter(int pid)
=> new CharacterBaseBuffer(pid);
private CharacterBaseBuffer(bool writer, int pid)
: base($"{_name}_{pid}_{_version}", _version)
{ }
private CharacterBaseBuffer(int pid)
: base($"{_name}_{pid}_{_version}", _version, _lineCount, _lineCapacity)
{ }
}

View file

@ -0,0 +1,220 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Numerics;
using System.Text;
namespace Penumbra.CrashHandler.Buffers;
public class MemoryMappedBuffer : IDisposable
{
private const int MinHeaderLength = 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4;
private readonly MemoryMappedFile _file;
private readonly MemoryMappedViewAccessor _header;
private readonly MemoryMappedViewAccessor[] _lines = [];
public readonly int Version;
public readonly uint LineCount;
public readonly uint LineCapacity;
private readonly uint _lineMask;
private bool _disposed;
protected uint CurrentLineCount
{
get => _header.ReadUInt32(16);
set => _header.Write(16, value);
}
protected uint CurrentLinePosition
{
get => _header.ReadUInt32(20);
set => _header.Write(20, value);
}
public uint TotalWrittenLines
{
get => _header.ReadUInt32(24);
protected set => _header.Write(24, value);
}
public MemoryMappedBuffer(string mapName, int version, uint lineCount, uint lineCapacity)
{
Version = version;
LineCount = BitOperations.RoundUpToPowerOf2(Math.Clamp(lineCount, 2, int.MaxValue >> 3));
LineCapacity = BitOperations.RoundUpToPowerOf2(Math.Clamp(lineCapacity, 2, int.MaxValue >> 3));
_lineMask = LineCount - 1;
var fileName = Encoding.UTF8.GetBytes(mapName);
var headerLength = (uint)(4 + 4 + 4 + 4 + 4 + 4 + 4 + fileName.Length + 1);
headerLength = (headerLength & 0b111) > 0 ? (headerLength & ~0b111u) + 0b1000 : headerLength;
var capacity = LineCount * LineCapacity + headerLength;
_file = MemoryMappedFile.CreateNew(mapName, capacity, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.None,
HandleInheritability.Inheritable);
_header = _file.CreateViewAccessor(0, headerLength);
_header.Write(0, headerLength);
_header.Write(4, Version);
_header.Write(8, LineCount);
_header.Write(12, LineCapacity);
_header.WriteArray(28, fileName, 0, fileName.Length);
_header.Write(fileName.Length + 28, (byte)0);
_lines = Enumerable.Range(0, (int)LineCount).Select(i
=> _file.CreateViewAccessor(headerLength + i * LineCapacity, LineCapacity, MemoryMappedFileAccess.ReadWrite))
.ToArray();
}
public MemoryMappedBuffer(string mapName, int? expectedVersion = null, uint? expectedMinLineCount = null,
uint? expectedMinLineCapacity = null)
{
_lines = [];
_file = MemoryMappedFile.OpenExisting(mapName, MemoryMappedFileRights.ReadWrite, HandleInheritability.Inheritable);
using var headerLine = _file.CreateViewAccessor(0, 4, MemoryMappedFileAccess.Read);
var headerLength = headerLine.ReadUInt32(0);
if (headerLength < MinHeaderLength)
Throw($"Map {mapName} did not contain a valid header.");
_header = _file.CreateViewAccessor(0, headerLength, MemoryMappedFileAccess.ReadWrite);
Version = _header.ReadInt32(4);
LineCount = _header.ReadUInt32(8);
LineCapacity = _header.ReadUInt32(12);
_lineMask = LineCount - 1;
if (expectedVersion.HasValue && expectedVersion.Value != Version)
Throw($"Map {mapName} has version {Version} instead of {expectedVersion.Value}.");
if (LineCount < expectedMinLineCount)
Throw($"Map {mapName} has line count {LineCount} but line count >= {expectedMinLineCount.Value} is required.");
if (LineCapacity < expectedMinLineCapacity)
Throw($"Map {mapName} has line capacity {LineCapacity} but line capacity >= {expectedMinLineCapacity.Value} is required.");
var name = ReadString(GetSpan(_header, 28));
if (name != mapName)
Throw($"Map {mapName} does not contain its map name at the expected location.");
_lines = Enumerable.Range(0, (int)LineCount).Select(i
=> _file.CreateViewAccessor(headerLength + i * LineCapacity, LineCapacity, MemoryMappedFileAccess.ReadWrite))
.ToArray();
[DoesNotReturn]
void Throw(string text)
{
_file.Dispose();
_header?.Dispose();
_disposed = true;
throw new Exception(text);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
_disposed = true;
}
protected static string ReadString(Span<byte> span)
{
if (span.IsEmpty)
throw new Exception("String from empty span requested.");
var termination = span.IndexOf((byte)0);
if (termination < 0)
throw new Exception("String in span is not terminated.");
return Encoding.UTF8.GetString(span[..termination]);
}
protected static int WriteString(string text, Span<byte> span)
{
var bytes = Encoding.UTF8.GetBytes(text);
var source = (Span<byte>)bytes;
var length = source.Length + 1;
if (length > span.Length)
source = source[..(span.Length - 1)];
source.CopyTo(span);
span[bytes.Length] = 0;
return source.Length + 1;
}
protected static int WriteSpan(ReadOnlySpan<byte> input, Span<byte> span)
{
var length = input.Length + 1;
if (length > span.Length)
input = input[..(span.Length - 1)];
input.CopyTo(span);
span[input.Length] = 0;
return input.Length + 1;
}
protected Span<byte> GetLine(int i)
{
if (i < 0 || i > LineCount)
return null;
lock (_header)
{
var lineIdx = (CurrentLinePosition + i) & _lineMask;
if (lineIdx > CurrentLineCount)
return null;
return GetSpan(_lines[lineIdx]);
}
}
protected MemoryMappedViewAccessor GetCurrentLineLocking()
{
MemoryMappedViewAccessor view;
lock (_header)
{
var currentLineCount = CurrentLineCount;
if (currentLineCount == LineCount)
{
var currentLinePos = CurrentLinePosition;
view = _lines[currentLinePos]!;
CurrentLinePosition = (currentLinePos + 1) & _lineMask;
}
else
{
view = _lines[currentLineCount];
++CurrentLineCount;
}
++TotalWrittenLines;
_header.Flush();
}
return view;
}
protected static Span<byte> GetSpan(MemoryMappedViewAccessor accessor, int offset = 0)
=> GetSpan(accessor, offset, (int)accessor.Capacity - offset);
protected static unsafe Span<byte> GetSpan(MemoryMappedViewAccessor accessor, int offset, int size)
{
byte* ptr = null;
accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
size = Math.Min(size, (int)accessor.Capacity - offset);
if (size < 0)
return [];
var span = new Span<byte>(ptr + offset + accessor.PointerOffset, size);
return span;
}
protected void Dispose(bool disposing)
{
if (_disposed)
return;
_header?.Dispose();
foreach (var line in _lines)
line?.Dispose();
_file?.Dispose();
}
~MemoryMappedBuffer()
=> Dispose(false);
}

View file

@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Nodes;
namespace Penumbra.CrashHandler.Buffers;
/// <summary> Only expose the write interface for the buffer. </summary>
public interface IModdedFileBufferWriter
{
/// <summary> Write a line into the buffer with the given data. </summary>
/// <param name="characterAddress"> The address of the related character, if known. </param>
/// <param name="characterName"> The name of the related character, anonymized or relying on index if unavailable, if known. </param>
/// <param name="collectionId"> The GUID of the associated collection. </param>
/// <param name="requestedFileName"> The file name as requested by the game. </param>
/// <param name="actualFileName"> The actual modded file name loaded. </param>
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, Guid collectionId, ReadOnlySpan<byte> requestedFileName,
ReadOnlySpan<byte> actualFileName);
}
/// <summary> The full crash entry for a loaded modded file. </summary>
public record struct ModdedFileLoadedEntry(
double Age,
DateTimeOffset Timestamp,
int ThreadId,
string CharacterName,
string CharacterAddress,
Guid CollectionId,
string RequestedFileName,
string ActualFileName) : ICrashDataEntry;
internal sealed class ModdedFileBuffer : MemoryMappedBuffer, IModdedFileBufferWriter, IBufferReader
{
private const int _version = 1;
private const int _lineCount = 128;
private const int _lineCapacity = 1024;
private const string _name = "Penumbra.ModdedFile";
public void WriteLine(nint characterAddress, ReadOnlySpan<byte> characterName, Guid collectionId, ReadOnlySpan<byte> requestedFileName,
ReadOnlySpan<byte> actualFileName)
{
var accessor = GetCurrentLineLocking();
lock (accessor)
{
accessor.Write(0, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
accessor.Write(8, Environment.CurrentManagedThreadId);
accessor.Write(12, characterAddress);
var span = GetSpan(accessor, 20, 16);
collectionId.TryWriteBytes(span);
accessor.SafeMemoryMappedViewHandle.ReleasePointer();
span = GetSpan(accessor, 36, 80);
WriteSpan(characterName, span);
accessor.SafeMemoryMappedViewHandle.ReleasePointer();
span = GetSpan(accessor, 116, 260);
WriteSpan(requestedFileName, span);
accessor.SafeMemoryMappedViewHandle.ReleasePointer();
span = GetSpan(accessor, 376);
WriteSpan(actualFileName, span);
accessor.SafeMemoryMappedViewHandle.ReleasePointer();
}
}
public uint TotalCount
=> TotalWrittenLines;
public IEnumerable<JsonObject> GetLines(DateTimeOffset crashTime)
{
var lineCount = (int)CurrentLineCount;
for (var i = lineCount - 1; i >= 0; --i)
{
var line = GetLine(i);
var timestamp = DateTimeOffset.FromUnixTimeMilliseconds(BitConverter.ToInt64(line));
var thread = BitConverter.ToInt32(line[8..]);
var address = BitConverter.ToUInt64(line[12..]);
var collectionId = new Guid(line[20..36]);
var characterName = ReadString(line[36..]);
var requestedFileName = ReadString(line[116..]);
var actualFileName = ReadString(line[376..]);
yield return new JsonObject()
{
[nameof(ModdedFileLoadedEntry.Age)] = (crashTime - timestamp).TotalSeconds,
[nameof(ModdedFileLoadedEntry.Timestamp)] = timestamp,
[nameof(ModdedFileLoadedEntry.ThreadId)] = thread,
[nameof(ModdedFileLoadedEntry.CharacterName)] = characterName,
[nameof(ModdedFileLoadedEntry.CharacterAddress)] = address.ToString("X"),
[nameof(ModdedFileLoadedEntry.CollectionId)] = collectionId,
[nameof(ModdedFileLoadedEntry.RequestedFileName)] = requestedFileName,
[nameof(ModdedFileLoadedEntry.ActualFileName)] = actualFileName,
};
}
}
public static IBufferReader CreateReader(int pid)
=> new ModdedFileBuffer(false, pid);
public static IModdedFileBufferWriter CreateWriter(int pid)
=> new ModdedFileBuffer(pid);
private ModdedFileBuffer(bool writer, int pid)
: base($"{_name}_{pid}_{_version}", _version)
{ }
private ModdedFileBuffer(int pid)
: base($"{_name}_{pid}_{_version}", _version, _lineCount, _lineCapacity)
{ }
}

View file

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using Penumbra.CrashHandler.Buffers;
namespace Penumbra.CrashHandler;
/// <summary> A base entry for crash data. </summary>
public interface ICrashDataEntry
{
/// <summary> The timestamp of the event. </summary>
DateTimeOffset Timestamp { get; }
/// <summary> The thread invoking the event. </summary>
int ThreadId { get; }
/// <summary> The age of the event compared to the crash. (Redundantly with the timestamp) </summary>
double Age { get; }
}
/// <summary> A full set of crash data. </summary>
public class CrashData
{
/// <summary> The mode this data was obtained - manually or from a crash. </summary>
public string Mode { get; set; } = "Unknown";
/// <summary> The time this crash data was generated. </summary>
public DateTimeOffset CrashTime { get; set; } = DateTimeOffset.UnixEpoch;
/// <summary> Penumbra's Version when this crash data was created. </summary>
public string Version { get; set; } = string.Empty;
/// <summary> The Game's Version when this crash data was created. </summary>
public string GameVersion { get; set; } = string.Empty;
/// <summary> The FFXIV process ID when this data was generated. </summary>
public int ProcessId { get; set; } = 0;
/// <summary> The FFXIV Exit Code (if any) when this data was generated. </summary>
public int ExitCode { get; set; } = 0;
/// <summary> The total amount of characters loaded during this session. </summary>
public int TotalCharactersLoaded { get; set; } = 0;
/// <summary> The total amount of modded files loaded during this session. </summary>
public int TotalModdedFilesLoaded { get; set; } = 0;
/// <summary> The total amount of vfx functions invoked during this session. </summary>
public int TotalVFXFuncsInvoked { get; set; } = 0;
/// <summary> The last character loaded before this crash data was generated. </summary>
public CharacterLoadedEntry? LastCharacterLoaded
=> LastCharactersLoaded.Count == 0 ? default : LastCharactersLoaded[0];
/// <summary> The last modded file loaded before this crash data was generated. </summary>
public ModdedFileLoadedEntry? LastModdedFileLoaded
=> LastModdedFilesLoaded.Count == 0 ? default : LastModdedFilesLoaded[0];
/// <summary> The last vfx function invoked before this crash data was generated. </summary>
public VfxFuncInvokedEntry? LastVfxFuncInvoked
=> LastVFXFuncsInvoked.Count == 0 ? default : LastVFXFuncsInvoked[0];
/// <summary> A collection of the last few characters loaded before this crash data was generated. </summary>
public List<CharacterLoadedEntry> LastCharactersLoaded { get; set; } = [];
/// <summary> A collection of the last few modded files loaded before this crash data was generated. </summary>
public List<ModdedFileLoadedEntry> LastModdedFilesLoaded { get; set; } = [];
/// <summary> A collection of the last few vfx functions invoked before this crash data was generated. </summary>
public List<VfxFuncInvokedEntry> LastVFXFuncsInvoked { get; set; } = [];
}

View file

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Nodes;
using Penumbra.CrashHandler.Buffers;
namespace Penumbra.CrashHandler;
public interface IBufferReader
{
public uint TotalCount { get; }
public IEnumerable<JsonObject> GetLines(DateTimeOffset crashTime);
}
public sealed class GameEventLogReader(int pid) : IDisposable
{
public readonly (IBufferReader Reader, string TypeSingular, string TypePlural)[] Readers =
[
(CharacterBaseBuffer.CreateReader(pid), "CharacterLoaded", "CharactersLoaded"),
(ModdedFileBuffer.CreateReader(pid), "ModdedFileLoaded", "ModdedFilesLoaded"),
(AnimationInvocationBuffer.CreateReader(pid), "VFXFuncInvoked", "VFXFuncsInvoked"),
];
public void Dispose()
{
foreach (var (reader, _, _) in Readers)
(reader as IDisposable)?.Dispose();
}
public JsonObject Dump(string mode, int processId, int exitCode, string version, string gameVersion)
{
var crashTime = DateTimeOffset.UtcNow;
var obj = new JsonObject
{
[nameof(CrashData.Mode)] = mode,
[nameof(CrashData.CrashTime)] = DateTimeOffset.UtcNow,
[nameof(CrashData.ProcessId)] = processId,
[nameof(CrashData.ExitCode)] = exitCode,
[nameof(CrashData.Version)] = version,
[nameof(CrashData.GameVersion)] = gameVersion,
};
foreach (var (reader, singular, _) in Readers)
obj["Last" + singular] = reader.GetLines(crashTime).FirstOrDefault();
foreach (var (reader, _, plural) in Readers)
{
obj["Total" + plural] = reader.TotalCount;
var array = new JsonArray();
foreach (var file in reader.GetLines(crashTime))
array.Add(file);
obj["Last" + plural] = array;
}
return obj;
}
}

View file

@ -0,0 +1,18 @@
using System;
using Penumbra.CrashHandler.Buffers;
namespace Penumbra.CrashHandler;
public sealed class GameEventLogWriter(int pid) : IDisposable
{
public readonly ICharacterBaseBufferWriter CharacterBase = CharacterBaseBuffer.CreateWriter(pid);
public readonly IModdedFileBufferWriter FileLoaded = ModdedFileBuffer.CreateWriter(pid);
public readonly IAnimationInvocationBufferWriter AnimationFuncInvoked = AnimationInvocationBuffer.CreateWriter(pid);
public void Dispose()
{
(CharacterBase as IDisposable)?.Dispose();
(FileLoaded as IDisposable)?.Dispose();
(AnimationFuncInvoked as IDisposable)?.Dispose();
}
}

View file

@ -0,0 +1,18 @@
<Project Sdk="Dalamud.NET.Sdk/14.0.1">
<PropertyGroup>
<OutputType>Exe</OutputType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>embedded</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>embedded</DebugType>
</PropertyGroup>
<PropertyGroup>
<Use_DalamudPackager>false</Use_DalamudPackager>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,41 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Text.Json;
namespace Penumbra.CrashHandler;
public class CrashHandler
{
public static void Main(string[] args)
{
if (args.Length < 4 || !int.TryParse(args[1], out var pid))
return;
try
{
using var reader = new GameEventLogReader(pid);
var parent = Process.GetProcessById(pid);
using var handle = parent.SafeHandle;
parent.WaitForExit();
int exitCode;
try
{
exitCode = parent.ExitCode;
}
catch
{
exitCode = -1;
}
var obj = reader.Dump("Crash", pid, exitCode, args[2], args[3]);
using var fs = File.Open(args[0], FileMode.Create);
using var w = new Utf8JsonWriter(fs, new JsonWriterOptions { Indented = true });
obj.WriteTo(w, new JsonSerializerOptions() { WriteIndented = true });
}
catch (Exception ex)
{
File.WriteAllText(args[0], $"{DateTime.UtcNow} {pid} {ex}");
}
}
}

View file

@ -0,0 +1,13 @@
{
"version": 1,
"dependencies": {
"net10.0-windows7.0": {
"DotNet.ReproducibleBuilds": {
"type": "Direct",
"requested": "[1.2.39, )",
"resolved": "1.2.39",
"contentHash": "fcFN01tDTIQqDuTwr1jUQK/geofiwjG5DycJQOnC72i1SsLAk1ELe+apBOuZ11UMQG8YKFZG1FgvjZPbqHyatg=="
}
}
}
}

1
Penumbra.GameData Submodule

@ -0,0 +1 @@
Subproject commit 0e973ed6eace6afd31cd298f8c58f76fa8d5ef60

View file

@ -1,43 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel;
namespace Penumbra.GameData.Enums
{
public enum BodySlot : byte
{
Unknown,
Hair,
Face,
Tail,
Body,
Zear,
}
public static class BodySlotEnumExtension
{
public static string ToSuffix( this BodySlot value )
{
return value switch
{
BodySlot.Zear => "zear",
BodySlot.Face => "face",
BodySlot.Hair => "hair",
BodySlot.Body => "body",
BodySlot.Tail => "tail",
_ => throw new InvalidEnumArgumentException(),
};
}
}
public static partial class Names
{
public static readonly Dictionary< string, BodySlot > StringToBodySlot = new()
{
{ BodySlot.Zear.ToSuffix(), BodySlot.Zear },
{ BodySlot.Face.ToSuffix(), BodySlot.Face },
{ BodySlot.Hair.ToSuffix(), BodySlot.Hair },
{ BodySlot.Body.ToSuffix(), BodySlot.Body },
{ BodySlot.Tail.ToSuffix(), BodySlot.Tail },
};
}
}

View file

@ -1,40 +0,0 @@
using System;
using Lumina.Excel.GeneratedSheets;
using Action = Lumina.Excel.GeneratedSheets.Action;
namespace Penumbra.GameData.Enums
{
public enum ChangedItemType
{
None,
Item,
Action,
Customization,
}
public static class ChangedItemExtensions
{
public static (ChangedItemType, uint) ChangedItemToTypeAndId( object? item )
{
return item switch
{
null => ( ChangedItemType.None, 0 ),
Item i => ( ChangedItemType.Item, i.RowId ),
Action a => ( ChangedItemType.Action, a.RowId ),
_ => ( ChangedItemType.Customization, 0 ),
};
}
public static object? GetObject( this ChangedItemType type, uint id )
{
return type switch
{
ChangedItemType.None => null,
ChangedItemType.Item => ObjectIdentification.DataManager?.GetExcelSheet< Item >()?.GetRow( id ),
ChangedItemType.Action => ObjectIdentification.DataManager?.GetExcelSheet< Action >()?.GetRow( id ),
ChangedItemType.Customization => null,
_ => throw new ArgumentOutOfRangeException( nameof( type ), type, null )
};
}
}
}

View file

@ -1,55 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel;
namespace Penumbra.GameData.Enums
{
public enum CustomizationType : byte
{
Unknown,
Body,
Tail,
Face,
Iris,
Accessory,
Hair,
Zear,
DecalFace,
DecalEquip,
Skin,
Etc,
}
public static class CustomizationTypeEnumExtension
{
public static string ToSuffix( this CustomizationType value )
{
return value switch
{
CustomizationType.Body => "top",
CustomizationType.Face => "fac",
CustomizationType.Iris => "iri",
CustomizationType.Accessory => "acc",
CustomizationType.Hair => "hir",
CustomizationType.Tail => "til",
CustomizationType.Zear => "zer",
CustomizationType.Etc => "etc",
_ => throw new InvalidEnumArgumentException(),
};
}
}
public static partial class Names
{
public static readonly Dictionary< string, CustomizationType > SuffixToCustomizationType = new()
{
{ CustomizationType.Body.ToSuffix(), CustomizationType.Body },
{ CustomizationType.Face.ToSuffix(), CustomizationType.Face },
{ CustomizationType.Iris.ToSuffix(), CustomizationType.Iris },
{ CustomizationType.Accessory.ToSuffix(), CustomizationType.Accessory },
{ CustomizationType.Hair.ToSuffix(), CustomizationType.Hair },
{ CustomizationType.Tail.ToSuffix(), CustomizationType.Tail },
{ CustomizationType.Zear.ToSuffix(), CustomizationType.Zear },
{ CustomizationType.Etc.ToSuffix(), CustomizationType.Etc },
};
}
}

View file

@ -1,125 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel;
namespace Penumbra.GameData.Enums
{
public enum EquipSlot : byte
{
Unknown = 0,
MainHand = 1,
OffHand = 2,
Head = 3,
Body = 4,
Hands = 5,
Belt = 6,
Legs = 7,
Feet = 8,
Ears = 9,
Neck = 10,
Wrists = 11,
RFinger = 12,
BothHand = 13,
LFinger = 14, // Not officially existing, means "weapon could be equipped in either hand" for the game.
HeadBody = 15,
BodyHandsLegsFeet = 16,
SoulCrystal = 17,
LegsFeet = 18,
FullBody = 19,
BodyHands = 20,
BodyLegsFeet = 21,
All = 22, // Not officially existing
}
public static class EquipSlotEnumExtension
{
public static string ToSuffix( this EquipSlot value )
{
return value switch
{
EquipSlot.Head => "met",
EquipSlot.Hands => "glv",
EquipSlot.Legs => "dwn",
EquipSlot.Feet => "sho",
EquipSlot.Body => "top",
EquipSlot.Ears => "ear",
EquipSlot.Neck => "nek",
EquipSlot.RFinger => "rir",
EquipSlot.LFinger => "ril",
EquipSlot.Wrists => "wrs",
_ => throw new InvalidEnumArgumentException(),
};
}
public static EquipSlot ToSlot( this EquipSlot value )
{
return value switch
{
EquipSlot.MainHand => EquipSlot.MainHand,
EquipSlot.OffHand => EquipSlot.OffHand,
EquipSlot.Head => EquipSlot.Head,
EquipSlot.Body => EquipSlot.Body,
EquipSlot.Hands => EquipSlot.Hands,
EquipSlot.Belt => EquipSlot.Belt,
EquipSlot.Legs => EquipSlot.Legs,
EquipSlot.Feet => EquipSlot.Feet,
EquipSlot.Ears => EquipSlot.Ears,
EquipSlot.Neck => EquipSlot.Neck,
EquipSlot.Wrists => EquipSlot.Wrists,
EquipSlot.RFinger => EquipSlot.RFinger,
EquipSlot.BothHand => EquipSlot.MainHand,
EquipSlot.LFinger => EquipSlot.RFinger,
EquipSlot.HeadBody => EquipSlot.Body,
EquipSlot.BodyHandsLegsFeet => EquipSlot.Body,
EquipSlot.SoulCrystal => EquipSlot.SoulCrystal,
EquipSlot.LegsFeet => EquipSlot.Legs,
EquipSlot.FullBody => EquipSlot.Body,
EquipSlot.BodyHands => EquipSlot.Body,
EquipSlot.BodyLegsFeet => EquipSlot.Body,
_ => throw new InvalidEnumArgumentException(),
};
}
public static bool IsEquipment( this EquipSlot value )
{
return value switch
{
EquipSlot.Head => true,
EquipSlot.Hands => true,
EquipSlot.Legs => true,
EquipSlot.Feet => true,
EquipSlot.Body => true,
_ => false,
};
}
public static bool IsAccessory( this EquipSlot value )
{
return value switch
{
EquipSlot.Ears => true,
EquipSlot.Neck => true,
EquipSlot.RFinger => true,
EquipSlot.LFinger => true,
EquipSlot.Wrists => true,
_ => false,
};
}
}
public static partial class Names
{
public static readonly Dictionary< string, EquipSlot > SuffixToEquipSlot = new()
{
{ EquipSlot.Head.ToSuffix(), EquipSlot.Head },
{ EquipSlot.Hands.ToSuffix(), EquipSlot.Hands },
{ EquipSlot.Legs.ToSuffix(), EquipSlot.Legs },
{ EquipSlot.Feet.ToSuffix(), EquipSlot.Feet },
{ EquipSlot.Body.ToSuffix(), EquipSlot.Body },
{ EquipSlot.Ears.ToSuffix(), EquipSlot.Ears },
{ EquipSlot.Neck.ToSuffix(), EquipSlot.Neck },
{ EquipSlot.RFinger.ToSuffix(), EquipSlot.RFinger },
{ EquipSlot.LFinger.ToSuffix(), EquipSlot.LFinger },
{ EquipSlot.Wrists.ToSuffix(), EquipSlot.Wrists },
};
}
}

View file

@ -1,45 +0,0 @@
using System.Collections.Generic;
namespace Penumbra.GameData.Enums
{
public enum FileType : byte
{
Unknown,
Sound,
Imc,
Vfx,
Animation,
Pap,
MetaInfo,
Material,
Texture,
Model,
Shader,
Font,
Environment,
}
public static partial class Names
{
public static readonly Dictionary< string, FileType > ExtensionToFileType = new()
{
{ ".mdl", FileType.Model },
{ ".tex", FileType.Texture },
{ ".mtrl", FileType.Material },
{ ".atex", FileType.Animation },
{ ".avfx", FileType.Vfx },
{ ".scd", FileType.Sound },
{ ".imc", FileType.Imc },
{ ".pap", FileType.Pap },
{ ".eqp", FileType.MetaInfo },
{ ".eqdp", FileType.MetaInfo },
{ ".est", FileType.MetaInfo },
{ ".exd", FileType.MetaInfo },
{ ".exh", FileType.MetaInfo },
{ ".shpk", FileType.Shader },
{ ".shcd", FileType.Shader },
{ ".fdt", FileType.Font },
{ ".envb", FileType.Environment },
};
}
}

View file

@ -1,10 +0,0 @@
namespace Penumbra.GameData.Enums
{
public enum MouseButton
{
None,
Left,
Right,
Middle,
}
}

View file

@ -1,21 +0,0 @@
namespace Penumbra.GameData.Enums
{
public enum ObjectType : byte
{
Unknown,
Vfx,
DemiHuman,
Accessory,
World,
Housing,
Monster,
Icon,
LoadingScreen,
Map,
Interface,
Equipment,
Character,
Weapon,
Font,
}
}

View file

@ -1,453 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace Penumbra.GameData.Enums
{
public enum Race : byte
{
Unknown,
Hyur,
Elezen,
Lalafell,
Miqote,
Roegadyn,
AuRa,
Hrothgar,
Viera,
}
public enum Gender : byte
{
Unknown,
Male,
Female,
MaleNpc,
FemaleNpc,
}
public enum ModelRace : byte
{
Unknown,
Midlander,
Highlander,
Elezen,
Lalafell,
Miqote,
Roegadyn,
AuRa,
Hrothgar,
Viera,
}
public enum SubRace : byte
{
Unknown,
Midlander,
Highlander,
Wildwood,
Duskwight,
Plainsfolk,
Dunesfolk,
SeekerOfTheSun,
KeeperOfTheMoon,
Seawolf,
Hellsguard,
Raen,
Xaela,
Helion,
Lost,
Rava,
Veena,
}
// The combined gender-race-npc numerical code as used by the game.
public enum GenderRace : ushort
{
Unknown = 0,
MidlanderMale = 0101,
MidlanderMaleNpc = 0104,
MidlanderFemale = 0201,
MidlanderFemaleNpc = 0204,
HighlanderMale = 0301,
HighlanderMaleNpc = 0304,
HighlanderFemale = 0401,
HighlanderFemaleNpc = 0404,
ElezenMale = 0501,
ElezenMaleNpc = 0504,
ElezenFemale = 0601,
ElezenFemaleNpc = 0604,
MiqoteMale = 0701,
MiqoteMaleNpc = 0704,
MiqoteFemale = 0801,
MiqoteFemaleNpc = 0804,
RoegadynMale = 0901,
RoegadynMaleNpc = 0904,
RoegadynFemale = 1001,
RoegadynFemaleNpc = 1004,
LalafellMale = 1101,
LalafellMaleNpc = 1104,
LalafellFemale = 1201,
LalafellFemaleNpc = 1204,
AuRaMale = 1301,
AuRaMaleNpc = 1304,
AuRaFemale = 1401,
AuRaFemaleNpc = 1404,
HrothgarMale = 1501,
HrothgarMaleNpc = 1504,
VieraFemale = 1801,
VieraFemaleNpc = 1804,
UnknownMaleNpc = 9104,
UnknownFemaleNpc = 9204,
}
public static class RaceEnumExtensions
{
public static int ToRspIndex( this SubRace subRace )
{
return subRace switch
{
SubRace.Midlander => 0,
SubRace.Highlander => 1,
SubRace.Wildwood => 10,
SubRace.Duskwight => 11,
SubRace.Plainsfolk => 20,
SubRace.Dunesfolk => 21,
SubRace.SeekerOfTheSun => 30,
SubRace.KeeperOfTheMoon => 31,
SubRace.Seawolf => 40,
SubRace.Hellsguard => 41,
SubRace.Raen => 50,
SubRace.Xaela => 51,
SubRace.Helion => 60,
SubRace.Lost => 61,
SubRace.Rava => 70,
SubRace.Veena => 71,
_ => throw new ArgumentOutOfRangeException( nameof( subRace ), subRace, null ),
};
}
public static Race ToRace( this ModelRace race )
{
return race switch
{
ModelRace.Unknown => Race.Unknown,
ModelRace.Midlander => Race.Hyur,
ModelRace.Highlander => Race.Hyur,
ModelRace.Elezen => Race.Elezen,
ModelRace.Lalafell => Race.Lalafell,
ModelRace.Miqote => Race.Miqote,
ModelRace.Roegadyn => Race.Roegadyn,
ModelRace.AuRa => Race.AuRa,
ModelRace.Hrothgar => Race.Hrothgar,
ModelRace.Viera => Race.Viera,
_ => throw new ArgumentOutOfRangeException( nameof( race ), race, null ),
};
}
public static Race ToRace( this SubRace subRace )
{
return subRace switch
{
SubRace.Unknown => Race.Unknown,
SubRace.Midlander => Race.Hyur,
SubRace.Highlander => Race.Hyur,
SubRace.Wildwood => Race.Elezen,
SubRace.Duskwight => Race.Elezen,
SubRace.Plainsfolk => Race.Lalafell,
SubRace.Dunesfolk => Race.Lalafell,
SubRace.SeekerOfTheSun => Race.Miqote,
SubRace.KeeperOfTheMoon => Race.Miqote,
SubRace.Seawolf => Race.Roegadyn,
SubRace.Hellsguard => Race.Roegadyn,
SubRace.Raen => Race.AuRa,
SubRace.Xaela => Race.AuRa,
SubRace.Helion => Race.Hrothgar,
SubRace.Lost => Race.Hrothgar,
SubRace.Rava => Race.Viera,
SubRace.Veena => Race.Viera,
_ => throw new ArgumentOutOfRangeException( nameof( subRace ), subRace, null ),
};
}
public static string ToName( this ModelRace modelRace )
{
return modelRace switch
{
ModelRace.Midlander => SubRace.Midlander.ToName(),
ModelRace.Highlander => SubRace.Highlander.ToName(),
ModelRace.Elezen => Race.Elezen.ToName(),
ModelRace.Lalafell => Race.Lalafell.ToName(),
ModelRace.Miqote => Race.Miqote.ToName(),
ModelRace.Roegadyn => Race.Roegadyn.ToName(),
ModelRace.AuRa => Race.AuRa.ToName(),
ModelRace.Hrothgar => Race.Hrothgar.ToName(),
ModelRace.Viera => Race.Viera.ToName(),
_ => throw new ArgumentOutOfRangeException( nameof( modelRace ), modelRace, null ),
};
}
public static string ToName( this Race race )
{
return race switch
{
Race.Hyur => "Hyur",
Race.Elezen => "Elezen",
Race.Lalafell => "Lalafell",
Race.Miqote => "Miqo'te",
Race.Roegadyn => "Roegadyn",
Race.AuRa => "Au Ra",
Race.Hrothgar => "Hrothgar",
Race.Viera => "Viera",
_ => throw new ArgumentOutOfRangeException( nameof( race ), race, null ),
};
}
public static string ToName( this Gender gender )
{
return gender switch
{
Gender.Male => "Male",
Gender.Female => "Female",
Gender.MaleNpc => "Male (NPC)",
Gender.FemaleNpc => "Female (NPC)",
_ => throw new InvalidEnumArgumentException(),
};
}
public static string ToName( this SubRace subRace )
{
return subRace switch
{
SubRace.Midlander => "Midlander",
SubRace.Highlander => "Highlander",
SubRace.Wildwood => "Wildwood",
SubRace.Duskwight => "Duskwright",
SubRace.Plainsfolk => "Plainsfolk",
SubRace.Dunesfolk => "Dunesfolk",
SubRace.SeekerOfTheSun => "Seeker Of The Sun",
SubRace.KeeperOfTheMoon => "Keeper Of The Moon",
SubRace.Seawolf => "Seawolf",
SubRace.Hellsguard => "Hellsguard",
SubRace.Raen => "Raen",
SubRace.Xaela => "Xaela",
SubRace.Helion => "Hellion",
SubRace.Lost => "Lost",
SubRace.Rava => "Rava",
SubRace.Veena => "Veena",
_ => throw new InvalidEnumArgumentException(),
};
}
public static bool FitsRace( this SubRace subRace, Race race )
=> subRace.ToRace() == race;
public static byte ToByte( this Gender gender, ModelRace modelRace )
=> ( byte )( ( int )gender | ( ( int )modelRace << 3 ) );
public static byte ToByte( this ModelRace modelRace, Gender gender )
=> gender.ToByte( modelRace );
public static byte ToByte( this GenderRace value )
{
var (gender, race) = value.Split();
return gender.ToByte( race );
}
public static (Gender, ModelRace) Split( this GenderRace value )
{
return value switch
{
GenderRace.Unknown => ( Gender.Unknown, ModelRace.Unknown ),
GenderRace.MidlanderMale => ( Gender.Male, ModelRace.Midlander ),
GenderRace.MidlanderMaleNpc => ( Gender.MaleNpc, ModelRace.Midlander ),
GenderRace.MidlanderFemale => ( Gender.Female, ModelRace.Midlander ),
GenderRace.MidlanderFemaleNpc => ( Gender.FemaleNpc, ModelRace.Midlander ),
GenderRace.HighlanderMale => ( Gender.Male, ModelRace.Highlander ),
GenderRace.HighlanderMaleNpc => ( Gender.MaleNpc, ModelRace.Highlander ),
GenderRace.HighlanderFemale => ( Gender.Female, ModelRace.Highlander ),
GenderRace.HighlanderFemaleNpc => ( Gender.FemaleNpc, ModelRace.Highlander ),
GenderRace.ElezenMale => ( Gender.Male, ModelRace.Elezen ),
GenderRace.ElezenMaleNpc => ( Gender.MaleNpc, ModelRace.Elezen ),
GenderRace.ElezenFemale => ( Gender.Female, ModelRace.Elezen ),
GenderRace.ElezenFemaleNpc => ( Gender.FemaleNpc, ModelRace.Elezen ),
GenderRace.LalafellMale => ( Gender.Male, ModelRace.Lalafell ),
GenderRace.LalafellMaleNpc => ( Gender.MaleNpc, ModelRace.Lalafell ),
GenderRace.LalafellFemale => ( Gender.Female, ModelRace.Lalafell ),
GenderRace.LalafellFemaleNpc => ( Gender.FemaleNpc, ModelRace.Lalafell ),
GenderRace.MiqoteMale => ( Gender.Male, ModelRace.Miqote ),
GenderRace.MiqoteMaleNpc => ( Gender.MaleNpc, ModelRace.Miqote ),
GenderRace.MiqoteFemale => ( Gender.Female, ModelRace.Miqote ),
GenderRace.MiqoteFemaleNpc => ( Gender.FemaleNpc, ModelRace.Miqote ),
GenderRace.RoegadynMale => ( Gender.Male, ModelRace.Roegadyn ),
GenderRace.RoegadynMaleNpc => ( Gender.MaleNpc, ModelRace.Roegadyn ),
GenderRace.RoegadynFemale => ( Gender.Female, ModelRace.Roegadyn ),
GenderRace.RoegadynFemaleNpc => ( Gender.FemaleNpc, ModelRace.Roegadyn ),
GenderRace.AuRaMale => ( Gender.Male, ModelRace.AuRa ),
GenderRace.AuRaMaleNpc => ( Gender.MaleNpc, ModelRace.AuRa ),
GenderRace.AuRaFemale => ( Gender.Female, ModelRace.AuRa ),
GenderRace.AuRaFemaleNpc => ( Gender.FemaleNpc, ModelRace.AuRa ),
GenderRace.HrothgarMale => ( Gender.Male, ModelRace.Hrothgar ),
GenderRace.HrothgarMaleNpc => ( Gender.MaleNpc, ModelRace.Hrothgar ),
GenderRace.VieraFemale => ( Gender.Female, ModelRace.Viera ),
GenderRace.VieraFemaleNpc => ( Gender.FemaleNpc, ModelRace.Viera ),
GenderRace.UnknownMaleNpc => ( Gender.MaleNpc, ModelRace.Unknown ),
GenderRace.UnknownFemaleNpc => ( Gender.FemaleNpc, ModelRace.Unknown ),
_ => throw new InvalidEnumArgumentException(),
};
}
public static bool IsValid( this GenderRace value )
=> value != GenderRace.Unknown && Enum.IsDefined( typeof( GenderRace ), value );
public static string ToRaceCode( this GenderRace value )
{
return value switch
{
GenderRace.MidlanderMale => "0101",
GenderRace.MidlanderMaleNpc => "0104",
GenderRace.MidlanderFemale => "0201",
GenderRace.MidlanderFemaleNpc => "0204",
GenderRace.HighlanderMale => "0301",
GenderRace.HighlanderMaleNpc => "0304",
GenderRace.HighlanderFemale => "0401",
GenderRace.HighlanderFemaleNpc => "0404",
GenderRace.ElezenMale => "0501",
GenderRace.ElezenMaleNpc => "0504",
GenderRace.ElezenFemale => "0601",
GenderRace.ElezenFemaleNpc => "0604",
GenderRace.MiqoteMale => "0701",
GenderRace.MiqoteMaleNpc => "0704",
GenderRace.MiqoteFemale => "0801",
GenderRace.MiqoteFemaleNpc => "0804",
GenderRace.RoegadynMale => "0901",
GenderRace.RoegadynMaleNpc => "0904",
GenderRace.RoegadynFemale => "1001",
GenderRace.RoegadynFemaleNpc => "1004",
GenderRace.LalafellMale => "1101",
GenderRace.LalafellMaleNpc => "1104",
GenderRace.LalafellFemale => "1201",
GenderRace.LalafellFemaleNpc => "1204",
GenderRace.AuRaMale => "1301",
GenderRace.AuRaMaleNpc => "1304",
GenderRace.AuRaFemale => "1401",
GenderRace.AuRaFemaleNpc => "1404",
GenderRace.HrothgarMale => "1501",
GenderRace.HrothgarMaleNpc => "1504",
GenderRace.VieraFemale => "1801",
GenderRace.VieraFemaleNpc => "1804",
GenderRace.UnknownMaleNpc => "9104",
GenderRace.UnknownFemaleNpc => "9204",
_ => throw new InvalidEnumArgumentException(),
};
}
}
public static partial class Names
{
public static GenderRace GenderRaceFromCode( string code )
{
return code switch
{
"0101" => GenderRace.MidlanderMale,
"0104" => GenderRace.MidlanderMaleNpc,
"0201" => GenderRace.MidlanderFemale,
"0204" => GenderRace.MidlanderFemaleNpc,
"0301" => GenderRace.HighlanderMale,
"0304" => GenderRace.HighlanderMaleNpc,
"0401" => GenderRace.HighlanderFemale,
"0404" => GenderRace.HighlanderFemaleNpc,
"0501" => GenderRace.ElezenMale,
"0504" => GenderRace.ElezenMaleNpc,
"0601" => GenderRace.ElezenFemale,
"0604" => GenderRace.ElezenFemaleNpc,
"0701" => GenderRace.MiqoteMale,
"0704" => GenderRace.MiqoteMaleNpc,
"0801" => GenderRace.MiqoteFemale,
"0804" => GenderRace.MiqoteFemaleNpc,
"0901" => GenderRace.RoegadynMale,
"0904" => GenderRace.RoegadynMaleNpc,
"1001" => GenderRace.RoegadynFemale,
"1004" => GenderRace.RoegadynFemaleNpc,
"1101" => GenderRace.LalafellMale,
"1104" => GenderRace.LalafellMaleNpc,
"1201" => GenderRace.LalafellFemale,
"1204" => GenderRace.LalafellFemaleNpc,
"1301" => GenderRace.AuRaMale,
"1304" => GenderRace.AuRaMaleNpc,
"1401" => GenderRace.AuRaFemale,
"1404" => GenderRace.AuRaFemaleNpc,
"1501" => GenderRace.HrothgarMale,
"1504" => GenderRace.HrothgarMaleNpc,
"1801" => GenderRace.VieraFemale,
"1804" => GenderRace.VieraFemaleNpc,
"9104" => GenderRace.UnknownMaleNpc,
"9204" => GenderRace.UnknownFemaleNpc,
_ => throw new KeyNotFoundException(),
};
}
public static GenderRace GenderRaceFromByte( byte value )
{
var gender = ( Gender )( value & 0b111 );
var race = ( ModelRace )( value >> 3 );
return CombinedRace( gender, race );
}
public static GenderRace CombinedRace( Gender gender, ModelRace modelRace )
{
return gender switch
{
Gender.Male => modelRace switch
{
ModelRace.Midlander => GenderRace.MidlanderMale,
ModelRace.Highlander => GenderRace.HighlanderMale,
ModelRace.Elezen => GenderRace.ElezenMale,
ModelRace.Lalafell => GenderRace.LalafellMale,
ModelRace.Miqote => GenderRace.MiqoteMale,
ModelRace.Roegadyn => GenderRace.RoegadynMale,
ModelRace.AuRa => GenderRace.AuRaMale,
ModelRace.Hrothgar => GenderRace.HrothgarMale,
_ => GenderRace.Unknown,
},
Gender.MaleNpc => modelRace switch
{
ModelRace.Midlander => GenderRace.MidlanderMaleNpc,
ModelRace.Highlander => GenderRace.HighlanderMaleNpc,
ModelRace.Elezen => GenderRace.ElezenMaleNpc,
ModelRace.Lalafell => GenderRace.LalafellMaleNpc,
ModelRace.Miqote => GenderRace.MiqoteMaleNpc,
ModelRace.Roegadyn => GenderRace.RoegadynMaleNpc,
ModelRace.AuRa => GenderRace.AuRaMaleNpc,
ModelRace.Hrothgar => GenderRace.HrothgarMaleNpc,
_ => GenderRace.Unknown,
},
Gender.Female => modelRace switch
{
ModelRace.Midlander => GenderRace.MidlanderFemale,
ModelRace.Highlander => GenderRace.HighlanderFemale,
ModelRace.Elezen => GenderRace.ElezenFemale,
ModelRace.Lalafell => GenderRace.LalafellFemale,
ModelRace.Miqote => GenderRace.MiqoteFemale,
ModelRace.Roegadyn => GenderRace.RoegadynFemale,
ModelRace.AuRa => GenderRace.AuRaFemale,
ModelRace.Viera => GenderRace.VieraFemale,
_ => GenderRace.Unknown,
},
Gender.FemaleNpc => modelRace switch
{
ModelRace.Midlander => GenderRace.MidlanderFemaleNpc,
ModelRace.Highlander => GenderRace.HighlanderFemaleNpc,
ModelRace.Elezen => GenderRace.ElezenFemaleNpc,
ModelRace.Lalafell => GenderRace.LalafellFemaleNpc,
ModelRace.Miqote => GenderRace.MiqoteFemaleNpc,
ModelRace.Roegadyn => GenderRace.RoegadynFemaleNpc,
ModelRace.AuRa => GenderRace.AuRaFemaleNpc,
ModelRace.Viera => GenderRace.VieraFemaleNpc,
_ => GenderRace.Unknown,
},
_ => GenderRace.Unknown,
};
}
}
}

View file

@ -1,14 +0,0 @@
namespace Penumbra.GameData.Enums
{
public enum RedrawType
{
WithoutSettings,
WithSettings,
OnlyWithSettings,
Unload,
RedrawWithoutSettings,
RedrawWithSettings,
AfterGPoseWithSettings,
AfterGPoseWithoutSettings,
}
}

View file

@ -1,92 +0,0 @@
using System.ComponentModel;
namespace Penumbra.GameData.Enums
{
public enum RspAttribute : byte
{
MaleMinSize,
MaleMaxSize,
MaleMinTail,
MaleMaxTail,
FemaleMinSize,
FemaleMaxSize,
FemaleMinTail,
FemaleMaxTail,
BustMinX,
BustMinY,
BustMinZ,
BustMaxX,
BustMaxY,
BustMaxZ,
NumAttributes,
}
public static class RspAttributeExtensions
{
public static Gender ToGender( this RspAttribute attribute )
{
return attribute switch
{
RspAttribute.MaleMinSize => Gender.Male,
RspAttribute.MaleMaxSize => Gender.Male,
RspAttribute.MaleMinTail => Gender.Male,
RspAttribute.MaleMaxTail => Gender.Male,
RspAttribute.FemaleMinSize => Gender.Female,
RspAttribute.FemaleMaxSize => Gender.Female,
RspAttribute.FemaleMinTail => Gender.Female,
RspAttribute.FemaleMaxTail => Gender.Female,
RspAttribute.BustMinX => Gender.Female,
RspAttribute.BustMinY => Gender.Female,
RspAttribute.BustMinZ => Gender.Female,
RspAttribute.BustMaxX => Gender.Female,
RspAttribute.BustMaxY => Gender.Female,
RspAttribute.BustMaxZ => Gender.Female,
_ => Gender.Unknown,
};
}
public static string ToUngenderedString( this RspAttribute attribute )
{
return attribute switch
{
RspAttribute.MaleMinSize => "MinSize",
RspAttribute.MaleMaxSize => "MaxSize",
RspAttribute.MaleMinTail => "MinTail",
RspAttribute.MaleMaxTail => "MaxTail",
RspAttribute.FemaleMinSize => "MinSize",
RspAttribute.FemaleMaxSize => "MaxSize",
RspAttribute.FemaleMinTail => "MinTail",
RspAttribute.FemaleMaxTail => "MaxTail",
RspAttribute.BustMinX => "BustMinX",
RspAttribute.BustMinY => "BustMinY",
RspAttribute.BustMinZ => "BustMinZ",
RspAttribute.BustMaxX => "BustMaxX",
RspAttribute.BustMaxY => "BustMaxY",
RspAttribute.BustMaxZ => "BustMaxZ",
_ => "",
};
}
public static string ToFullString( this RspAttribute attribute )
{
return attribute switch
{
RspAttribute.MaleMinSize => "Male Minimum Size",
RspAttribute.MaleMaxSize => "Male Maximum Size",
RspAttribute.FemaleMinSize => "Female Minimum Size",
RspAttribute.FemaleMaxSize => "Female Maximum Size",
RspAttribute.BustMinX => "Bust Minimum X-Axis",
RspAttribute.BustMaxX => "Bust Maximum X-Axis",
RspAttribute.BustMinY => "Bust Minimum Y-Axis",
RspAttribute.BustMaxY => "Bust Maximum Y-Axis",
RspAttribute.BustMinZ => "Bust Minimum Z-Axis",
RspAttribute.BustMaxZ => "Bust Maximum Z-Axis",
RspAttribute.MaleMinTail => "Male Minimum Tail Length",
RspAttribute.MaleMaxTail => "Male Maximum Tail Length",
RspAttribute.FemaleMinTail => "Female Minimum Tail Length",
RspAttribute.FemaleMaxTail => "Female Maximum Tail Length",
_ => throw new InvalidEnumArgumentException(),
};
}
}
}

View file

@ -1,51 +0,0 @@
using System;
using System.Collections.Generic;
using Dalamud;
using Dalamud.Data;
using Lumina.Excel.GeneratedSheets;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.GameData.Util;
namespace Penumbra.GameData
{
public static class GameData
{
internal static ObjectIdentification? Identification;
internal static readonly GamePathParser GamePathParser = new();
public static IObjectIdentifier GetIdentifier( DataManager dataManager, ClientLanguage clientLanguage )
{
Identification ??= new ObjectIdentification( dataManager, clientLanguage );
return Identification;
}
public static IObjectIdentifier GetIdentifier()
{
if( Identification == null )
{
throw new Exception( "Object Identification was not initialized." );
}
return Identification;
}
public static IGamePathParser GetGamePathParser()
=> GamePathParser;
}
public interface IObjectIdentifier
{
public void Identify( IDictionary< string, object? > set, GamePath path );
public Dictionary< string, object? > Identify( GamePath path );
public Item? Identify( SetId setId, WeaponType weaponType, ushort variant, EquipSlot slot );
}
public interface IGamePathParser
{
public ObjectType PathToObjectType( GamePath path );
public GameObjectInfo GetFileInfo( GamePath path );
public string VfxToKey( GamePath path );
}
}

View file

@ -1,332 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Dalamud.Logging;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.GameData.Util;
namespace Penumbra.GameData
{
internal class GamePathParser : IGamePathParser
{
private const string CharacterFolder = "chara";
private const string EquipmentFolder = "equipment";
private const string PlayerFolder = "human";
private const string WeaponFolder = "weapon";
private const string AccessoryFolder = "accessory";
private const string DemiHumanFolder = "demihuman";
private const string MonsterFolder = "monster";
private const string CommonFolder = "common";
private const string UiFolder = "ui";
private const string IconFolder = "icon";
private const string LoadingFolder = "loadingimage";
private const string MapFolder = "map";
private const string InterfaceFolder = "uld";
private const string FontFolder = "font";
private const string HousingFolder = "hou";
private const string VfxFolder = "vfx";
private const string WorldFolder1 = "bgcommon";
private const string WorldFolder2 = "bg";
// @formatter:off
private readonly Dictionary<FileType, Dictionary<ObjectType, Regex[]>> _regexes = new()
{ { FileType.Font, new Dictionary< ObjectType, Regex[] >(){ { ObjectType.Font, new Regex[]{ new(@"common/font/(?'fontname'.*)_(?'id'\d\d)(_lobby)?\.fdt") } } } }
, { FileType.Texture, new Dictionary< ObjectType, Regex[] >()
{ { ObjectType.Icon, new Regex[]{ new(@"ui/icon/(?'group'\d*)(/(?'lang'[a-z]{2}))?(/(?'hq'hq))?/(?'id'\d*)\.tex") } }
, { ObjectType.Map, new Regex[]{ new(@"ui/map/(?'id'[a-z0-9]{4})/(?'variant'\d{2})/\k'id'\k'variant'(?'suffix'[a-z])?(_[a-z])?\.tex") } }
, { ObjectType.Weapon, new Regex[]{ new(@"chara/weapon/w(?'id'\d{4})/obj/body/b(?'weapon'\d{4})/texture/v(?'variant'\d{2})_w\k'id'b\k'weapon'(_[a-z])?_[a-z]\.tex") } }
, { ObjectType.Monster, new Regex[]{ new(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/texture/v(?'variant'\d{2})_m\k'monster'b\k'id'(_[a-z])?_[a-z]\.tex") } }
, { ObjectType.Equipment, new Regex[]{ new(@"chara/equipment/e(?'id'\d{4})/texture/v(?'variant'\d{2})_c(?'race'\d{4})e\k'id'_(?'slot'[a-z]{3})(_[a-z])?_[a-z]\.tex") } }
, { ObjectType.DemiHuman, new Regex[]{ new(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/texture/v(?'variant'\d{2})_d\k'id'e\k'equip'_(?'slot'[a-z]{3})(_[a-z])?_[a-z]\.tex") } }
, { ObjectType.Accessory, new Regex[]{ new(@"chara/accessory/a(?'id'\d{4})/texture/v(?'variant'\d{2})_c(?'race'\d{4})a\k'id'_(?'slot'[a-z]{3})_[a-z]\.tex") } }
, { ObjectType.Character, new Regex[]{ new(@"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/texture/(?'minus'(--)?)(v(?'variant'\d{2})_)?c\k'race'\k'typeabr'\k'id'(_(?'slot'[a-z]{3}))?(_[a-z])?_[a-z]\.tex")
, new(@"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/texture")
, new(@"chara/common/texture/skin(?'skin'.*)\.tex")
, new(@"chara/common/texture/decal_(?'location'[a-z]+)/[-_]?decal_(?'id'\d+).tex") } } } }
, { FileType.Model, new Dictionary< ObjectType, Regex[] >()
{ { ObjectType.Weapon, new Regex[]{ new(@"chara/weapon/w(?'id'\d{4})/obj/body/b(?'weapon'\d{4})/model/w\k'id'b\k'weapon'\.mdl") } }
, { ObjectType.Monster, new Regex[]{ new(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/model/m\k'monster'b\k'id'\.mdl") } }
, { ObjectType.Equipment, new Regex[]{ new(@"chara/equipment/e(?'id'\d{4})/model/c(?'race'\d{4})e\k'id'_(?'slot'[a-z]{3})\.mdl") } }
, { ObjectType.DemiHuman, new Regex[]{ new(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/model/d\k'id'e\k'equip'_(?'slot'[a-z]{3})\.mdl") } }
, { ObjectType.Accessory, new Regex[]{ new(@"chara/accessory/a(?'id'\d{4})/model/c(?'race'\d{4})a\k'id'_(?'slot'[a-z]{3})\.mdl") } }
, { ObjectType.Character, new Regex[]{ new(@"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/model/c\k'race'\k'typeabr'\k'id'_(?'slot'[a-z]{3})\.mdl") } } } }
, { FileType.Material, new Dictionary< ObjectType, Regex[] >()
{ { ObjectType.Weapon, new Regex[]{ new(@"chara/weapon/w(?'id'\d{4})/obj/body/b(?'weapon'\d{4})/material/v(?'variant'\d{4})/mt_w\k'id'b\k'weapon'_[a-z]\.mtrl") } }
, { ObjectType.Monster, new Regex[]{ new(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/material/v(?'variant'\d{4})/mt_m\k'monster'b\k'id'_[a-z]\.mtrl") } }
, { ObjectType.Equipment, new Regex[]{ new(@"chara/equipment/e(?'id'\d{4})/material/v(?'variant'\d{4})/mt_c(?'race'\d{4})e\k'id'_(?'slot'[a-z]{3})_[a-z]\.mtrl") } }
, { ObjectType.DemiHuman, new Regex[]{ new(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/material/v(?'variant'\d{4})/mt_d\k'id'e\k'equip'_(?'slot'[a-z]{3})_[a-z]\.mtrl") } }
, { ObjectType.Accessory, new Regex[]{ new(@"chara/accessory/a(?'id'\d{4})/material/v(?'variant'\d{4})/mt_c(?'race'\d{4})a\k'id'_(?'slot'[a-z]{3})_[a-z]\.mtrl") } }
, { ObjectType.Character, new Regex[]{ new(@"chara/human/c(?'race'\d{4})/obj/(?'type'[a-z]+)/(?'typeabr'[a-z])(?'id'\d{4})/material/v(?'variant'\d{4})/mt_c\k'race'\k'typeabr'\k'id'(_(?'slot'[a-z]{3}))?_[a-z]\.mtrl") } } } }
, { FileType.Imc, new Dictionary< ObjectType, Regex[] >()
{ { ObjectType.Weapon, new Regex[]{ new(@"chara/weapon/w(?'id'\d{4})/obj/body/b(?'weapon'\d{4})/b\k'weapon'\.imc") } }
, { ObjectType.Monster, new Regex[]{ new(@"chara/monster/m(?'monster'\d{4})/obj/body/b(?'id'\d{4})/b\k'id'\.imc") } }
, { ObjectType.Equipment, new Regex[]{ new(@"chara/equipment/e(?'id'\d{4})/e\k'id'\.imc") } }
, { ObjectType.DemiHuman, new Regex[]{ new(@"chara/demihuman/d(?'id'\d{4})/obj/equipment/e(?'equip'\d{4})/e\k'equip'\.imc") } }
, { ObjectType.Accessory, new Regex[]{ new(@"chara/accessory/a(?'id'\d{4})/a\k'id'\.imc") } } } },
};
// @formatter:on
public ObjectType PathToObjectType( GamePath path )
{
if( path.Empty )
{
return ObjectType.Unknown;
}
string p = path;
var folders = p.Split( '/' );
if( folders.Length < 2 )
{
return ObjectType.Unknown;
}
return folders[ 0 ] switch
{
CharacterFolder => folders[ 1 ] switch
{
EquipmentFolder => ObjectType.Equipment,
AccessoryFolder => ObjectType.Accessory,
WeaponFolder => ObjectType.Weapon,
PlayerFolder => ObjectType.Character,
DemiHumanFolder => ObjectType.DemiHuman,
MonsterFolder => ObjectType.Monster,
CommonFolder => ObjectType.Character,
_ => ObjectType.Unknown,
},
UiFolder => folders[ 1 ] switch
{
IconFolder => ObjectType.Icon,
LoadingFolder => ObjectType.LoadingScreen,
MapFolder => ObjectType.Map,
InterfaceFolder => ObjectType.Interface,
_ => ObjectType.Unknown,
},
CommonFolder => folders[ 1 ] switch
{
FontFolder => ObjectType.Font,
_ => ObjectType.Unknown,
},
HousingFolder => ObjectType.Housing,
WorldFolder1 => folders[ 1 ] switch
{
HousingFolder => ObjectType.Housing,
_ => ObjectType.World,
},
WorldFolder2 => ObjectType.World,
VfxFolder => ObjectType.Vfx,
_ => ObjectType.Unknown,
};
}
private (FileType, ObjectType, Match?) ParseGamePath( GamePath path )
{
if( !Names.ExtensionToFileType.TryGetValue( Extension( path ), out var fileType ) )
{
fileType = FileType.Unknown;
}
var objectType = PathToObjectType( path );
if( !_regexes.TryGetValue( fileType, out var objectDict ) )
{
return ( fileType, objectType, null );
}
if( !objectDict.TryGetValue( objectType, out var regexes ) )
{
return ( fileType, objectType, null );
}
foreach( var regex in regexes )
{
var match = regex.Match( path );
if( match.Success )
{
return ( fileType, objectType, match );
}
}
return ( fileType, objectType, null );
}
private static string Extension( string filename )
{
var extIdx = filename.LastIndexOf( '.' );
return extIdx < 0 ? "" : filename.Substring( extIdx );
}
private static GameObjectInfo HandleEquipment( FileType fileType, GroupCollection groups )
{
var setId = ushort.Parse( groups[ "id" ].Value );
if( fileType == FileType.Imc )
{
return GameObjectInfo.Equipment( fileType, setId );
}
var gr = Names.GenderRaceFromCode( groups[ "race" ].Value );
var slot = Names.SuffixToEquipSlot[ groups[ "slot" ].Value ];
if( fileType == FileType.Model )
{
return GameObjectInfo.Equipment( fileType, setId, gr, slot );
}
var variant = byte.Parse( groups[ "variant" ].Value );
return GameObjectInfo.Equipment( fileType, setId, gr, slot, variant );
}
private static GameObjectInfo HandleWeapon( FileType fileType, GroupCollection groups )
{
var weaponId = ushort.Parse( groups[ "weapon" ].Value );
var setId = ushort.Parse( groups[ "id" ].Value );
if( fileType == FileType.Imc || fileType == FileType.Model )
{
return GameObjectInfo.Weapon( fileType, setId, weaponId );
}
var variant = byte.Parse( groups[ "variant" ].Value );
return GameObjectInfo.Weapon( fileType, setId, weaponId, variant );
}
private static GameObjectInfo HandleMonster( FileType fileType, GroupCollection groups )
{
var monsterId = ushort.Parse( groups[ "monster" ].Value );
var bodyId = ushort.Parse( groups[ "id" ].Value );
if( fileType == FileType.Imc || fileType == FileType.Model )
{
return GameObjectInfo.Monster( fileType, monsterId, bodyId );
}
var variant = byte.Parse( groups[ "variant" ].Value );
return GameObjectInfo.Monster( fileType, monsterId, bodyId, variant );
}
private static GameObjectInfo HandleDemiHuman( FileType fileType, GroupCollection groups )
{
var demiHumanId = ushort.Parse( groups[ "id" ].Value );
var equipId = ushort.Parse( groups[ "equip" ].Value );
if( fileType == FileType.Imc )
{
return GameObjectInfo.DemiHuman( fileType, demiHumanId, equipId );
}
var slot = Names.SuffixToEquipSlot[ groups[ "slot" ].Value ];
if( fileType == FileType.Model )
{
return GameObjectInfo.DemiHuman( fileType, demiHumanId, equipId, slot );
}
var variant = byte.Parse( groups[ "variant" ].Value );
return GameObjectInfo.DemiHuman( fileType, demiHumanId, equipId, slot, variant );
}
private static GameObjectInfo HandleCustomization( FileType fileType, GroupCollection groups )
{
if( groups[ "skin" ].Success )
{
return GameObjectInfo.Customization( fileType, CustomizationType.Skin );
}
var id = ushort.Parse( groups[ "id" ].Value );
if( groups[ "location" ].Success )
{
var tmpType = groups[ "location" ].Value == "face" ? CustomizationType.DecalFace
: groups[ "location" ].Value == "equip" ? CustomizationType.DecalEquip : CustomizationType.Unknown;
return GameObjectInfo.Customization( fileType, tmpType, id );
}
var gr = Names.GenderRaceFromCode( groups[ "race" ].Value );
var bodySlot = Names.StringToBodySlot[ groups[ "type" ].Value ];
var type = groups[ "slot" ].Success
? Names.SuffixToCustomizationType[ groups[ "slot" ].Value ]
: CustomizationType.Skin;
if( fileType == FileType.Material )
{
var variant = byte.Parse( groups[ "variant" ].Value );
return GameObjectInfo.Customization( fileType, type, id, gr, bodySlot, variant );
}
return GameObjectInfo.Customization( fileType, type, id, gr, bodySlot );
}
private static GameObjectInfo HandleIcon( FileType fileType, GroupCollection groups )
{
var hq = groups[ "hq" ].Success;
var id = uint.Parse( groups[ "id" ].Value );
if( !groups[ "lang" ].Success )
{
return GameObjectInfo.Icon( fileType, id, hq );
}
var language = groups[ "lang" ].Value switch
{
"en" => Dalamud.ClientLanguage.English,
"ja" => Dalamud.ClientLanguage.Japanese,
"de" => Dalamud.ClientLanguage.German,
"fr" => Dalamud.ClientLanguage.French,
_ => Dalamud.ClientLanguage.English,
};
return GameObjectInfo.Icon( fileType, id, hq, language );
}
private static GameObjectInfo HandleMap( FileType fileType, GroupCollection groups )
{
var map = Encoding.ASCII.GetBytes( groups[ "id" ].Value );
var variant = byte.Parse( groups[ "variant" ].Value );
if( groups[ "suffix" ].Success )
{
var suffix = Encoding.ASCII.GetBytes( groups[ "suffix" ].Value )[ 0 ];
return GameObjectInfo.Map( fileType, map[ 0 ], map[ 1 ], map[ 2 ], map[ 3 ], variant, suffix );
}
return GameObjectInfo.Map( fileType, map[ 0 ], map[ 1 ], map[ 2 ], map[ 3 ], variant );
}
public GameObjectInfo GetFileInfo( GamePath path )
{
var (fileType, objectType, match) = ParseGamePath( path );
if( match == null || !match.Success )
{
return new GameObjectInfo { FileType = fileType, ObjectType = objectType };
}
try
{
var groups = match.Groups;
switch( objectType )
{
case ObjectType.Accessory: return HandleEquipment( fileType, groups );
case ObjectType.Equipment: return HandleEquipment( fileType, groups );
case ObjectType.Weapon: return HandleWeapon( fileType, groups );
case ObjectType.Map: return HandleMap( fileType, groups );
case ObjectType.Monster: return HandleMonster( fileType, groups );
case ObjectType.DemiHuman: return HandleDemiHuman( fileType, groups );
case ObjectType.Character: return HandleCustomization( fileType, groups );
case ObjectType.Icon: return HandleIcon( fileType, groups );
}
}
catch( Exception e )
{
PluginLog.Error( $"Could not parse {path}:\n{e}" );
}
return new GameObjectInfo { FileType = fileType, ObjectType = objectType };
}
private readonly Regex _vfxRegexTmb = new( @"chara/action/(?'key'[^\s]+?)\.tmb" );
private readonly Regex _vfxRegexPap = new( @"chara/human/c0101/animation/a0001/[^\s]+?/(?'key'[^\s]+?)\.pap" );
public string VfxToKey( GamePath path )
{
var match = _vfxRegexTmb.Match( path );
if( match.Success )
{
return match.Groups[ "key" ].Value.ToLowerInvariant();
}
match = _vfxRegexPap.Match( path );
return match.Success ? match.Groups[ "key" ].Value.ToLowerInvariant() : string.Empty;
}
}
}

View file

@ -1,325 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Dalamud;
using Dalamud.Data;
using Lumina.Excel.GeneratedSheets;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.GameData.Util;
using Action = Lumina.Excel.GeneratedSheets.Action;
namespace Penumbra.GameData
{
internal class ObjectIdentification : IObjectIdentifier
{
public static DataManager? DataManager = null!;
private readonly List< (ulong, HashSet< Item >) > _weapons;
private readonly List< (ulong, HashSet< Item >) > _equipment;
private readonly Dictionary< string, HashSet< Action > > _actions;
private static bool Add( IDictionary< ulong, HashSet< Item > > dict, ulong key, Item item )
{
if( dict.TryGetValue( key, out var list ) )
{
return list.Add( item );
}
dict[ key ] = new HashSet< Item > { item };
return true;
}
private static ulong EquipmentKey( Item i )
{
var model = ( ulong )( ( Lumina.Data.Parsing.Quad )i.ModelMain ).A;
var variant = ( ulong )( ( Lumina.Data.Parsing.Quad )i.ModelMain ).B;
var slot = ( ulong )( ( EquipSlot )i.EquipSlotCategory.Row ).ToSlot();
return ( model << 32 ) | ( slot << 16 ) | variant;
}
private static ulong WeaponKey( Item i, bool offhand )
{
var quad = offhand ? ( Lumina.Data.Parsing.Quad )i.ModelSub : ( Lumina.Data.Parsing.Quad )i.ModelMain;
var model = ( ulong )quad.A;
var type = ( ulong )quad.B;
var variant = ( ulong )quad.C;
return ( model << 32 ) | ( type << 16 ) | variant;
}
private void AddAction( string key, Action action )
{
if( key.Length == 0 )
{
return;
}
key = key.ToLowerInvariant();
if( _actions.TryGetValue( key, out var actions ) )
{
actions.Add( action );
}
else
{
_actions[ key ] = new HashSet< Action > { action };
}
}
public ObjectIdentification( DataManager dataManager, ClientLanguage clientLanguage )
{
DataManager = dataManager;
var items = dataManager.GetExcelSheet< Item >( clientLanguage )!;
SortedList< ulong, HashSet< Item > > weapons = new();
SortedList< ulong, HashSet< Item > > equipment = new();
foreach( var item in items )
{
switch( ( EquipSlot )item.EquipSlotCategory.Row )
{
case EquipSlot.MainHand:
case EquipSlot.OffHand:
case EquipSlot.BothHand:
if( item.ModelMain != 0 )
{
Add( weapons, WeaponKey( item, false ), item );
}
if( item.ModelSub != 0 )
{
Add( weapons, WeaponKey( item, true ), item );
}
break;
// Accessories
case EquipSlot.RFinger:
case EquipSlot.Wrists:
case EquipSlot.Ears:
case EquipSlot.Neck:
Add( equipment, EquipmentKey( item ), item );
break;
// Equipment
case EquipSlot.Head:
case EquipSlot.Body:
case EquipSlot.Hands:
case EquipSlot.Legs:
case EquipSlot.Feet:
case EquipSlot.BodyHands:
case EquipSlot.BodyHandsLegsFeet:
case EquipSlot.BodyLegsFeet:
case EquipSlot.FullBody:
case EquipSlot.HeadBody:
case EquipSlot.LegsFeet:
Add( equipment, EquipmentKey( item ), item );
break;
default: continue;
}
}
_actions = new Dictionary< string, HashSet< Action > >();
foreach( var action in dataManager.GetExcelSheet< Action >( clientLanguage )!
.Where( a => a.Name.ToString().Any() ) )
{
var startKey = action.AnimationStart?.Value?.Name?.Value?.Key.ToString() ?? string.Empty;
var endKey = action.AnimationEnd?.Value?.Key.ToString() ?? string.Empty;
var hitKey = action.ActionTimelineHit?.Value?.Key.ToString() ?? string.Empty;
AddAction( startKey, action );
AddAction( endKey, action );
AddAction( hitKey, action );
}
_weapons = weapons.Select( kvp => ( kvp.Key, kvp.Value ) ).ToList();
_equipment = equipment.Select( kvp => ( kvp.Key, kvp.Value ) ).ToList();
}
private class Comparer : IComparer< (ulong, HashSet< Item >) >
{
public int Compare( (ulong, HashSet< Item >) x, (ulong, HashSet< Item >) y )
=> x.Item1.CompareTo( y.Item1 );
}
private static (int, int) FindIndexRange( List< (ulong, HashSet< Item >) > list, ulong key, ulong mask )
{
var maskedKey = key & mask;
var idx = list.BinarySearch( 0, list.Count, ( key, null! ), new Comparer() );
if( idx < 0 )
{
if( ~idx == list.Count || maskedKey != ( list[ ~idx ].Item1 & mask ) )
{
return ( -1, -1 );
}
idx = ~idx;
}
var endIdx = idx + 1;
while( endIdx < list.Count && maskedKey == ( list[ endIdx ].Item1 & mask ) )
{
++endIdx;
}
return ( idx, endIdx );
}
private void FindEquipment( IDictionary< string, object? > set, GameObjectInfo info )
{
var key = ( ulong )info.PrimaryId << 32;
var mask = 0xFFFF00000000ul;
if( info.EquipSlot != EquipSlot.Unknown )
{
key |= ( ulong )info.EquipSlot.ToSlot() << 16;
mask |= 0xFFFF0000;
}
if( info.Variant != 0 )
{
key |= info.Variant;
mask |= 0xFFFF;
}
var (start, end) = FindIndexRange( _equipment, key, mask );
if( start == -1 )
{
return;
}
for( ; start < end; ++start )
{
foreach( var item in _equipment[ start ].Item2 )
{
set[ item.Name.ToString() ] = item;
}
}
}
private void FindWeapon( IDictionary< string, object? > set, GameObjectInfo info )
{
var key = ( ulong )info.PrimaryId << 32;
var mask = 0xFFFF00000000ul;
if( info.SecondaryId != 0 )
{
key |= ( ulong )info.SecondaryId << 16;
mask |= 0xFFFF0000;
}
if( info.Variant != 0 )
{
key |= info.Variant;
mask |= 0xFFFF;
}
var (start, end) = FindIndexRange( _weapons, key, mask );
if( start == -1 )
{
return;
}
for( ; start < end; ++start )
{
foreach( var item in _weapons[ start ].Item2 )
{
set[ item.Name.ToString() ] = item;
}
}
}
private void IdentifyParsed( IDictionary< string, object? > set, GameObjectInfo info )
{
switch( info.ObjectType )
{
case ObjectType.Unknown:
case ObjectType.LoadingScreen:
case ObjectType.Map:
case ObjectType.Interface:
case ObjectType.Vfx:
case ObjectType.World:
case ObjectType.Housing:
case ObjectType.DemiHuman:
case ObjectType.Monster:
case ObjectType.Icon:
case ObjectType.Font:
// Don't do anything for these cases.
break;
case ObjectType.Accessory:
case ObjectType.Equipment:
FindEquipment( set, info );
break;
case ObjectType.Weapon:
FindWeapon( set, info );
break;
case ObjectType.Character:
var (gender, race) = info.GenderRace.Split();
var raceString = race != ModelRace.Unknown ? race.ToName() + " " : "";
var genderString = gender != Gender.Unknown ? gender.ToName() + " " : "Player ";
if( info.CustomizationType == CustomizationType.Skin )
{
set[ $"Customization: {raceString}{genderString}Skin Textures" ] = null;
}
else
{
var customizationString =
$"Customization: {race} {gender} {info.BodySlot} ({info.CustomizationType}) {info.PrimaryId}";
set[ customizationString ] = null;
}
break;
default: throw new InvalidEnumArgumentException();
}
}
private void IdentifyVfx( IDictionary< string, object? > set, GamePath path )
{
var key = GameData.GamePathParser.VfxToKey( path );
if( key.Length == 0 || !_actions.TryGetValue( key, out var actions ) )
{
return;
}
foreach( var action in actions )
{
set[ $"Action: {action.Name}" ] = action;
}
}
public void Identify( IDictionary< string, object? > set, GamePath path )
{
if( ( ( string )path ).EndsWith( ".pap" ) || ( ( string )path ).EndsWith( ".tmb" ) )
{
IdentifyVfx( set, path );
}
else
{
var info = GameData.GamePathParser.GetFileInfo( path );
IdentifyParsed( set, info );
}
}
public Dictionary< string, object? > Identify( GamePath path )
{
Dictionary< string, object? > ret = new();
Identify( ret, path );
return ret;
}
public Item? Identify( SetId setId, WeaponType weaponType, ushort variant, EquipSlot slot )
{
switch( slot )
{
case EquipSlot.MainHand:
case EquipSlot.OffHand:
{
var (begin, _) = FindIndexRange( _weapons, ( ( ulong )setId << 32 ) | ( ( ulong )weaponType << 16 ) | variant,
0xFFFFFFFFFFFF );
return begin >= 0 ? _weapons[ begin ].Item2.FirstOrDefault() : null;
}
default:
{
var (begin, _) = FindIndexRange( _equipment,
( ( ulong )setId << 32 ) | ( ( ulong )slot.ToSlot() << 16 ) | variant,
0xFFFFFFFFFFFF );
return begin >= 0 ? _equipment[ begin ].Item2.FirstOrDefault() : null;
}
}
}
}
}

View file

@ -1,48 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0-windows</TargetFramework>
<LangVersion>preview</LangVersion>
<PlatformTarget>x64</PlatformTarget>
<AssemblyTitle>Penumbra.GameData</AssemblyTitle>
<Company>absolute gangstas</Company>
<Product>Penumbra</Product>
<Copyright>Copyright © 2020</Copyright>
<FileVersion>1.0.0.0</FileVersion>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<OutputPath>bin\$(Configuration)\</OutputPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugType>full</DebugType>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
</PropertyGroup>
<PropertyGroup>
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
</PropertyGroup>
<ItemGroup>
<Reference Include="Dalamud">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Lumina">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Lumina.Excel">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3"/>
</ItemGroup>
</Project>

View file

@ -1,15 +0,0 @@
using System.Runtime.InteropServices;
namespace Penumbra.GameData.Structs
{
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
public readonly struct CharacterArmor
{
public readonly SetId Set;
public readonly byte Variant;
public readonly StainId Stain;
public override string ToString()
=> $"{Set},{Variant},{Stain}";
}
}

View file

@ -1,149 +0,0 @@
using System;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Objects.Types;
// Read the customization data regarding weapons and displayable equipment from an actor struct.
// Stores the data in a 56 bytes, i.e. 7 longs for easier comparison.
namespace Penumbra.GameData.Structs
{
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
public class CharacterEquipment
{
public const int MainWeaponOffset = 0x0F08;
public const int OffWeaponOffset = 0x0F70;
public const int EquipmentOffset = 0x1040;
public const int EquipmentSlots = 10;
public const int WeaponSlots = 2;
public CharacterWeapon MainHand;
public CharacterWeapon OffHand;
public CharacterArmor Head;
public CharacterArmor Body;
public CharacterArmor Hands;
public CharacterArmor Legs;
public CharacterArmor Feet;
public CharacterArmor Ears;
public CharacterArmor Neck;
public CharacterArmor Wrists;
public CharacterArmor RFinger;
public CharacterArmor LFinger;
public ushort IsSet; // Also fills struct size to 56, a multiple of 8.
public CharacterEquipment()
=> Clear();
public CharacterEquipment( Character actor )
: this( actor.Address )
{ }
public override string ToString()
=> IsSet == 0
? "(Not Set)"
: $"({MainHand}) | ({OffHand}) | ({Head}) | ({Body}) | ({Hands}) | ({Legs}) | "
+ $"({Feet}) | ({Ears}) | ({Neck}) | ({Wrists}) | ({LFinger}) | ({RFinger})";
public bool Equal( Character rhs )
=> CompareData( new CharacterEquipment( rhs ) );
public bool Equal( CharacterEquipment rhs )
=> CompareData( rhs );
public bool CompareAndUpdate( Character rhs )
=> CompareAndOverwrite( new CharacterEquipment( rhs ) );
public bool CompareAndUpdate( CharacterEquipment rhs )
=> CompareAndOverwrite( rhs );
private unsafe CharacterEquipment( IntPtr actorAddress )
{
IsSet = 1;
var actorPtr = ( byte* )actorAddress.ToPointer();
fixed( CharacterWeapon* main = &MainHand, off = &OffHand )
{
Buffer.MemoryCopy( actorPtr + MainWeaponOffset, main, sizeof( CharacterWeapon ), sizeof( CharacterWeapon ) );
Buffer.MemoryCopy( actorPtr + OffWeaponOffset, off, sizeof( CharacterWeapon ), sizeof( CharacterWeapon ) );
}
fixed( CharacterArmor* equipment = &Head )
{
Buffer.MemoryCopy( actorPtr + EquipmentOffset, equipment, EquipmentSlots * sizeof( CharacterArmor ),
EquipmentSlots * sizeof( CharacterArmor ) );
}
}
public unsafe void Clear()
{
fixed( CharacterWeapon* main = &MainHand )
{
var structSizeEights = ( 2 + EquipmentSlots * sizeof( CharacterArmor ) + WeaponSlots * sizeof( CharacterWeapon ) ) / 8;
for( ulong* ptr = ( ulong* )main, end = ptr + structSizeEights; ptr != end; ++ptr )
{
*ptr = 0;
}
}
}
private unsafe bool CompareAndOverwrite( CharacterEquipment rhs )
{
var structSizeEights = ( 2 + EquipmentSlots * sizeof( CharacterArmor ) + WeaponSlots * sizeof( CharacterWeapon ) ) / 8;
var ret = true;
fixed( CharacterWeapon* data1 = &MainHand, data2 = &rhs.MainHand )
{
var ptr1 = ( ulong* )data1;
var ptr2 = ( ulong* )data2;
for( var end = ptr1 + structSizeEights; ptr1 != end; ++ptr1, ++ptr2 )
{
if( *ptr1 != *ptr2 )
{
*ptr1 = *ptr2;
ret = false;
}
}
}
return ret;
}
private unsafe bool CompareData( CharacterEquipment rhs )
{
var structSizeEights = ( 2 + EquipmentSlots * sizeof( CharacterArmor ) + WeaponSlots * sizeof( CharacterWeapon ) ) / 8;
fixed( CharacterWeapon* data1 = &MainHand, data2 = &rhs.MainHand )
{
var ptr1 = ( ulong* )data1;
var ptr2 = ( ulong* )data2;
for( var end = ptr1 + structSizeEights; ptr1 != end; ++ptr1, ++ptr2 )
{
if( *ptr1 != *ptr2 )
{
return false;
}
}
}
return true;
}
public unsafe void WriteBytes( byte[] array, int offset = 0 )
{
fixed( CharacterWeapon* data = &MainHand )
{
Marshal.Copy( new IntPtr( data ), array, offset, 56 );
}
}
public byte[] ToBytes()
{
var ret = new byte[56];
WriteBytes( ret );
return ret;
}
public unsafe void FromBytes( byte[] array, int offset = 0 )
{
fixed( CharacterWeapon* data = &MainHand )
{
Marshal.Copy( array, offset, new IntPtr( data ), 56 );
}
}
}
}

View file

@ -1,16 +0,0 @@
using System.Runtime.InteropServices;
namespace Penumbra.GameData.Structs
{
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
public readonly struct CharacterWeapon
{
public readonly SetId Set;
public readonly WeaponType Type;
public readonly ushort Variant;
public readonly StainId Stain;
public override string ToString()
=> $"{Set},{Type},{Variant},{Stain}";
}
}

View file

@ -1,107 +0,0 @@
using System;
using System.ComponentModel;
using Penumbra.GameData.Enums;
namespace Penumbra.GameData.Structs
{
[Flags]
public enum EqdpEntry : ushort
{
Invalid = 0,
Head1 = 0b0000000001,
Head2 = 0b0000000010,
HeadMask = 0b0000000011,
Body1 = 0b0000000100,
Body2 = 0b0000001000,
BodyMask = 0b0000001100,
Hands1 = 0b0000010000,
Hands2 = 0b0000100000,
HandsMask = 0b0000110000,
Legs1 = 0b0001000000,
Legs2 = 0b0010000000,
LegsMask = 0b0011000000,
Feet1 = 0b0100000000,
Feet2 = 0b1000000000,
FeetMask = 0b1100000000,
Ears1 = 0b0000000001,
Ears2 = 0b0000000010,
EarsMask = 0b0000000011,
Neck1 = 0b0000000100,
Neck2 = 0b0000001000,
NeckMask = 0b0000001100,
Wrists1 = 0b0000010000,
Wrists2 = 0b0000100000,
WristsMask = 0b0000110000,
RingR1 = 0b0001000000,
RingR2 = 0b0010000000,
RingRMask = 0b0011000000,
RingL1 = 0b0100000000,
RingL2 = 0b1000000000,
RingLMask = 0b1100000000,
}
public static class Eqdp
{
public static int Offset( EquipSlot slot )
{
return slot switch
{
EquipSlot.Head => 0,
EquipSlot.Body => 2,
EquipSlot.Hands => 4,
EquipSlot.Legs => 6,
EquipSlot.Feet => 8,
EquipSlot.Ears => 0,
EquipSlot.Neck => 2,
EquipSlot.Wrists => 4,
EquipSlot.RFinger => 6,
EquipSlot.LFinger => 8,
_ => throw new InvalidEnumArgumentException(),
};
}
public static EqdpEntry FromSlotAndBits( EquipSlot slot, bool bit1, bool bit2 )
{
EqdpEntry ret = 0;
var offset = Offset( slot );
if( bit1 )
{
ret |= ( EqdpEntry )( 1 << offset );
}
if( bit2 )
{
ret |= ( EqdpEntry )( 1 << ( offset + 1 ) );
}
return ret;
}
public static EqdpEntry Mask( EquipSlot slot )
{
return slot switch
{
EquipSlot.Head => EqdpEntry.HeadMask,
EquipSlot.Body => EqdpEntry.BodyMask,
EquipSlot.Hands => EqdpEntry.HandsMask,
EquipSlot.Legs => EqdpEntry.LegsMask,
EquipSlot.Feet => EqdpEntry.FeetMask,
EquipSlot.Ears => EqdpEntry.EarsMask,
EquipSlot.Neck => EqdpEntry.NeckMask,
EquipSlot.Wrists => EqdpEntry.WristsMask,
EquipSlot.RFinger => EqdpEntry.RingRMask,
EquipSlot.LFinger => EqdpEntry.RingLMask,
_ => 0,
};
}
}
}

View file

@ -1,308 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;
using Penumbra.GameData.Enums;
namespace Penumbra.GameData.Structs
{
[Flags]
public enum EqpEntry : ulong
{
BodyEnabled = 0x00_01ul,
BodyHideWaist = 0x00_02ul,
_2 = 0x00_04ul,
BodyHideGlovesS = 0x00_08ul,
_4 = 0x00_10ul,
BodyHideGlovesM = 0x00_20ul,
BodyHideGlovesL = 0x00_40ul,
BodyHideGorget = 0x00_80ul,
BodyShowLeg = 0x01_00ul,
BodyShowHand = 0x02_00ul,
BodyShowHead = 0x04_00ul,
BodyShowNecklace = 0x08_00ul,
BodyShowBracelet = 0x10_00ul,
BodyShowTail = 0x20_00ul,
_14 = 0x40_00ul,
_15 = 0x80_00ul,
BodyMask = 0xFF_FFul,
LegsEnabled = 0x01ul << 16,
LegsHideKneePads = 0x02ul << 16,
LegsHideBootsS = 0x04ul << 16,
LegsHideBootsM = 0x08ul << 16,
_20 = 0x10ul << 16,
LegsShowFoot = 0x20ul << 16,
LegsShowTail = 0x40ul << 16,
_23 = 0x80ul << 16,
LegsMask = 0xFFul << 16,
HandsEnabled = 0x01ul << 24,
HandsHideElbow = 0x02ul << 24,
HandsHideForearm = 0x04ul << 24,
_27 = 0x08ul << 24,
HandShowBracelet = 0x10ul << 24,
HandShowRingL = 0x20ul << 24,
HandShowRingR = 0x40ul << 24,
_31 = 0x80ul << 24,
HandsMask = 0xFFul << 24,
FeetEnabled = 0x01ul << 32,
FeetHideKnee = 0x02ul << 32,
FeetHideCalf = 0x04ul << 32,
FeetHideAnkle = 0x08ul << 32,
_36 = 0x10ul << 32,
_37 = 0x20ul << 32,
_38 = 0x40ul << 32,
_39 = 0x80ul << 32,
FeetMask = 0xFFul << 32,
HeadEnabled = 0x00_00_01ul << 40,
HeadHideScalp = 0x00_00_02ul << 40,
HeadHideHair = 0x00_00_04ul << 40,
HeadShowHairOverride = 0x00_00_08ul << 40,
HeadHideNeck = 0x00_00_10ul << 40,
HeadShowNecklace = 0x00_00_20ul << 40,
_46 = 0x00_00_40ul << 40,
HeadShowEarrings = 0x00_00_80ul << 40,
HeadShowEarringsHuman = 0x00_01_00ul << 40,
HeadShowEarringsAura = 0x00_02_00ul << 40,
HeadShowEarHuman = 0x00_04_00ul << 40,
HeadShowEarMiqote = 0x00_08_00ul << 40,
HeadShowEarAuRa = 0x00_10_00ul << 40,
HeadShowEarViera = 0x00_20_00ul << 40,
_54 = 0x00_40_00ul << 40,
_55 = 0x00_80_00ul << 40,
HeadShowHrothgarHat = 0x01_00_00ul << 40,
HeadShowVieraHat = 0x02_00_00ul << 40,
_58 = 0x04_00_00ul << 40,
_59 = 0x08_00_00ul << 40,
_60 = 0x10_00_00ul << 40,
_61 = 0x20_00_00ul << 40,
_62 = 0x40_00_00ul << 40,
_63 = 0x80_00_00ul << 40,
HeadMask = 0xFF_FF_FFul << 40,
}
public static class Eqp
{
public static (int, int) BytesAndOffset( EquipSlot slot )
{
return slot switch
{
EquipSlot.Body => ( 2, 0 ),
EquipSlot.Legs => ( 1, 2 ),
EquipSlot.Hands => ( 1, 3 ),
EquipSlot.Feet => ( 1, 4 ),
EquipSlot.Head => ( 3, 5 ),
_ => throw new InvalidEnumArgumentException(),
};
}
public static EqpEntry FromSlotAndBytes( EquipSlot slot, byte[] value )
{
EqpEntry ret = 0;
var (bytes, offset) = BytesAndOffset( slot );
if( bytes != value.Length )
{
throw new ArgumentException();
}
for( var i = 0; i < bytes; ++i )
{
ret |= ( EqpEntry )( ( ulong )value[ i ] << ( ( offset + i ) * 8 ) );
}
return ret;
}
public static EqpEntry Mask( EquipSlot slot )
{
return slot switch
{
EquipSlot.Body => EqpEntry.BodyMask,
EquipSlot.Head => EqpEntry.HeadMask,
EquipSlot.Legs => EqpEntry.LegsMask,
EquipSlot.Feet => EqpEntry.FeetMask,
EquipSlot.Hands => EqpEntry.HandsMask,
_ => 0,
};
}
public static EquipSlot ToEquipSlot( this EqpEntry entry )
{
return entry switch
{
EqpEntry.BodyEnabled => EquipSlot.Body,
EqpEntry.BodyHideWaist => EquipSlot.Body,
EqpEntry._2 => EquipSlot.Body,
EqpEntry.BodyHideGlovesS => EquipSlot.Body,
EqpEntry._4 => EquipSlot.Body,
EqpEntry.BodyHideGlovesM => EquipSlot.Body,
EqpEntry.BodyHideGlovesL => EquipSlot.Body,
EqpEntry.BodyHideGorget => EquipSlot.Body,
EqpEntry.BodyShowLeg => EquipSlot.Body,
EqpEntry.BodyShowHand => EquipSlot.Body,
EqpEntry.BodyShowHead => EquipSlot.Body,
EqpEntry.BodyShowNecklace => EquipSlot.Body,
EqpEntry.BodyShowBracelet => EquipSlot.Body,
EqpEntry.BodyShowTail => EquipSlot.Body,
EqpEntry._14 => EquipSlot.Body,
EqpEntry._15 => EquipSlot.Body,
EqpEntry.LegsEnabled => EquipSlot.Legs,
EqpEntry.LegsHideKneePads => EquipSlot.Legs,
EqpEntry.LegsHideBootsS => EquipSlot.Legs,
EqpEntry.LegsHideBootsM => EquipSlot.Legs,
EqpEntry._20 => EquipSlot.Legs,
EqpEntry.LegsShowFoot => EquipSlot.Legs,
EqpEntry.LegsShowTail => EquipSlot.Legs,
EqpEntry._23 => EquipSlot.Legs,
EqpEntry.HandsEnabled => EquipSlot.Hands,
EqpEntry.HandsHideElbow => EquipSlot.Hands,
EqpEntry.HandsHideForearm => EquipSlot.Hands,
EqpEntry._27 => EquipSlot.Hands,
EqpEntry.HandShowBracelet => EquipSlot.Hands,
EqpEntry.HandShowRingL => EquipSlot.Hands,
EqpEntry.HandShowRingR => EquipSlot.Hands,
EqpEntry._31 => EquipSlot.Hands,
EqpEntry.FeetEnabled => EquipSlot.Feet,
EqpEntry.FeetHideKnee => EquipSlot.Feet,
EqpEntry.FeetHideCalf => EquipSlot.Feet,
EqpEntry.FeetHideAnkle => EquipSlot.Feet,
EqpEntry._36 => EquipSlot.Feet,
EqpEntry._37 => EquipSlot.Feet,
EqpEntry._38 => EquipSlot.Feet,
EqpEntry._39 => EquipSlot.Feet,
EqpEntry.HeadEnabled => EquipSlot.Head,
EqpEntry.HeadHideScalp => EquipSlot.Head,
EqpEntry.HeadHideHair => EquipSlot.Head,
EqpEntry.HeadShowHairOverride => EquipSlot.Head,
EqpEntry.HeadHideNeck => EquipSlot.Head,
EqpEntry.HeadShowNecklace => EquipSlot.Head,
EqpEntry._46 => EquipSlot.Head,
EqpEntry.HeadShowEarrings => EquipSlot.Head,
EqpEntry.HeadShowEarringsHuman => EquipSlot.Head,
EqpEntry.HeadShowEarringsAura => EquipSlot.Head,
EqpEntry.HeadShowEarHuman => EquipSlot.Head,
EqpEntry.HeadShowEarMiqote => EquipSlot.Head,
EqpEntry.HeadShowEarAuRa => EquipSlot.Head,
EqpEntry.HeadShowEarViera => EquipSlot.Head,
EqpEntry._54 => EquipSlot.Head,
EqpEntry._55 => EquipSlot.Head,
EqpEntry.HeadShowHrothgarHat => EquipSlot.Head,
EqpEntry.HeadShowVieraHat => EquipSlot.Head,
EqpEntry._58 => EquipSlot.Head,
EqpEntry._59 => EquipSlot.Head,
EqpEntry._60 => EquipSlot.Head,
EqpEntry._61 => EquipSlot.Head,
EqpEntry._62 => EquipSlot.Head,
EqpEntry._63 => EquipSlot.Head,
_ => EquipSlot.Unknown,
};
}
public static string ToLocalName( this EqpEntry entry )
{
return entry switch
{
EqpEntry.BodyEnabled => "Enabled",
EqpEntry.BodyHideWaist => "Hide Waist",
EqpEntry._2 => "Unknown 2",
EqpEntry.BodyHideGlovesS => "Hide Small Gloves",
EqpEntry._4 => "Unknown 4",
EqpEntry.BodyHideGlovesM => "Hide Medium Gloves",
EqpEntry.BodyHideGlovesL => "Hide Large Gloves",
EqpEntry.BodyHideGorget => "Hide Gorget",
EqpEntry.BodyShowLeg => "Show Legs",
EqpEntry.BodyShowHand => "Show Hands",
EqpEntry.BodyShowHead => "Show Head",
EqpEntry.BodyShowNecklace => "Show Necklace",
EqpEntry.BodyShowBracelet => "Show Bracelet",
EqpEntry.BodyShowTail => "Show Tail",
EqpEntry._14 => "Unknown 14",
EqpEntry._15 => "Unknown 15",
EqpEntry.LegsEnabled => "Enabled",
EqpEntry.LegsHideKneePads => "Hide Knee Pads",
EqpEntry.LegsHideBootsS => "Hide Small Boots",
EqpEntry.LegsHideBootsM => "Hide Medium Boots",
EqpEntry._20 => "Unknown 20",
EqpEntry.LegsShowFoot => "Show Foot",
EqpEntry.LegsShowTail => "Show Tail",
EqpEntry._23 => "Unknown 23",
EqpEntry.HandsEnabled => "Enabled",
EqpEntry.HandsHideElbow => "Hide Elbow",
EqpEntry.HandsHideForearm => "Hide Forearm",
EqpEntry._27 => "Unknown 27",
EqpEntry.HandShowBracelet => "Show Bracelet",
EqpEntry.HandShowRingL => "Show Left Ring",
EqpEntry.HandShowRingR => "Show Right Ring",
EqpEntry._31 => "Unknown 31",
EqpEntry.FeetEnabled => "Enabled",
EqpEntry.FeetHideKnee => "Hide Knees",
EqpEntry.FeetHideCalf => "Hide Calves",
EqpEntry.FeetHideAnkle => "Hide Ankles",
EqpEntry._36 => "Unknown 36",
EqpEntry._37 => "Unknown 37",
EqpEntry._38 => "Unknown 38",
EqpEntry._39 => "Unknown 39",
EqpEntry.HeadEnabled => "Enabled",
EqpEntry.HeadHideScalp => "Hide Scalp",
EqpEntry.HeadHideHair => "Hide Hair",
EqpEntry.HeadShowHairOverride => "Show Hair Override",
EqpEntry.HeadHideNeck => "Hide Neck",
EqpEntry.HeadShowNecklace => "Show Necklace",
EqpEntry._46 => "Unknown 46",
EqpEntry.HeadShowEarrings => "Show Earrings",
EqpEntry.HeadShowEarringsHuman => "Show Earrings (Human)",
EqpEntry.HeadShowEarringsAura => "Show Earrings (Au Ra)",
EqpEntry.HeadShowEarHuman => "Show Ears (Human)",
EqpEntry.HeadShowEarMiqote => "Show Ears (Miqo'te)",
EqpEntry.HeadShowEarAuRa => "Show Ears (Au Ra)",
EqpEntry.HeadShowEarViera => "Show Ears (Viera)",
EqpEntry._54 => "Unknown 54",
EqpEntry._55 => "Unknown 55",
EqpEntry.HeadShowHrothgarHat => "Show on Hrothgar",
EqpEntry.HeadShowVieraHat => "Show on Viera",
EqpEntry._58 => "Unknown 58",
EqpEntry._59 => "Unknown 59",
EqpEntry._60 => "Unknown 60",
EqpEntry._61 => "Unknown 61",
EqpEntry._62 => "Unknown 62",
EqpEntry._63 => "Unknown 63",
_ => throw new InvalidEnumArgumentException(),
};
}
private static EqpEntry[] GetEntriesForSlot( EquipSlot slot )
{
return ( ( EqpEntry[] )Enum.GetValues( typeof( EqpEntry ) ) )
.Where( e => e.ToEquipSlot() == slot )
.ToArray();
}
public static readonly EqpEntry[] EqpAttributesBody = GetEntriesForSlot( EquipSlot.Body );
public static readonly EqpEntry[] EqpAttributesLegs = GetEntriesForSlot( EquipSlot.Legs );
public static readonly EqpEntry[] EqpAttributesHands = GetEntriesForSlot( EquipSlot.Hands );
public static readonly EqpEntry[] EqpAttributesFeet = GetEntriesForSlot( EquipSlot.Feet );
public static readonly EqpEntry[] EqpAttributesHead = GetEntriesForSlot( EquipSlot.Head );
public static IReadOnlyDictionary< EquipSlot, EqpEntry[] > EqpAttributes = new Dictionary< EquipSlot, EqpEntry[] >()
{
[ EquipSlot.Body ] = EqpAttributesBody,
[ EquipSlot.Legs ] = EqpAttributesLegs,
[ EquipSlot.Hands ] = EqpAttributesHands,
[ EquipSlot.Feet ] = EqpAttributesFeet,
[ EquipSlot.Head ] = EqpAttributesHead,
};
}
}

View file

@ -1,160 +0,0 @@
using System;
using System.Runtime.InteropServices;
using Dalamud;
using Penumbra.GameData.Enums;
namespace Penumbra.GameData.Structs
{
[StructLayout( LayoutKind.Explicit )]
public struct GameObjectInfo : IComparable
{
public static GameObjectInfo Equipment( FileType type, ushort setId, GenderRace gr = GenderRace.Unknown
, EquipSlot slot = EquipSlot.Unknown, byte variant = 0 )
=> new()
{
FileType = type,
ObjectType = slot.IsAccessory() ? ObjectType.Accessory : ObjectType.Equipment,
PrimaryId = setId,
GenderRace = gr,
Variant = variant,
EquipSlot = slot,
};
public static GameObjectInfo Weapon( FileType type, ushort setId, ushort weaponId, byte variant = 0 )
=> new()
{
FileType = type,
ObjectType = ObjectType.Weapon,
PrimaryId = setId,
SecondaryId = weaponId,
Variant = variant,
};
public static GameObjectInfo Customization( FileType type, CustomizationType customizationType, ushort id = 0
, GenderRace gr = GenderRace.Unknown, BodySlot bodySlot = BodySlot.Unknown, byte variant = 0 )
=> new()
{
FileType = type,
ObjectType = ObjectType.Character,
PrimaryId = id,
GenderRace = gr,
BodySlot = bodySlot,
Variant = variant,
CustomizationType = customizationType,
};
public static GameObjectInfo Monster( FileType type, ushort monsterId, ushort bodyId, byte variant = 0 )
=> new()
{
FileType = type,
ObjectType = ObjectType.Monster,
PrimaryId = monsterId,
SecondaryId = bodyId,
Variant = variant,
};
public static GameObjectInfo DemiHuman( FileType type, ushort demiHumanId, ushort bodyId, EquipSlot slot = EquipSlot.Unknown,
byte variant = 0
)
=> new()
{
FileType = type,
ObjectType = ObjectType.DemiHuman,
PrimaryId = demiHumanId,
SecondaryId = bodyId,
Variant = variant,
EquipSlot = slot,
};
public static GameObjectInfo Map( FileType type, byte c1, byte c2, byte c3, byte c4, byte variant, byte suffix = 0 )
=> new()
{
FileType = type,
ObjectType = ObjectType.Map,
MapC1 = c1,
MapC2 = c2,
MapC3 = c3,
MapC4 = c4,
MapSuffix = suffix,
Variant = variant,
};
public static GameObjectInfo Icon( FileType type, uint iconId, bool hq, ClientLanguage lang = ClientLanguage.English )
=> new()
{
FileType = type,
ObjectType = ObjectType.Map,
IconId = iconId,
IconHq = hq,
Language = lang,
};
[FieldOffset( 0 )]
public readonly ulong Identifier;
[FieldOffset( 0 )]
public FileType FileType;
[FieldOffset( 1 )]
public ObjectType ObjectType;
[FieldOffset( 2 )]
public ushort PrimaryId; // Equipment, Weapon, Customization, Monster, DemiHuman
[FieldOffset( 2 )]
public uint IconId; // Icon
[FieldOffset( 2 )]
public byte MapC1; // Map
[FieldOffset( 3 )]
public byte MapC2; // Map
[FieldOffset( 4 )]
public ushort SecondaryId; // Weapon, Monster, Demihuman
[FieldOffset( 4 )]
public byte MapC3; // Map
[FieldOffset( 4 )]
private byte _genderRaceByte; // Equipment, Customization
public GenderRace GenderRace
{
get => Names.GenderRaceFromByte( _genderRaceByte );
set => _genderRaceByte = value.ToByte();
}
[FieldOffset( 5 )]
public BodySlot BodySlot; // Customization
[FieldOffset( 5 )]
public byte MapC4; // Map
[FieldOffset( 6 )]
public byte Variant; // Equipment, Weapon, Customization, Map, Monster, Demihuman
[FieldOffset( 6 )]
public bool IconHq; // Icon
[FieldOffset( 7 )]
public EquipSlot EquipSlot; // Equipment, Demihuman
[FieldOffset( 7 )]
public CustomizationType CustomizationType; // Customization
[FieldOffset( 7 )]
public ClientLanguage Language; // Icon
[FieldOffset( 7 )]
public byte MapSuffix;
public override int GetHashCode()
=> Identifier.GetHashCode();
public int CompareTo( object? r )
=> Identifier.CompareTo( r );
}
}

View file

@ -1,92 +0,0 @@
using System.IO;
namespace Penumbra.GameData.Structs
{
public struct GmpEntry
{
public bool Enabled
{
get => ( Value & 1 ) == 1;
set
{
if( value )
{
Value |= 1ul;
}
else
{
Value &= ~1ul;
}
}
}
public bool Animated
{
get => ( Value & 2 ) == 2;
set
{
if( value )
{
Value |= 2ul;
}
else
{
Value &= ~2ul;
}
}
}
public ushort RotationA
{
get => ( ushort )( ( Value >> 2 ) & 0x3FF );
set => Value = ( Value & ~0xFFCul ) | ( ( value & 0x3FFul ) << 2 );
}
public ushort RotationB
{
get => ( ushort )( ( Value >> 12 ) & 0x3FF );
set => Value = ( Value & ~0x3FF000ul ) | ( ( value & 0x3FFul ) << 12 );
}
public ushort RotationC
{
get => ( ushort )( ( Value >> 22 ) & 0x3FF );
set => Value = ( Value & ~0xFFC00000ul ) | ( ( value & 0x3FFul ) << 22 );
}
public byte UnknownA
{
get => ( byte )( ( Value >> 32 ) & 0x0F );
set => Value = ( Value & ~0x0F00000000ul ) | ( ( value & 0x0Ful ) << 32 );
}
public byte UnknownB
{
get => ( byte )( ( Value >> 36 ) & 0x0F );
set => Value = ( Value & ~0xF000000000ul ) | ( ( value & 0x0Ful ) << 36 );
}
public byte UnknownTotal
{
get => ( byte )( ( Value >> 32 ) & 0xFF );
set => Value = ( Value & ~0xFF00000000ul ) | ( ( value & 0xFFul ) << 32 );
}
public ulong Value { get; set; }
public static GmpEntry FromTexToolsMeta( byte[] data )
{
GmpEntry ret = new();
using var reader = new BinaryReader( new MemoryStream( data ) );
ret.Value = reader.ReadUInt32();
ret.UnknownTotal = data[ 4 ];
return ret;
}
public static implicit operator ulong( GmpEntry entry )
=> entry.Value;
public static explicit operator GmpEntry( ulong entry )
=> new() { Value = entry };
}
}

View file

@ -1,58 +0,0 @@
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using Penumbra.GameData.Enums;
namespace Penumbra.GameData.Structs
{
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
public readonly struct RspEntry
{
public const int ByteSize = ( int )RspAttribute.NumAttributes * 4;
private readonly float[] Attributes;
public RspEntry( RspEntry copy )
=> Attributes = ( float[] )copy.Attributes.Clone();
public RspEntry( byte[] bytes, int offset )
{
if( offset < 0 || offset + ByteSize > bytes.Length )
{
throw new ArgumentOutOfRangeException();
}
Attributes = new float[( int )RspAttribute.NumAttributes];
using MemoryStream s = new( bytes ) { Position = offset };
using BinaryReader br = new( s );
for( var i = 0; i < ( int )RspAttribute.NumAttributes; ++i )
{
Attributes[ i ] = br.ReadSingle();
}
}
private static int ToIndex( RspAttribute attribute )
=> attribute < RspAttribute.NumAttributes && attribute >= 0
? ( int )attribute
: throw new InvalidEnumArgumentException();
public float this[ RspAttribute attribute ]
{
get => Attributes[ ToIndex( attribute ) ];
set => Attributes[ ToIndex( attribute ) ] = value;
}
public byte[] ToBytes()
{
using var s = new MemoryStream( ByteSize );
using var bw = new BinaryWriter( s );
foreach( var attribute in Attributes )
{
bw.Write( attribute );
}
return s.ToArray();
}
}
}

View file

@ -1,24 +0,0 @@
using System;
namespace Penumbra.GameData.Structs
{
public readonly struct SetId : IComparable< SetId >
{
public readonly ushort Value;
public SetId( ushort value )
=> Value = value;
public static implicit operator SetId( ushort id )
=> new( id );
public static explicit operator ushort( SetId id )
=> id.Value;
public override string ToString()
=> Value.ToString();
public int CompareTo( SetId other )
=> Value.CompareTo( other.Value );
}
}

View file

@ -1,30 +0,0 @@
using System;
namespace Penumbra.GameData.Structs
{
public readonly struct StainId : IEquatable< StainId >
{
public readonly byte Value;
public StainId( byte value )
=> Value = value;
public static implicit operator StainId( byte id )
=> new( id );
public static explicit operator byte( StainId id )
=> id.Value;
public override string ToString()
=> Value.ToString();
public bool Equals( StainId other )
=> Value == other.Value;
public override bool Equals( object? obj )
=> obj is StainId other && Equals( other );
public override int GetHashCode()
=> Value.GetHashCode();
}
}

View file

@ -1,30 +0,0 @@
using System;
namespace Penumbra.GameData.Structs
{
public readonly struct WeaponType : IEquatable< WeaponType >
{
public readonly ushort Value;
public WeaponType( ushort value )
=> Value = value;
public static implicit operator WeaponType( ushort id )
=> new( id );
public static explicit operator ushort( WeaponType id )
=> id.Value;
public override string ToString()
=> Value.ToString();
public bool Equals( WeaponType other )
=> Value == other.Value;
public override bool Equals( object? obj )
=> obj is WeaponType other && Equals( other );
public override int GetHashCode()
=> Value.GetHashCode();
}
}

View file

@ -1,105 +0,0 @@
using System;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Penumbra.GameData.Util
{
public readonly struct GamePath : IComparable
{
public const int MaxGamePathLength = 256;
private readonly string _path;
private GamePath( string path, bool _ )
=> _path = path;
public GamePath( string? path )
{
if( path != null && path.Length < MaxGamePathLength )
{
_path = Lower( Trim( ReplaceSlash( path ) ) );
}
else
{
_path = "";
}
}
public GamePath( FileInfo file, DirectoryInfo baseDir )
=> _path = CheckPre( file, baseDir ) ? Lower( Trim( ReplaceSlash( Substring( file, baseDir ) ) ) ) : "";
private static bool CheckPre( FileInfo file, DirectoryInfo baseDir )
=> file.FullName.StartsWith( baseDir.FullName ) && file.FullName.Length < MaxGamePathLength;
private static string Substring( FileInfo file, DirectoryInfo baseDir )
=> file.FullName.Substring( baseDir.FullName.Length );
private static string ReplaceSlash( string path )
=> path.Replace( '\\', '/' );
private static string Trim( string path )
=> path.TrimStart( '/' );
private static string Lower( string path )
=> path.ToLowerInvariant();
public static GamePath GenerateUnchecked( string path )
=> new( path, true );
public static GamePath GenerateUncheckedLower( string path )
=> new( Lower( path ), true );
public static implicit operator string( GamePath gamePath )
=> gamePath._path;
public static explicit operator GamePath( string gamePath )
=> new( gamePath );
public bool Empty
=> _path.Length == 0;
public string Filename()
{
var idx = _path.LastIndexOf( "/", StringComparison.Ordinal );
return idx == -1 ? _path : idx == _path.Length - 1 ? "" : _path.Substring( idx + 1 );
}
public int CompareTo( object? rhs )
{
return rhs switch
{
string path => string.Compare( _path, path, StringComparison.InvariantCulture ),
GamePath path => string.Compare( _path, path._path, StringComparison.InvariantCulture ),
_ => -1,
};
}
public override string ToString()
=> _path;
}
public class GamePathConverter : JsonConverter
{
public override bool CanConvert( Type objectType )
=> objectType == typeof( GamePath );
public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer )
{
var token = JToken.Load( reader );
return token.ToObject< GamePath >();
}
public override bool CanWrite
=> true;
public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer )
{
if( value != null )
{
var v = ( GamePath )value;
serializer.Serialize( writer, v.ToString() );
}
}
}
}

View file

@ -1,62 +0,0 @@
using System;
using System.Reflection;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
namespace Penumbra.PlayerWatch
{
public static class CharacterFactory
{
private static ConstructorInfo? _characterConstructor = null;
private static void Initialize()
{
_characterConstructor ??= typeof( Character ).GetConstructor( BindingFlags.NonPublic | BindingFlags.Instance, null, new[]
{
typeof( IntPtr ),
}, null )!;
}
private static Character Character( IntPtr address )
{
Initialize();
return ( Character )_characterConstructor?.Invoke( new object[]
{
address,
} )!;
}
public static Character? Convert( GameObject? actor )
{
if( actor == null )
{
return null;
}
return actor switch
{
PlayerCharacter p => p,
BattleChara b => b,
_ => actor.ObjectKind switch
{
ObjectKind.BattleNpc => Character( actor.Address ),
ObjectKind.Companion => Character( actor.Address ),
ObjectKind.EventNpc => Character( actor.Address ),
_ => null,
},
};
}
}
public static class GameObjectExtensions
{
private const int ModelTypeOffset = 0x01B4;
public static unsafe int ModelType( this GameObject actor )
=> *( int* )( actor.Address + ModelTypeOffset );
public static unsafe void SetModelType( this GameObject actor, int value )
=> *( int* )( actor.Address + ModelTypeOffset ) = value;
}
}

View file

@ -1,31 +0,0 @@
using System;
using System.Collections.Generic;
using Dalamud.Game.ClientState.Objects.Types;
using Penumbra.GameData.Structs;
namespace Penumbra.PlayerWatch
{
public delegate void PlayerChange( Character actor );
public interface IPlayerWatcherBase : IDisposable
{
public int Version { get; }
public bool Valid { get; }
}
public interface IPlayerWatcher : IPlayerWatcherBase
{
public event PlayerChange? PlayerChanged;
public bool Active { get; }
public void Enable();
public void Disable();
public void SetStatus( bool enabled );
public void AddPlayerToWatch( string playerName );
public void RemovePlayerFromWatch( string playerName );
public CharacterEquipment UpdatePlayerWithoutEvent( Character actor );
public IEnumerable< (string, CharacterEquipment) > WatchedPlayers();
}
}

View file

@ -1,40 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0-windows</TargetFramework>
<LangVersion>preview</LangVersion>
<PlatformTarget>x64</PlatformTarget>
<AssemblyTitle>Penumbra.PlayerWatch</AssemblyTitle>
<Company>absolute gangstas</Company>
<Product>Penumbra</Product>
<Copyright>Copyright © 2020</Copyright>
<FileVersion>1.0.0.0</FileVersion>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<OutputPath>bin\$(Configuration)\</OutputPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugType>full</DebugType>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
</PropertyGroup>
<PropertyGroup>
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
</PropertyGroup>
<ItemGroup>
<Reference Include="Dalamud">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Penumbra.GameData\Penumbra.GameData.csproj" />
</ItemGroup>
</Project>

View file

@ -1,277 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Logging;
using Penumbra.GameData.Structs;
namespace Penumbra.PlayerWatch
{
internal class PlayerWatchBase : IDisposable
{
public const int GPosePlayerIdx = 201;
public const int GPoseTableEnd = GPosePlayerIdx + 48;
private const int ObjectsPerFrame = 32;
private readonly Framework _framework;
private readonly ClientState _clientState;
private readonly ObjectTable _objects;
internal readonly HashSet< PlayerWatcher > RegisteredWatchers = new();
internal readonly Dictionary< string, (CharacterEquipment, HashSet< PlayerWatcher >) > Equip = new();
private int _frameTicker;
private bool _inGPose;
private bool _enabled;
private bool _cancel;
internal PlayerWatchBase( Framework framework, ClientState clientState, ObjectTable objects )
{
_framework = framework;
_clientState = clientState;
_objects = objects;
}
internal void RegisterWatcher( PlayerWatcher watcher )
{
RegisteredWatchers.Add( watcher );
if( watcher.Active )
{
EnablePlayerWatch();
}
}
internal void UnregisterWatcher( PlayerWatcher watcher )
{
if( RegisteredWatchers.Remove( watcher ) )
{
foreach( var items in Equip.Values )
{
items.Item2.Remove( watcher );
}
}
CheckActiveStatus();
}
internal void CheckActiveStatus()
{
if( RegisteredWatchers.Any( w => w.Active ) )
{
EnablePlayerWatch();
}
else
{
DisablePlayerWatch();
}
}
internal CharacterEquipment UpdatePlayerWithoutEvent( Character actor )
{
var equipment = new CharacterEquipment( actor );
if( Equip.ContainsKey( actor.Name.ToString() ) )
{
Equip[ actor.Name.ToString() ] = ( equipment, Equip[ actor.Name.ToString() ].Item2 );
}
return equipment;
}
internal void AddPlayerToWatch( string playerName, PlayerWatcher watcher )
{
if( Equip.TryGetValue( playerName, out var items ) )
{
items.Item2.Add( watcher );
}
else
{
Equip[ playerName ] = ( new CharacterEquipment(), new HashSet< PlayerWatcher > { watcher } );
}
}
public void RemovePlayerFromWatch( string playerName, PlayerWatcher watcher )
{
if( Equip.TryGetValue( playerName, out var items ) )
{
items.Item2.Remove( watcher );
if( items.Item2.Count == 0 )
{
Equip.Remove( playerName );
}
}
}
internal void EnablePlayerWatch()
{
if( !_enabled )
{
_enabled = true;
_framework.Update += OnFrameworkUpdate;
_clientState.TerritoryChanged += OnTerritoryChange;
_clientState.Logout += OnLogout;
}
}
internal void DisablePlayerWatch()
{
if( _enabled )
{
_enabled = false;
_framework.Update -= OnFrameworkUpdate;
_clientState.TerritoryChanged -= OnTerritoryChange;
_clientState.Logout -= OnLogout;
}
}
public void Dispose()
=> DisablePlayerWatch();
private void OnTerritoryChange( object? _1, ushort _2 )
=> Clear();
private void OnLogout( object? _1, object? _2 )
=> Clear();
internal void Clear()
{
PluginLog.Debug( "Clearing PlayerWatcher Store." );
_cancel = true;
foreach( var kvp in Equip )
{
kvp.Value.Item1.Clear();
}
_frameTicker = 0;
}
private static void TriggerEvents( IEnumerable< PlayerWatcher > watchers, Character player )
{
PluginLog.Debug( "Triggering events for {PlayerName} at {Address}.", player.Name, player.Address );
foreach( var watcher in watchers.Where( w => w.Active ) )
{
watcher.Trigger( player );
}
}
internal void TriggerGPose()
{
for( var i = GPosePlayerIdx; i < GPoseTableEnd; ++i )
{
var player = _objects[ i ];
if( player == null )
{
return;
}
if( Equip.TryGetValue( player.Name.ToString(), out var watcher ) )
{
TriggerEvents( watcher.Item2, ( Character )player );
}
}
}
private Character? CheckGPoseObject( GameObject player )
{
if( !_inGPose )
{
return CharacterFactory.Convert( player );
}
for( var i = GPosePlayerIdx; i < GPoseTableEnd; ++i )
{
var a = _objects[ i ];
if( a == null )
{
return CharacterFactory.Convert( player);
}
if( a.Name == player.Name )
{
return CharacterFactory.Convert( a );
}
}
return CharacterFactory.Convert(player)!;
}
private bool TryGetPlayer( GameObject gameObject, out (CharacterEquipment, HashSet< PlayerWatcher >) equip )
{
equip = default;
var name = gameObject.Name.ToString();
return name.Length != 0 && Equip.TryGetValue( name, out equip );
}
private static bool InvalidObjectKind( ObjectKind kind )
{
return kind switch
{
ObjectKind.BattleNpc => false,
ObjectKind.EventNpc => false,
ObjectKind.Player => false,
_ => true,
};
}
private GameObject? GetNextObject()
{
if( _frameTicker == GPosePlayerIdx - 1 )
_frameTicker = GPoseTableEnd;
else if( _frameTicker == _objects.Length - 1 )
_frameTicker = 0;
else
++_frameTicker;
return _objects[ _frameTicker ];
}
private void OnFrameworkUpdate( object framework )
{
var newInGPose = _objects[ GPosePlayerIdx ] != null;
if( newInGPose != _inGPose )
{
if( newInGPose )
{
TriggerGPose();
}
else
{
Clear();
}
_inGPose = newInGPose;
}
for( var i = 0; i < ObjectsPerFrame; ++i )
{
var actor = GetNextObject();
if( actor == null
|| InvalidObjectKind(actor.ObjectKind)
|| !TryGetPlayer( actor, out var equip ) )
{
continue;
}
var character = CheckGPoseObject( actor );
if( _cancel )
{
_cancel = false;
return;
}
if( character == null || character.ModelType() != 0 )
{
continue;
}
PluginLog.Verbose( "Comparing Gear for {PlayerName} at {Address}...", character.Name, character.Address );
if( !equip.Item1.CompareAndUpdate( character ) )
{
TriggerEvents( equip.Item2, character );
}
}
}
}
}

View file

@ -1,103 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types;
using Penumbra.GameData.Structs;
namespace Penumbra.PlayerWatch
{
public class PlayerWatcher : IPlayerWatcher
{
public int Version { get; } = 2;
private static PlayerWatchBase? _playerWatch;
public event PlayerChange? PlayerChanged;
public bool Active { get; set; } = true;
public bool Valid
=> _playerWatch != null;
internal PlayerWatcher( Framework framework, ClientState clientState, ObjectTable objects )
{
_playerWatch ??= new PlayerWatchBase( framework, clientState, objects );
_playerWatch.RegisterWatcher( this );
}
public void Enable()
=> SetStatus( true );
public void Disable()
=> SetStatus( false );
public void SetStatus( bool enabled )
{
Active = enabled && Valid;
_playerWatch?.CheckActiveStatus();
}
internal void Trigger( Character actor )
=> PlayerChanged?.Invoke( actor );
public void Dispose()
{
if( _playerWatch == null )
{
return;
}
Active = false;
PlayerChanged = null;
_playerWatch.UnregisterWatcher( this );
if( _playerWatch.RegisteredWatchers.Count == 0 )
{
_playerWatch.Dispose();
_playerWatch = null;
}
}
private void CheckValidity()
{
if( !Valid )
{
throw new Exception( $"PlayerWatch was already disposed." );
}
}
public void AddPlayerToWatch( string name )
{
CheckValidity();
_playerWatch!.AddPlayerToWatch( name, this );
}
public void RemovePlayerFromWatch( string playerName )
{
CheckValidity();
_playerWatch!.RemovePlayerFromWatch( playerName, this );
}
public CharacterEquipment UpdatePlayerWithoutEvent( Character actor )
{
CheckValidity();
return _playerWatch!.UpdatePlayerWithoutEvent( actor );
}
public IEnumerable< (string, CharacterEquipment) > WatchedPlayers()
{
CheckValidity();
return _playerWatch!.Equip
.Where( kvp => kvp.Value.Item2.Contains( this ) )
.Select( kvp => ( kvp.Key, kvp.Value.Item1 ) );
}
}
public static class PlayerWatchFactory
{
public static IPlayerWatcher Create( Framework framework, ClientState clientState, ObjectTable objects )
=> new PlayerWatcher( framework, clientState, objects );
}
}

1
Penumbra.String Submodule

@ -0,0 +1 @@
Subproject commit 9bd016fbef5fb2de467dd42165267fdd93cd9592

View file

@ -1,41 +1,98 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29709.97
# Visual Studio Version 17
VisualStudioVersion = 17.2.32210.308
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra", "Penumbra\Penumbra.csproj", "{13C812E9-0D42-4B95-8646-40EEBF30636F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F89C9EAE-25C8-43BE-8108-5921E5A93502}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.github\workflows\build.yml = .github\workflows\build.yml
Penumbra\Penumbra.json = Penumbra\Penumbra.json
.github\workflows\release.yml = .github\workflows\release.yml
repo.json = repo.json
.github\workflows\test_release.yml = .github\workflows\test_release.yml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "Penumbra.GameData\Penumbra.GameData.csproj", "{EE551E87-FDB3-4612-B500-DC870C07C605}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.PlayerWatch", "Penumbra.PlayerWatch\Penumbra.PlayerWatch.csproj", "{01685BD8-8847-4B49-BF90-1683B4C76B0E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OtterGui", "OtterGui\OtterGui.csproj", "{87750518-1A20-40B4-9FC1-22F906EFB290}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.Api", "Penumbra.Api\Penumbra.Api.csproj", "{1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.String", "Penumbra.String\Penumbra.String.csproj", "{5549BAFD-6357-4B1A-800C-75AC36E5B76D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.CrashHandler", "Penumbra.CrashHandler\Penumbra.CrashHandler.csproj", "{EE834491-A98F-4395-BE0D-6861AE5AD953}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Schemas", "Schemas", "{BFEA7504-1210-4F79-A7FE-BF03B6567E33}"
ProjectSection(SolutionItems) = preProject
schemas\default_mod.json = schemas\default_mod.json
schemas\group.json = schemas\group.json
schemas\local_mod_data-v3.json = schemas\local_mod_data-v3.json
schemas\mod_meta-v3.json = schemas\mod_meta-v3.json
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "structs", "structs", "{B03F276A-0572-4F62-AF86-EF62F6B80463}"
ProjectSection(SolutionItems) = preProject
schemas\structs\container.json = schemas\structs\container.json
schemas\structs\group_combining.json = schemas\structs\group_combining.json
schemas\structs\group_imc.json = schemas\structs\group_imc.json
schemas\structs\group_multi.json = schemas\structs\group_multi.json
schemas\structs\group_single.json = schemas\structs\group_single.json
schemas\structs\manipulation.json = schemas\structs\manipulation.json
schemas\structs\meta_atch.json = schemas\structs\meta_atch.json
schemas\structs\meta_atr.json = schemas\structs\meta_atr.json
schemas\structs\meta_enums.json = schemas\structs\meta_enums.json
schemas\structs\meta_eqdp.json = schemas\structs\meta_eqdp.json
schemas\structs\meta_eqp.json = schemas\structs\meta_eqp.json
schemas\structs\meta_est.json = schemas\structs\meta_est.json
schemas\structs\meta_geqp.json = schemas\structs\meta_geqp.json
schemas\structs\meta_gmp.json = schemas\structs\meta_gmp.json
schemas\structs\meta_imc.json = schemas\structs\meta_imc.json
schemas\structs\meta_rsp.json = schemas\structs\meta_rsp.json
schemas\structs\meta_shp.json = schemas\structs\meta_shp.json
schemas\structs\option.json = schemas\structs\option.json
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|Any CPU.Build.0 = Release|Any CPU
{EE551E87-FDB3-4612-B500-DC870C07C605}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EE551E87-FDB3-4612-B500-DC870C07C605}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EE551E87-FDB3-4612-B500-DC870C07C605}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EE551E87-FDB3-4612-B500-DC870C07C605}.Release|Any CPU.Build.0 = Release|Any CPU
{01685BD8-8847-4B49-BF90-1683B4C76B0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{01685BD8-8847-4B49-BF90-1683B4C76B0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{01685BD8-8847-4B49-BF90-1683B4C76B0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{01685BD8-8847-4B49-BF90-1683B4C76B0E}.Release|Any CPU.Build.0 = Release|Any CPU
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.ActiveCfg = Debug|x64
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.Build.0 = Debug|x64
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.ActiveCfg = Release|x64
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.Build.0 = Release|x64
{EE551E87-FDB3-4612-B500-DC870C07C605}.Debug|x64.ActiveCfg = Debug|x64
{EE551E87-FDB3-4612-B500-DC870C07C605}.Debug|x64.Build.0 = Debug|x64
{EE551E87-FDB3-4612-B500-DC870C07C605}.Release|x64.ActiveCfg = Release|x64
{EE551E87-FDB3-4612-B500-DC870C07C605}.Release|x64.Build.0 = Release|x64
{87750518-1A20-40B4-9FC1-22F906EFB290}.Debug|x64.ActiveCfg = Debug|x64
{87750518-1A20-40B4-9FC1-22F906EFB290}.Debug|x64.Build.0 = Debug|x64
{87750518-1A20-40B4-9FC1-22F906EFB290}.Release|x64.ActiveCfg = Release|x64
{87750518-1A20-40B4-9FC1-22F906EFB290}.Release|x64.Build.0 = Release|x64
{1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Debug|x64.ActiveCfg = Debug|x64
{1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Debug|x64.Build.0 = Debug|x64
{1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Release|x64.ActiveCfg = Release|x64
{1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Release|x64.Build.0 = Release|x64
{5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Debug|x64.ActiveCfg = Debug|x64
{5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Debug|x64.Build.0 = Debug|x64
{5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|x64.ActiveCfg = Release|x64
{5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|x64.Build.0 = Release|x64
{EE834491-A98F-4395-BE0D-6861AE5AD953}.Debug|x64.ActiveCfg = Debug|x64
{EE834491-A98F-4395-BE0D-6861AE5AD953}.Debug|x64.Build.0 = Debug|x64
{EE834491-A98F-4395-BE0D-6861AE5AD953}.Release|x64.ActiveCfg = Release|x64
{EE834491-A98F-4395-BE0D-6861AE5AD953}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{BFEA7504-1210-4F79-A7FE-BF03B6567E33} = {F89C9EAE-25C8-43BE-8108-5921E5A93502}
{B03F276A-0572-4F62-AF86-EF62F6B80463} = {BFEA7504-1210-4F79-A7FE-BF03B6567E33}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B17E85B1-5F60-4440-9F9A-3DDE877E8CDF}
EndGlobalSection

View file

@ -1,49 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using EmbedIO;
using EmbedIO.Routing;
using EmbedIO.WebApi;
using Penumbra.Mods;
using Penumbra.Util;
namespace Penumbra.Api
{
public class ModsController : WebApiController
{
private readonly Penumbra _penumbra;
public ModsController( Penumbra penumbra )
=> _penumbra = penumbra;
[Route( HttpVerbs.Get, "/mods" )]
public object? GetMods()
{
var modManager = Service< ModManager >.Get();
return modManager.Collections.CurrentCollection.Cache?.AvailableMods.Values.Select( x => new
{
x.Settings.Enabled,
x.Settings.Priority,
x.Data.BasePath.Name,
x.Data.Meta,
BasePath = x.Data.BasePath.FullName,
Files = x.Data.Resources.ModFiles.Select( fi => fi.FullName ),
} )
?? null;
}
[Route( HttpVerbs.Post, "/mods" )]
public object CreateMod()
=> new { };
[Route( HttpVerbs.Get, "/files" )]
public object GetFiles()
{
var modManager = Service< ModManager >.Get();
return modManager.Collections.CurrentCollection.Cache?.ResolvedFiles.ToDictionary(
o => ( string )o.Key,
o => o.Value.FullName
)
?? new Dictionary< string, string >();
}
}
}

View file

@ -0,0 +1,78 @@
using OtterGui.Log;
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Interop;
using Penumbra.Interop.PathResolving;
namespace Penumbra.Api.Api;
public class ApiHelpers(
CollectionManager collectionManager,
ObjectManager objects,
CollectionResolver collectionResolver,
ActorManager actors) : IApiService
{
/// <summary> Return the associated identifier for an object given by its index. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal ActorIdentifier AssociatedIdentifier(int gameObjectIdx)
{
if (gameObjectIdx < 0 || gameObjectIdx >= objects.TotalCount)
return ActorIdentifier.Invalid;
var ptr = objects[gameObjectIdx];
return actors.FromObject(ptr, out _, false, true, true);
}
/// <summary>
/// Return the collection associated to a current game object. If it does not exist, return the default collection.
/// If the index is invalid, returns false and the default collection.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal unsafe bool AssociatedCollection(int gameObjectIdx, out ModCollection collection)
{
collection = collectionManager.Active.Default;
if (gameObjectIdx < 0 || gameObjectIdx >= objects.TotalCount)
return false;
var ptr = objects[gameObjectIdx];
var data = collectionResolver.IdentifyCollection(ptr.AsObject, false);
if (data.Valid)
collection = data.ModCollection;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static PenumbraApiEc Return(PenumbraApiEc ec, LazyString args, [CallerMemberName] string name = "Unknown")
{
if (ec is PenumbraApiEc.Success or PenumbraApiEc.NothingChanged)
Penumbra.Log.Verbose($"[{name}] Called with {args}, returned {ec}.");
else
Penumbra.Log.Debug($"[{name}] Called with {args}, returned {ec}.");
return ec;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static LazyString Args(params object[] arguments)
{
if (arguments.Length == 0)
return new LazyString(() => "no arguments");
return new LazyString(() =>
{
var sb = new StringBuilder();
for (var i = 0; i < arguments.Length / 2; ++i)
{
sb.Append(arguments[2 * i]);
sb.Append(" = ");
sb.Append(arguments[2 * i + 1]);
sb.Append(", ");
}
return sb.ToString(0, sb.Length - 2);
});
}
}

View file

@ -0,0 +1,177 @@
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.Mods;
namespace Penumbra.Api.Api;
public class CollectionApi(CollectionManager collections, ApiHelpers helpers) : IPenumbraApiCollection, IApiService
{
public Dictionary<Guid, string> GetCollections()
=> collections.Storage.ToDictionary(c => c.Identity.Id, c => c.Identity.Name);
public List<(Guid Id, string Name)> GetCollectionsByIdentifier(string identifier)
{
if (identifier.Length == 0)
return [];
var list = new List<(Guid Id, string Name)>(4);
if (Guid.TryParse(identifier, out var guid) && collections.Storage.ById(guid, out var collection) && collection != ModCollection.Empty)
list.Add((collection.Identity.Id, collection.Identity.Name));
else if (identifier.Length >= 8)
list.AddRange(collections.Storage.Where(c => c.Identity.Identifier.StartsWith(identifier, StringComparison.OrdinalIgnoreCase))
.Select(c => (c.Identity.Id, c.Identity.Name)));
list.AddRange(collections.Storage
.Where(c => string.Equals(c.Identity.Name, identifier, StringComparison.OrdinalIgnoreCase)
&& !list.Contains((c.Identity.Id, c.Identity.Name)))
.Select(c => (c.Identity.Id, c.Identity.Name)));
return list;
}
public Func<string, (string ModDirectory, string ModName)[]> CheckCurrentChangedItemFunc()
{
var weakRef = new WeakReference<CollectionManager>(collections);
return s =>
{
if (!weakRef.TryGetTarget(out var c))
throw new ObjectDisposedException("The underlying collection storage of this IPC container was disposed.");
if (!c.Active.Current.ChangedItems.TryGetValue(s, out var d))
return [];
return d.Item1.Select(m => (m is Mod mod ? mod.Identifier : string.Empty, m.Name.Text)).ToArray();
};
}
public Dictionary<string, object?> GetChangedItemsForCollection(Guid collectionId)
{
try
{
if (!collections.Storage.ById(collectionId, out var collection))
collection = ModCollection.Empty;
if (collection.HasCache)
return collection.ChangedItems.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Item2?.ToInternalObject());
Penumbra.Log.Warning($"Collection {collectionId} does not exist or is not loaded.");
return [];
}
catch (Exception e)
{
Penumbra.Log.Error($"Could not obtain Changed Items for {collectionId}:\n{e}");
throw;
}
}
public (Guid Id, string Name)? GetCollection(ApiCollectionType type)
{
if (!Enum.IsDefined(type))
return null;
var collection = collections.Active.ByType((CollectionType)type);
return collection == null ? null : (collection.Identity.Id, collection.Identity.Name);
}
internal (Guid Id, string Name)? GetCollection(byte type)
=> GetCollection((ApiCollectionType)type);
public (bool ObjectValid, bool IndividualSet, (Guid Id, string Name) EffectiveCollection) GetCollectionForObject(int gameObjectIdx)
{
var id = helpers.AssociatedIdentifier(gameObjectIdx);
if (!id.IsValid)
return (false, false, (collections.Active.Default.Identity.Id, collections.Active.Default.Identity.Name));
if (collections.Active.Individuals.TryGetValue(id, out var collection))
return (true, true, (collection.Identity.Id, collection.Identity.Name));
helpers.AssociatedCollection(gameObjectIdx, out collection);
return (true, false, (collection.Identity.Id, collection.Identity.Name));
}
public Guid[] GetCollectionByName(string name)
=> collections.Storage.Where(c => string.Equals(name, c.Identity.Name, StringComparison.OrdinalIgnoreCase)).Select(c => c.Identity.Id)
.ToArray();
public (PenumbraApiEc, (Guid Id, string Name)? OldCollection) SetCollection(ApiCollectionType type, Guid? collectionId,
bool allowCreateNew, bool allowDelete)
{
if (!Enum.IsDefined(type))
return (PenumbraApiEc.InvalidArgument, null);
var oldCollection = collections.Active.ByType((CollectionType)type);
var old = oldCollection != null ? (oldCollection.Identity.Id, oldCollection.Identity.Name) : new ValueTuple<Guid, string>?();
if (collectionId == null)
{
if (old == null)
return (PenumbraApiEc.NothingChanged, old);
if (!allowDelete || type is ApiCollectionType.Current or ApiCollectionType.Default or ApiCollectionType.Interface)
return (PenumbraApiEc.AssignmentDeletionDisallowed, old);
collections.Active.RemoveSpecialCollection((CollectionType)type);
return (PenumbraApiEc.Success, old);
}
if (!collections.Storage.ById(collectionId.Value, out var collection))
return (PenumbraApiEc.CollectionMissing, old);
if (old == null)
{
if (!allowCreateNew)
return (PenumbraApiEc.AssignmentCreationDisallowed, old);
collections.Active.CreateSpecialCollection((CollectionType)type);
}
else if (old.Value.Item1 == collection.Identity.Id)
{
return (PenumbraApiEc.NothingChanged, old);
}
collections.Active.SetCollection(collection, (CollectionType)type);
return (PenumbraApiEc.Success, old);
}
public (PenumbraApiEc, (Guid Id, string Name)? OldCollection) SetCollectionForObject(int gameObjectIdx, Guid? collectionId,
bool allowCreateNew, bool allowDelete)
{
var id = helpers.AssociatedIdentifier(gameObjectIdx);
if (!id.IsValid)
return (PenumbraApiEc.InvalidIdentifier, (collections.Active.Default.Identity.Id, collections.Active.Default.Identity.Name));
var oldCollection = collections.Active.Individuals.TryGetValue(id, out var c) ? c : null;
var old = oldCollection != null ? (oldCollection.Identity.Id, oldCollection.Identity.Name) : new ValueTuple<Guid, string>?();
if (collectionId == null)
{
if (old == null)
return (PenumbraApiEc.NothingChanged, old);
if (!allowDelete)
return (PenumbraApiEc.AssignmentDeletionDisallowed, old);
var idx = collections.Active.Individuals.Index(id);
collections.Active.RemoveIndividualCollection(idx);
return (PenumbraApiEc.Success, old);
}
if (!collections.Storage.ById(collectionId.Value, out var collection))
return (PenumbraApiEc.CollectionMissing, old);
if (old == null)
{
if (!allowCreateNew)
return (PenumbraApiEc.AssignmentCreationDisallowed, old);
var ids = collections.Active.Individuals.GetGroup(id);
collections.Active.CreateIndividualCollection(ids);
}
else if (old.Value.Item1 == collection.Identity.Id)
{
return (PenumbraApiEc.NothingChanged, old);
}
collections.Active.SetCollection(collection, CollectionType.Individual, collections.Active.Individuals.Index(id));
return (PenumbraApiEc.Success, old);
}
}

View file

@ -0,0 +1,54 @@
using OtterGui.Services;
using Penumbra.Import.Textures;
using TextureType = Penumbra.Api.Enums.TextureType;
namespace Penumbra.Api.Api;
public class EditingApi(TextureManager textureManager) : IPenumbraApiEditing, IApiService
{
public Task ConvertTextureFile(string inputFile, string outputFile, TextureType textureType, bool mipMaps)
=> textureType switch
{
TextureType.Png => textureManager.SavePng(inputFile, outputFile),
TextureType.Targa => textureManager.SaveTga(inputFile, outputFile),
TextureType.AsIsTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, true, inputFile, outputFile),
TextureType.AsIsDds => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, false, inputFile, outputFile),
TextureType.RgbaTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, true, inputFile, outputFile),
TextureType.RgbaDds => textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, false, inputFile, outputFile),
TextureType.Bc3Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC3, mipMaps, true, inputFile, outputFile),
TextureType.Bc3Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC3, mipMaps, false, inputFile, outputFile),
TextureType.Bc7Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC7, mipMaps, true, inputFile, outputFile),
TextureType.Bc7Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC7, mipMaps, false, inputFile, outputFile),
TextureType.Bc1Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC1, mipMaps, true, inputFile, outputFile),
TextureType.Bc1Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC1, mipMaps, false, inputFile, outputFile),
TextureType.Bc4Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC4, mipMaps, true, inputFile, outputFile),
TextureType.Bc4Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC4, mipMaps, false, inputFile, outputFile),
TextureType.Bc5Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC5, mipMaps, true, inputFile, outputFile),
TextureType.Bc5Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC5, mipMaps, false, inputFile, outputFile),
_ => Task.FromException(new Exception($"Invalid input value {textureType}.")),
};
// @formatter:off
public Task ConvertTextureData(byte[] rgbaData, int width, string outputFile, TextureType textureType, bool mipMaps)
=> textureType switch
{
TextureType.Png => textureManager.SavePng(new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
TextureType.Targa => textureManager.SaveTga(new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
TextureType.AsIsTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
TextureType.AsIsDds => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
TextureType.RgbaTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
TextureType.RgbaDds => textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
TextureType.Bc3Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC3, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
TextureType.Bc3Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC3, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
TextureType.Bc7Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC7, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
TextureType.Bc7Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC7, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
TextureType.Bc1Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC1, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
TextureType.Bc1Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC1, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
TextureType.Bc4Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC4, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
TextureType.Bc4Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC4, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
TextureType.Bc5Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC5, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
TextureType.Bc5Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC5, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
_ => Task.FromException(new Exception($"Invalid input value {textureType}.")),
};
// @formatter:on
}

View file

@ -0,0 +1,123 @@
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Interop.Hooks.ResourceLoading;
using Penumbra.Interop.PathResolving;
using Penumbra.Interop.Structs;
using Penumbra.Services;
using Penumbra.String.Classes;
namespace Penumbra.Api.Api;
public class GameStateApi : IPenumbraApiGameState, IApiService, IDisposable
{
private readonly CommunicatorService _communicator;
private readonly CollectionResolver _collectionResolver;
private readonly DrawObjectState _drawObjectState;
private readonly CutsceneService _cutsceneService;
private readonly ResourceLoader _resourceLoader;
public unsafe GameStateApi(CommunicatorService communicator, CollectionResolver collectionResolver, CutsceneService cutsceneService,
ResourceLoader resourceLoader, DrawObjectState drawObjectState)
{
_communicator = communicator;
_collectionResolver = collectionResolver;
_cutsceneService = cutsceneService;
_resourceLoader = resourceLoader;
_drawObjectState = drawObjectState;
_resourceLoader.ResourceLoaded += OnResourceLoaded;
_resourceLoader.PapRequested += OnPapRequested;
_communicator.CreatedCharacterBase.Subscribe(OnCreatedCharacterBase, Communication.CreatedCharacterBase.Priority.Api);
}
public unsafe void Dispose()
{
_resourceLoader.ResourceLoaded -= OnResourceLoaded;
_resourceLoader.PapRequested -= OnPapRequested;
_communicator.CreatedCharacterBase.Unsubscribe(OnCreatedCharacterBase);
}
public event CreatedCharacterBaseDelegate? CreatedCharacterBase;
public event GameObjectResourceResolvedDelegate? GameObjectResourceResolved;
public event CreatingCharacterBaseDelegate? CreatingCharacterBase
{
add
{
if (value == null)
return;
_communicator.CreatingCharacterBase.Subscribe(new Action<nint, Guid, nint, nint, nint>(value),
Communication.CreatingCharacterBase.Priority.Api);
}
remove
{
if (value == null)
return;
_communicator.CreatingCharacterBase.Unsubscribe(new Action<nint, Guid, nint, nint, nint>(value));
}
}
public unsafe (nint GameObject, (Guid Id, string Name) Collection) GetDrawObjectInfo(nint drawObject)
{
var data = _collectionResolver.IdentifyCollection((DrawObject*)drawObject, true);
return (data.AssociatedGameObject, (Id: data.ModCollection.Identity.Id, Name: data.ModCollection.Identity.Name));
}
public int GetCutsceneParentIndex(int actorIdx)
=> _cutsceneService.GetParentIndex(actorIdx);
public Func<int, int> GetCutsceneParentIndexFunc()
{
var weakRef = new WeakReference<CutsceneService>(_cutsceneService);
return idx =>
{
if (!weakRef.TryGetTarget(out var c))
throw new ObjectDisposedException("The underlying cutscene state storage of this IPC container was disposed.");
return c.GetParentIndex(idx);
};
}
public Func<nint, nint> GetGameObjectFromDrawObjectFunc()
{
var weakRef = new WeakReference<DrawObjectState>(_drawObjectState);
return model =>
{
if (!weakRef.TryGetTarget(out var c))
throw new ObjectDisposedException("The underlying draw object state storage of this IPC container was disposed.");
return c.TryGetValue(model, out var data) ? data.Item1.Address : nint.Zero;
};
}
public PenumbraApiEc SetCutsceneParentIndex(int copyIdx, int newParentIdx)
=> _cutsceneService.SetParentIndex(copyIdx, newParentIdx)
? PenumbraApiEc.Success
: PenumbraApiEc.InvalidArgument;
private unsafe void OnResourceLoaded(ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath, ResolveData resolveData)
{
if (resolveData.AssociatedGameObject != nint.Zero && GameObjectResourceResolved != null)
{
var original = originalPath.ToString();
GameObjectResourceResolved.Invoke(resolveData.AssociatedGameObject, original,
manipulatedPath?.ToString() ?? original);
}
}
private void OnPapRequested(Utf8GamePath originalPath, FullPath? manipulatedPath, ResolveData resolveData)
{
if (resolveData.AssociatedGameObject != nint.Zero && GameObjectResourceResolved != null)
{
var original = originalPath.ToString();
GameObjectResourceResolved.Invoke(resolveData.AssociatedGameObject, original,
manipulatedPath?.ToString() ?? original);
}
}
private void OnCreatedCharacterBase(nint gameObject, ModCollection collection, nint drawObject)
=> CreatedCharacterBase?.Invoke(gameObject, collection.Identity.Id, drawObject);
}

View file

@ -0,0 +1,7 @@
namespace Penumbra.Api.Api;
public static class IdentityChecker
{
public static bool Check(string identity)
=> true;
}

544
Penumbra/Api/Api/MetaApi.cs Normal file
View file

@ -0,0 +1,544 @@
using Dalamud.Plugin.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui;
using OtterGui.Services;
using Penumbra.Collections;
using Penumbra.Collections.Cache;
using Penumbra.GameData.Files.AtchStructs;
using Penumbra.GameData.Files.Utility;
using Penumbra.GameData.Structs;
using Penumbra.Interop.PathResolving;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Api.Api;
public class MetaApi(IFramework framework, CollectionResolver collectionResolver, ApiHelpers helpers)
: IPenumbraApiMeta, IApiService
{
public string GetPlayerMetaManipulations()
{
var collection = collectionResolver.PlayerCollection();
return CompressMetaManipulations(collection);
}
public string GetMetaManipulations(int gameObjectIdx)
{
helpers.AssociatedCollection(gameObjectIdx, out var collection);
return CompressMetaManipulations(collection);
}
public Task<string> GetPlayerMetaManipulationsAsync()
{
return Task.Run(async () =>
{
var playerCollection = await framework.RunOnFrameworkThread(collectionResolver.PlayerCollection).ConfigureAwait(false);
return CompressMetaManipulations(playerCollection);
});
}
public Task<string> GetMetaManipulationsAsync(int gameObjectIdx)
{
return Task.Run(async () =>
{
var playerCollection = await framework.RunOnFrameworkThread(() =>
{
helpers.AssociatedCollection(gameObjectIdx, out var collection);
return collection;
}).ConfigureAwait(false);
return CompressMetaManipulations(playerCollection);
});
}
internal static string CompressMetaManipulations(ModCollection collection)
=> CompressMetaManipulationsV1(collection);
private static string CompressMetaManipulationsV0(ModCollection collection)
{
var array = new JArray();
if (collection.MetaCache is { } cache)
{
MetaDictionary.SerializeTo(array, cache.GlobalEqp.Select(kvp => kvp.Key));
MetaDictionary.SerializeTo(array, cache.Imc.Select(kvp => new KeyValuePair<ImcIdentifier, ImcEntry>(kvp.Key, kvp.Value.Entry)));
MetaDictionary.SerializeTo(array, cache.Eqp.Select(kvp => new KeyValuePair<EqpIdentifier, EqpEntry>(kvp.Key, kvp.Value.Entry)));
MetaDictionary.SerializeTo(array, cache.Eqdp.Select(kvp => new KeyValuePair<EqdpIdentifier, EqdpEntry>(kvp.Key, kvp.Value.Entry)));
MetaDictionary.SerializeTo(array, cache.Est.Select(kvp => new KeyValuePair<EstIdentifier, EstEntry>(kvp.Key, kvp.Value.Entry)));
MetaDictionary.SerializeTo(array, cache.Rsp.Select(kvp => new KeyValuePair<RspIdentifier, RspEntry>(kvp.Key, kvp.Value.Entry)));
MetaDictionary.SerializeTo(array, cache.Gmp.Select(kvp => new KeyValuePair<GmpIdentifier, GmpEntry>(kvp.Key, kvp.Value.Entry)));
MetaDictionary.SerializeTo(array, cache.Atch.Select(kvp => new KeyValuePair<AtchIdentifier, AtchEntry>(kvp.Key, kvp.Value.Entry)));
MetaDictionary.SerializeTo(array, cache.Shp.Select(kvp => new KeyValuePair<ShpIdentifier, ShpEntry>(kvp.Key, kvp.Value.Entry)));
MetaDictionary.SerializeTo(array, cache.Atr.Select(kvp => new KeyValuePair<AtrIdentifier, AtrEntry>(kvp.Key, kvp.Value.Entry)));
}
return Functions.ToCompressedBase64(array, 0);
}
private static unsafe string CompressMetaManipulationsV1(ModCollection? collection)
{
using var ms = new MemoryStream();
ms.Capacity = 1024;
using (var zipStream = new GZipStream(ms, CompressionMode.Compress, true))
{
zipStream.Write((byte)1);
zipStream.Write("META0001"u8);
if (collection?.MetaCache is not { } cache)
{
zipStream.Write(0);
zipStream.Write(0);
zipStream.Write(0);
zipStream.Write(0);
zipStream.Write(0);
zipStream.Write(0);
zipStream.Write(0);
}
else
{
WriteCache(zipStream, cache.Imc);
WriteCache(zipStream, cache.Eqp);
WriteCache(zipStream, cache.Eqdp);
WriteCache(zipStream, cache.Est);
WriteCache(zipStream, cache.Rsp);
WriteCache(zipStream, cache.Gmp);
cache.GlobalEqp.EnterReadLock();
try
{
zipStream.Write(cache.GlobalEqp.Count);
foreach (var (globalEqp, _) in cache.GlobalEqp)
zipStream.Write(new ReadOnlySpan<byte>(&globalEqp, sizeof(GlobalEqpManipulation)));
}
finally
{
cache.GlobalEqp.ExitReadLock();
}
WriteCache(zipStream, cache.Atch);
WriteCache(zipStream, cache.Shp);
WriteCache(zipStream, cache.Atr);
}
}
ms.Flush();
ms.Position = 0;
var data = ms.GetBuffer().AsSpan(0, (int)ms.Length);
return Convert.ToBase64String(data);
void WriteCache<TKey, TValue>(Stream stream, MetaCacheBase<TKey, TValue> metaCache)
where TKey : unmanaged, IMetaIdentifier
where TValue : unmanaged
{
metaCache.EnterReadLock();
try
{
stream.Write(metaCache.Count);
foreach (var (identifier, (_, value)) in metaCache)
{
stream.Write(identifier);
stream.Write(value);
}
}
finally
{
metaCache.ExitReadLock();
}
}
}
public const uint ImcKey = ((uint)'I' << 24) | ((uint)'M' << 16) | ((uint)'C' << 8);
public const uint EqpKey = ((uint)'E' << 24) | ((uint)'Q' << 16) | ((uint)'P' << 8);
public const uint EqdpKey = ((uint)'E' << 24) | ((uint)'Q' << 16) | ((uint)'D' << 8) | 'P';
public const uint EstKey = ((uint)'E' << 24) | ((uint)'S' << 16) | ((uint)'T' << 8);
public const uint RspKey = ((uint)'R' << 24) | ((uint)'S' << 16) | ((uint)'P' << 8);
public const uint GmpKey = ((uint)'G' << 24) | ((uint)'M' << 16) | ((uint)'P' << 8);
public const uint GeqpKey = ((uint)'G' << 24) | ((uint)'E' << 16) | ((uint)'Q' << 8) | 'P';
public const uint AtchKey = ((uint)'A' << 24) | ((uint)'T' << 16) | ((uint)'C' << 8) | 'H';
public const uint ShpKey = ((uint)'S' << 24) | ((uint)'H' << 16) | ((uint)'P' << 8);
public const uint AtrKey = ((uint)'A' << 24) | ((uint)'T' << 16) | ((uint)'R' << 8);
private static unsafe string CompressMetaManipulationsV2(ModCollection? collection)
{
using var ms = new MemoryStream();
ms.Capacity = 1024;
using (var zipStream = new GZipStream(ms, CompressionMode.Compress, true))
{
zipStream.Write((byte)2);
zipStream.Write("META0002"u8);
if (collection?.MetaCache is { } cache)
{
WriteCache(zipStream, cache.Imc, ImcKey);
WriteCache(zipStream, cache.Eqp, EqpKey);
WriteCache(zipStream, cache.Eqdp, EqdpKey);
WriteCache(zipStream, cache.Est, EstKey);
WriteCache(zipStream, cache.Rsp, RspKey);
WriteCache(zipStream, cache.Gmp, GmpKey);
cache.GlobalEqp.EnterReadLock();
try
{
if (cache.GlobalEqp.Count > 0)
{
zipStream.Write(GeqpKey);
zipStream.Write(cache.GlobalEqp.Count);
foreach (var (globalEqp, _) in cache.GlobalEqp)
zipStream.Write(new ReadOnlySpan<byte>(&globalEqp, sizeof(GlobalEqpManipulation)));
}
}
finally
{
cache.GlobalEqp.ExitReadLock();
}
WriteCache(zipStream, cache.Atch, AtchKey);
WriteCache(zipStream, cache.Shp, ShpKey);
WriteCache(zipStream, cache.Atr, AtrKey);
}
}
ms.Flush();
ms.Position = 0;
var data = ms.GetBuffer().AsSpan(0, (int)ms.Length);
return Convert.ToBase64String(data);
void WriteCache<TKey, TValue>(Stream stream, MetaCacheBase<TKey, TValue> metaCache, uint label)
where TKey : unmanaged, IMetaIdentifier
where TValue : unmanaged
{
metaCache.EnterReadLock();
try
{
if (metaCache.Count <= 0)
return;
stream.Write(label);
stream.Write(metaCache.Count);
foreach (var (identifier, (_, value)) in metaCache)
{
stream.Write(identifier);
stream.Write(value);
}
}
finally
{
metaCache.ExitReadLock();
}
}
}
/// <summary>
/// Convert manipulations from a transmitted base64 string to actual manipulations.
/// The empty string is treated as an empty set.
/// Only returns true if all conversions are successful and distinct.
/// </summary>
internal static bool ConvertManips(string manipString, [NotNullWhen(true)] out MetaDictionary? manips, out byte version)
{
if (manipString.Length == 0)
{
manips = new MetaDictionary();
version = byte.MaxValue;
return true;
}
try
{
var bytes = Convert.FromBase64String(manipString);
using var compressedStream = new MemoryStream(bytes);
using var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var resultStream = new MemoryStream();
zipStream.CopyTo(resultStream);
resultStream.Flush();
resultStream.Position = 0;
var data = resultStream.GetBuffer().AsSpan(0, (int)resultStream.Length);
version = data[0];
data = data[1..];
switch (version)
{
case 0: return ConvertManipsV0(data, out manips);
case 1: return ConvertManipsV1(data, out manips);
case 2: return ConvertManipsV2(data, out manips);
default:
Penumbra.Log.Debug($"Invalid version for manipulations: {version}.");
manips = null;
return false;
}
}
catch (Exception ex)
{
Penumbra.Log.Debug($"Error decompressing manipulations:\n{ex}");
manips = null;
version = byte.MaxValue;
return false;
}
}
private static bool ConvertManipsV2(ReadOnlySpan<byte> data, [NotNullWhen(true)] out MetaDictionary? manips)
{
if (!data.StartsWith("META0002"u8))
{
Penumbra.Log.Debug("Invalid manipulations of version 2, does not start with valid prefix.");
manips = null;
return false;
}
manips = new MetaDictionary();
var r = new SpanBinaryReader(data[8..]);
while (r.Remaining > 4)
{
var prefix = r.ReadUInt32();
var count = r.Remaining > 4 ? r.ReadInt32() : 0;
if (count is 0)
continue;
switch (prefix)
{
case ImcKey:
for (var i = 0; i < count; ++i)
{
var identifier = r.Read<ImcIdentifier>();
var value = r.Read<ImcEntry>();
if (!identifier.Validate() || !manips.TryAdd(identifier, value))
return false;
}
break;
case EqpKey:
for (var i = 0; i < count; ++i)
{
var identifier = r.Read<EqpIdentifier>();
var value = r.Read<EqpEntry>();
if (!identifier.Validate() || !manips.TryAdd(identifier, value))
return false;
}
break;
case EqdpKey:
for (var i = 0; i < count; ++i)
{
var identifier = r.Read<EqdpIdentifier>();
var value = r.Read<EqdpEntry>();
if (!identifier.Validate() || !manips.TryAdd(identifier, value))
return false;
}
break;
case EstKey:
for (var i = 0; i < count; ++i)
{
var identifier = r.Read<EstIdentifier>();
var value = r.Read<EstEntry>();
if (!identifier.Validate() || !manips.TryAdd(identifier, value))
return false;
}
break;
case RspKey:
for (var i = 0; i < count; ++i)
{
var identifier = r.Read<RspIdentifier>();
var value = r.Read<RspEntry>();
if (!identifier.Validate() || !manips.TryAdd(identifier, value))
return false;
}
break;
case GmpKey:
for (var i = 0; i < count; ++i)
{
var identifier = r.Read<GmpIdentifier>();
var value = r.Read<GmpEntry>();
if (!identifier.Validate() || !manips.TryAdd(identifier, value))
return false;
}
break;
case GeqpKey:
for (var i = 0; i < count; ++i)
{
var identifier = r.Read<GlobalEqpManipulation>();
if (!identifier.Validate() || !manips.TryAdd(identifier))
return false;
}
break;
case AtchKey:
for (var i = 0; i < count; ++i)
{
var identifier = r.Read<AtchIdentifier>();
var value = r.Read<AtchEntry>();
if (!identifier.Validate() || !manips.TryAdd(identifier, value))
return false;
}
break;
case ShpKey:
for (var i = 0; i < count; ++i)
{
var identifier = r.Read<ShpIdentifier>();
var value = r.Read<ShpEntry>();
if (!identifier.Validate() || !manips.TryAdd(identifier, value))
return false;
}
break;
case AtrKey:
for (var i = 0; i < count; ++i)
{
var identifier = r.Read<AtrIdentifier>();
var value = r.Read<AtrEntry>();
if (!identifier.Validate() || !manips.TryAdd(identifier, value))
return false;
}
break;
}
}
return true;
}
private static bool ConvertManipsV1(ReadOnlySpan<byte> data, [NotNullWhen(true)] out MetaDictionary? manips)
{
if (!data.StartsWith("META0001"u8))
{
Penumbra.Log.Debug($"Invalid manipulations of version 1, does not start with valid prefix.");
manips = null;
return false;
}
manips = new MetaDictionary();
var r = new SpanBinaryReader(data[8..]);
var imcCount = r.ReadInt32();
for (var i = 0; i < imcCount; ++i)
{
var identifier = r.Read<ImcIdentifier>();
var value = r.Read<ImcEntry>();
if (!identifier.Validate() || !manips.TryAdd(identifier, value))
return false;
}
var eqpCount = r.ReadInt32();
for (var i = 0; i < eqpCount; ++i)
{
var identifier = r.Read<EqpIdentifier>();
var value = r.Read<EqpEntry>();
if (!identifier.Validate() || !manips.TryAdd(identifier, value))
return false;
}
var eqdpCount = r.ReadInt32();
for (var i = 0; i < eqdpCount; ++i)
{
var identifier = r.Read<EqdpIdentifier>();
var value = r.Read<EqdpEntry>();
if (!identifier.Validate() || !manips.TryAdd(identifier, value))
return false;
}
var estCount = r.ReadInt32();
for (var i = 0; i < estCount; ++i)
{
var identifier = r.Read<EstIdentifier>();
var value = r.Read<EstEntry>();
if (!identifier.Validate() || !manips.TryAdd(identifier, value))
return false;
}
var rspCount = r.ReadInt32();
for (var i = 0; i < rspCount; ++i)
{
var identifier = r.Read<RspIdentifier>();
var value = r.Read<RspEntry>();
if (!identifier.Validate() || !manips.TryAdd(identifier, value))
return false;
}
var gmpCount = r.ReadInt32();
for (var i = 0; i < gmpCount; ++i)
{
var identifier = r.Read<GmpIdentifier>();
var value = r.Read<GmpEntry>();
if (!identifier.Validate() || !manips.TryAdd(identifier, value))
return false;
}
var globalEqpCount = r.ReadInt32();
for (var i = 0; i < globalEqpCount; ++i)
{
var manip = r.Read<GlobalEqpManipulation>();
if (!manip.Validate() || !manips.TryAdd(manip))
return false;
}
// Atch was added after there were already some V1 around, so check for size here.
if (r.Position < r.Count)
{
var atchCount = r.ReadInt32();
for (var i = 0; i < atchCount; ++i)
{
var identifier = r.Read<AtchIdentifier>();
var value = r.Read<AtchEntry>();
if (!identifier.Validate() || !manips.TryAdd(identifier, value))
return false;
}
// Shp and Atr was added later
if (r.Position < r.Count)
{
var shpCount = r.ReadInt32();
for (var i = 0; i < shpCount; ++i)
{
var identifier = r.Read<ShpIdentifier>();
var value = r.Read<ShpEntry>();
if (!identifier.Validate() || !manips.TryAdd(identifier, value))
return false;
}
var atrCount = r.ReadInt32();
for (var i = 0; i < atrCount; ++i)
{
var identifier = r.Read<AtrIdentifier>();
var value = r.Read<AtrEntry>();
if (!identifier.Validate() || !manips.TryAdd(identifier, value))
return false;
}
}
}
return true;
}
private static bool ConvertManipsV0(ReadOnlySpan<byte> data, [NotNullWhen(true)] out MetaDictionary? manips)
{
var json = Encoding.UTF8.GetString(data);
manips = JsonConvert.DeserializeObject<MetaDictionary>(json);
return manips != null;
}
internal void TestMetaManipulations()
{
var collection = collectionResolver.PlayerCollection();
var dict = new MetaDictionary(collection.MetaCache);
var count = dict.Count;
var watch = Stopwatch.StartNew();
var v0 = CompressMetaManipulationsV0(collection);
var v0Time = watch.ElapsedMilliseconds;
watch.Restart();
var v1 = CompressMetaManipulationsV1(collection);
var v1Time = watch.ElapsedMilliseconds;
watch.Restart();
var v1Success = ConvertManips(v1, out var v1Roundtrip, out _);
var v1RoundtripTime = watch.ElapsedMilliseconds;
watch.Restart();
var v0Success = ConvertManips(v0, out var v0Roundtrip, out _);
var v0RoundtripTime = watch.ElapsedMilliseconds;
Penumbra.Log.Information($"Version | Count | Time | Length | Success | ReCount | ReTime | Equal");
Penumbra.Log.Information(
$"0 | {count} | {v0Time} | {v0.Length} | {v0Success} | {v0Roundtrip?.Count} | {v0RoundtripTime} | {v0Roundtrip?.Equals(dict)}");
Penumbra.Log.Information(
$"1 | {count} | {v1Time} | {v1.Length} | {v1Success} | {v1Roundtrip?.Count} | {v1RoundtripTime} | {v0Roundtrip?.Equals(dict)}");
}
}

View file

@ -0,0 +1,362 @@
using OtterGui.Extensions;
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Api.Helpers;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.Communication;
using Penumbra.Interop.PathResolving;
using Penumbra.Mods;
using Penumbra.Mods.Editor;
using Penumbra.Mods.Groups;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Manager.OptionEditor;
using Penumbra.Mods.Settings;
using Penumbra.Mods.SubMods;
using Penumbra.Services;
namespace Penumbra.Api.Api;
public class ModSettingsApi : IPenumbraApiModSettings, IApiService, IDisposable
{
private readonly CollectionResolver _collectionResolver;
private readonly ModManager _modManager;
private readonly CollectionManager _collectionManager;
private readonly CollectionEditor _collectionEditor;
private readonly CommunicatorService _communicator;
public ModSettingsApi(CollectionResolver collectionResolver,
ModManager modManager,
CollectionManager collectionManager,
CollectionEditor collectionEditor,
CommunicatorService communicator)
{
_collectionResolver = collectionResolver;
_modManager = modManager;
_collectionManager = collectionManager;
_collectionEditor = collectionEditor;
_communicator = communicator;
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ApiModSettings);
_communicator.ModSettingChanged.Subscribe(OnModSettingChange, Communication.ModSettingChanged.Priority.Api);
_communicator.ModOptionChanged.Subscribe(OnModOptionEdited, ModOptionChanged.Priority.Api);
_communicator.ModFileChanged.Subscribe(OnModFileChanged, ModFileChanged.Priority.Api);
}
public void Dispose()
{
_communicator.ModPathChanged.Unsubscribe(OnModPathChange);
_communicator.ModSettingChanged.Unsubscribe(OnModSettingChange);
_communicator.ModOptionChanged.Unsubscribe(OnModOptionEdited);
_communicator.ModFileChanged.Unsubscribe(OnModFileChanged);
}
public event ModSettingChangedDelegate? ModSettingChanged;
public AvailableModSettings? GetAvailableModSettings(string modDirectory, string modName)
{
if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
return null;
var dict = new Dictionary<string, (string[], int)>(mod.Groups.Count);
foreach (var g in mod.Groups)
dict.Add(g.Name, (g.Options.Select(o => o.Name).ToArray(), (int)g.Type));
return new AvailableModSettings(dict);
}
public (PenumbraApiEc, (bool, int, Dictionary<string, List<string>>, bool)?) GetCurrentModSettings(Guid collectionId, string modDirectory,
string modName, bool ignoreInheritance)
{
var ret = GetCurrentModSettingsWithTemp(collectionId, modDirectory, modName, ignoreInheritance, true, 0);
if (ret.Item2 is null)
return (ret.Item1, null);
return (ret.Item1, (ret.Item2.Value.Item1, ret.Item2.Value.Item2, ret.Item2.Value.Item3, ret.Item2.Value.Item4));
}
public PenumbraApiEc GetSettingsInAllCollections(string modDirectory, string modName,
out Dictionary<Guid, (bool, int, Dictionary<string, List<string>>, bool, bool)> settings,
bool ignoreTemporaryCollections = false)
{
settings = [];
if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
return PenumbraApiEc.ModMissing;
var collections = ignoreTemporaryCollections
? _collectionManager.Storage.Where(c => c != ModCollection.Empty)
: _collectionManager.Storage.Where(c => c != ModCollection.Empty).Concat(_collectionManager.Temp.Values);
settings = [];
foreach (var collection in collections)
{
if (GetCurrentSettings(collection, mod, false, false, 0) is { } s)
settings.Add(collection.Identity.Id, s);
}
return PenumbraApiEc.Success;
}
public (PenumbraApiEc, (bool, int, Dictionary<string, List<string>>, bool, bool)?) GetCurrentModSettingsWithTemp(Guid collectionId,
string modDirectory, string modName, bool ignoreInheritance, bool ignoreTemporary, int key)
{
if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
return (PenumbraApiEc.ModMissing, null);
if (!_collectionManager.Storage.ById(collectionId, out var collection))
return (PenumbraApiEc.CollectionMissing, null);
if (collection.Identity.Id == Guid.Empty)
return (PenumbraApiEc.Success, null);
if (GetCurrentSettings(collection, mod, ignoreInheritance, ignoreTemporary, key) is { } settings)
return (PenumbraApiEc.Success, settings);
return (PenumbraApiEc.Success, null);
}
public (PenumbraApiEc, Dictionary<string, (bool, int, Dictionary<string, List<string>>, bool, bool)>?) GetAllModSettings(Guid collectionId,
bool ignoreInheritance, bool ignoreTemporary, int key)
{
if (!_collectionManager.Storage.ById(collectionId, out var collection))
return (PenumbraApiEc.CollectionMissing, null);
if (collection.Identity.Id == Guid.Empty)
return (PenumbraApiEc.Success, []);
var ret = new Dictionary<string, (bool, int, Dictionary<string, List<string>>, bool, bool)>(_modManager.Count);
foreach (var mod in _modManager)
{
if (GetCurrentSettings(collection, mod, ignoreInheritance, ignoreTemporary, key) is { } settings)
ret[mod.Identifier] = settings;
}
return (PenumbraApiEc.Success, ret);
}
public PenumbraApiEc TryInheritMod(Guid collectionId, string modDirectory, string modName, bool inherit)
{
var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "Inherit",
inherit.ToString());
if (collectionId == Guid.Empty)
return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
if (!_collectionManager.Storage.ById(collectionId, out var collection))
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
var ret = _collectionEditor.SetModInheritance(collection, mod, inherit)
? PenumbraApiEc.Success
: PenumbraApiEc.NothingChanged;
return ApiHelpers.Return(ret, args);
}
public PenumbraApiEc TrySetMod(Guid collectionId, string modDirectory, string modName, bool enabled)
{
var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "Enabled", enabled);
if (!_collectionManager.Storage.ById(collectionId, out var collection))
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
var ret = _collectionEditor.SetModState(collection, mod, enabled)
? PenumbraApiEc.Success
: PenumbraApiEc.NothingChanged;
return ApiHelpers.Return(ret, args);
}
public PenumbraApiEc TrySetModPriority(Guid collectionId, string modDirectory, string modName, int priority)
{
var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "Priority", priority);
if (!_collectionManager.Storage.ById(collectionId, out var collection))
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
var ret = _collectionEditor.SetModPriority(collection, mod, new ModPriority(priority))
? PenumbraApiEc.Success
: PenumbraApiEc.NothingChanged;
return ApiHelpers.Return(ret, args);
}
public PenumbraApiEc TrySetModSetting(Guid collectionId, string modDirectory, string modName, string optionGroupName, string optionName)
{
var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName",
optionGroupName, "OptionName", optionName);
if (!_collectionManager.Storage.ById(collectionId, out var collection))
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
var groupIdx = mod.Groups.IndexOf(g => g.Name == optionGroupName);
if (groupIdx < 0)
return ApiHelpers.Return(PenumbraApiEc.OptionGroupMissing, args);
var optionIdx = mod.Groups[groupIdx].Options.IndexOf(o => o.Name == optionName);
if (optionIdx < 0)
return ApiHelpers.Return(PenumbraApiEc.OptionMissing, args);
var setting = mod.Groups[groupIdx].Behaviour switch
{
GroupDrawBehaviour.MultiSelection => Setting.Multi(optionIdx),
GroupDrawBehaviour.SingleSelection => Setting.Single(optionIdx),
_ => Setting.Zero,
};
var ret = _collectionEditor.SetModSetting(collection, mod, groupIdx, setting)
? PenumbraApiEc.Success
: PenumbraApiEc.NothingChanged;
return ApiHelpers.Return(ret, args);
}
public PenumbraApiEc TrySetModSettings(Guid collectionId, string modDirectory, string modName, string optionGroupName,
IReadOnlyList<string> optionNames)
{
var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName",
optionGroupName, "#optionNames", optionNames.Count);
if (!_collectionManager.Storage.ById(collectionId, out var collection))
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
var settingSuccess = ConvertModSetting(mod, optionGroupName, optionNames, out var groupIdx, out var setting);
if (settingSuccess is not PenumbraApiEc.Success)
return ApiHelpers.Return(settingSuccess, args);
var ret = _collectionEditor.SetModSetting(collection, mod, groupIdx, setting)
? PenumbraApiEc.Success
: PenumbraApiEc.NothingChanged;
return ApiHelpers.Return(ret, args);
}
public PenumbraApiEc CopyModSettings(Guid? collectionId, string modDirectoryFrom, string modDirectoryTo)
{
var args = ApiHelpers.Args("CollectionId", collectionId.HasValue ? collectionId.Value.ToString() : "NULL",
"From", modDirectoryFrom, "To", modDirectoryTo);
var sourceMod = _modManager.FirstOrDefault(m => string.Equals(m.ModPath.Name, modDirectoryFrom, StringComparison.OrdinalIgnoreCase));
var targetMod = _modManager.FirstOrDefault(m => string.Equals(m.ModPath.Name, modDirectoryTo, StringComparison.OrdinalIgnoreCase));
if (collectionId == null)
foreach (var collection in _collectionManager.Storage)
_collectionEditor.CopyModSettings(collection, sourceMod, modDirectoryFrom, targetMod, modDirectoryTo);
else if (_collectionManager.Storage.ById(collectionId.Value, out var collection))
_collectionEditor.CopyModSettings(collection, sourceMod, modDirectoryFrom, targetMod, modDirectoryTo);
else
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
return ApiHelpers.Return(PenumbraApiEc.Success, args);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private (bool, int, Dictionary<string, List<string>>, bool, bool)? GetCurrentSettings(ModCollection collection, Mod mod,
bool ignoreInheritance, bool ignoreTemporary, int key)
{
var settings = collection.Settings.Settings[mod.Index];
if (!ignoreTemporary && settings.TempSettings is { } tempSettings && (tempSettings.Lock <= 0 || tempSettings.Lock == key))
{
if (!tempSettings.ForceInherit)
return (tempSettings.Enabled, tempSettings.Priority.Value, tempSettings.ConvertToShareable(mod).Settings,
false, true);
if (!ignoreInheritance && collection.GetActualSettings(mod.Index).Settings is { } actualSettingsTemp)
return (actualSettingsTemp.Enabled, actualSettingsTemp.Priority.Value,
actualSettingsTemp.ConvertToShareable(mod).Settings, true, true);
}
if (settings.Settings is { } ownSettings)
return (ownSettings.Enabled, ownSettings.Priority.Value, ownSettings.ConvertToShareable(mod).Settings, false,
false);
if (!ignoreInheritance && collection.GetInheritedSettings(mod.Index).Settings is { } actualSettings)
return (actualSettings.Enabled, actualSettings.Priority.Value,
actualSettings.ConvertToShareable(mod).Settings, true, false);
return null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private void TriggerSettingEdited(Mod mod)
{
var collection = _collectionResolver.PlayerCollection();
var (settings, parent) = collection.GetActualSettings(mod.Index);
if (settings is { Enabled: true })
ModSettingChanged?.Invoke(ModSettingChange.Edited, collection.Identity.Id, mod.Identifier, parent != collection);
}
private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? _1, DirectoryInfo? _2)
{
if (type == ModPathChangeType.Reloaded)
TriggerSettingEdited(mod);
}
private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, Setting _1, int _2, bool inherited)
=> ModSettingChanged?.Invoke(type, collection.Identity.Id, mod?.ModPath.Name ?? string.Empty, inherited);
private void OnModOptionEdited(ModOptionChangeType type, Mod mod, IModGroup? group, IModOption? option, IModDataContainer? container,
int moveIndex)
{
switch (type)
{
case ModOptionChangeType.GroupDeleted:
case ModOptionChangeType.GroupMoved:
case ModOptionChangeType.GroupTypeChanged:
case ModOptionChangeType.PriorityChanged:
case ModOptionChangeType.OptionDeleted:
case ModOptionChangeType.OptionMoved:
case ModOptionChangeType.OptionFilesChanged:
case ModOptionChangeType.OptionFilesAdded:
case ModOptionChangeType.OptionSwapsChanged:
case ModOptionChangeType.OptionMetaChanged:
TriggerSettingEdited(mod);
break;
}
}
private void OnModFileChanged(Mod mod, FileRegistry file)
{
if (file.CurrentUsage == 0)
return;
TriggerSettingEdited(mod);
}
public static PenumbraApiEc ConvertModSetting(Mod mod, string groupName, IReadOnlyList<string> optionNames, out int groupIndex,
out Setting setting)
{
groupIndex = mod.Groups.IndexOf(g => g.Name.Equals(groupName, StringComparison.OrdinalIgnoreCase));
setting = Setting.Zero;
if (groupIndex < 0)
return PenumbraApiEc.OptionGroupMissing;
switch (mod.Groups[groupIndex])
{
case { Behaviour: GroupDrawBehaviour.SingleSelection } single:
{
var optionIdx = optionNames.Count == 0 ? -1 : single.Options.IndexOf(o => o.Name == optionNames[^1]);
if (optionIdx < 0)
return PenumbraApiEc.OptionMissing;
setting = Setting.Single(optionIdx);
break;
}
case { Behaviour: GroupDrawBehaviour.MultiSelection } multi:
{
foreach (var name in optionNames)
{
var optionIdx = multi.Options.IndexOf(o => o.Name == name);
if (optionIdx < 0)
return PenumbraApiEc.OptionMissing;
setting |= Setting.Multi(optionIdx);
}
break;
}
}
return PenumbraApiEc.Success;
}
}

165
Penumbra/Api/Api/ModsApi.cs Normal file
View file

@ -0,0 +1,165 @@
using Newtonsoft.Json.Linq;
using OtterGui.Compression;
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Communication;
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Services;
namespace Penumbra.Api.Api;
public class ModsApi : IPenumbraApiMods, IApiService, IDisposable
{
private readonly CommunicatorService _communicator;
private readonly ModManager _modManager;
private readonly ModImportManager _modImportManager;
private readonly Configuration _config;
private readonly ModFileSystem _modFileSystem;
private readonly MigrationManager _migrationManager;
public ModsApi(ModManager modManager, ModImportManager modImportManager, Configuration config, ModFileSystem modFileSystem,
CommunicatorService communicator, MigrationManager migrationManager)
{
_modManager = modManager;
_modImportManager = modImportManager;
_config = config;
_modFileSystem = modFileSystem;
_communicator = communicator;
_migrationManager = migrationManager;
_communicator.ModPathChanged.Subscribe(OnModPathChanged, ModPathChanged.Priority.ApiMods);
}
private void OnModPathChanged(ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory, DirectoryInfo? newDirectory)
{
switch (type)
{
case ModPathChangeType.Deleted when oldDirectory != null: ModDeleted?.Invoke(oldDirectory.Name); break;
case ModPathChangeType.Added when newDirectory != null: ModAdded?.Invoke(newDirectory.Name); break;
case ModPathChangeType.Moved when newDirectory != null && oldDirectory != null:
ModMoved?.Invoke(oldDirectory.Name, newDirectory.Name);
break;
}
}
public void Dispose()
{
_communicator.ModPathChanged.Unsubscribe(OnModPathChanged);
}
public Dictionary<string, string> GetModList()
=> _modManager.ToDictionary(m => m.ModPath.Name, m => m.Name.Text);
public PenumbraApiEc InstallMod(string modFilePackagePath)
{
if (!File.Exists(modFilePackagePath))
return ApiHelpers.Return(PenumbraApiEc.FileMissing, ApiHelpers.Args("ModFilePackagePath", modFilePackagePath));
_modImportManager.AddUnpack(modFilePackagePath);
return ApiHelpers.Return(PenumbraApiEc.Success, ApiHelpers.Args("ModFilePackagePath", modFilePackagePath));
}
public PenumbraApiEc ReloadMod(string modDirectory, string modName)
{
if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
return ApiHelpers.Return(PenumbraApiEc.ModMissing, ApiHelpers.Args("ModDirectory", modDirectory, "ModName", modName));
_modManager.ReloadMod(mod);
return ApiHelpers.Return(PenumbraApiEc.Success, ApiHelpers.Args("ModDirectory", modDirectory, "ModName", modName));
}
public PenumbraApiEc AddMod(string modDirectory)
{
var args = ApiHelpers.Args("ModDirectory", modDirectory);
var dir = new DirectoryInfo(Path.Join(_modManager.BasePath.FullName, Path.GetFileName(modDirectory)));
if (!dir.Exists)
return ApiHelpers.Return(PenumbraApiEc.FileMissing, args);
if (dir.Parent == null
|| Path.TrimEndingDirectorySeparator(Path.GetFullPath(_modManager.BasePath.FullName))
!= Path.TrimEndingDirectorySeparator(Path.GetFullPath(dir.Parent.FullName)))
return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
_modManager.AddMod(dir, true);
if (_config.MigrateImportedModelsToV6)
{
_migrationManager.MigrateMdlDirectory(dir.FullName, false);
_migrationManager.Await();
}
if (_config.UseFileSystemCompression)
new FileCompactor(Penumbra.Log).StartMassCompact(dir.EnumerateFiles("*.*", SearchOption.AllDirectories),
CompressionAlgorithm.Xpress8K, false);
return ApiHelpers.Return(PenumbraApiEc.Success, args);
}
public PenumbraApiEc DeleteMod(string modDirectory, string modName)
{
if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
return ApiHelpers.Return(PenumbraApiEc.NothingChanged, ApiHelpers.Args("ModDirectory", modDirectory, "ModName", modName));
_modManager.DeleteMod(mod);
return ApiHelpers.Return(PenumbraApiEc.Success, ApiHelpers.Args("ModDirectory", modDirectory, "ModName", modName));
}
public event Action<string>? ModDeleted;
public event Action<string>? ModAdded;
public event Action<string, string>? ModMoved;
public event Action<JObject, ushort, string>? CreatingPcp
{
add => _communicator.PcpCreation.Subscribe(value!, PcpCreation.Priority.ModsApi);
remove => _communicator.PcpCreation.Unsubscribe(value!);
}
public event Action<JObject, string, Guid>? ParsingPcp
{
add => _communicator.PcpParsing.Subscribe(value!, PcpParsing.Priority.ModsApi);
remove => _communicator.PcpParsing.Unsubscribe(value!);
}
public (PenumbraApiEc, string, bool, bool) GetModPath(string modDirectory, string modName)
{
if (!_modManager.TryGetMod(modDirectory, modName, out var mod)
|| !_modFileSystem.TryGetValue(mod, out var leaf))
return (PenumbraApiEc.ModMissing, string.Empty, false, false);
var fullPath = leaf.FullName();
var isDefault = ModFileSystem.ModHasDefaultPath(mod, fullPath);
var isNameDefault = isDefault || ModFileSystem.ModHasDefaultPath(mod, leaf.Name);
return (PenumbraApiEc.Success, fullPath, !isDefault, !isNameDefault);
}
public PenumbraApiEc SetModPath(string modDirectory, string modName, string newPath)
{
if (newPath.Length == 0)
return PenumbraApiEc.InvalidArgument;
if (!_modManager.TryGetMod(modDirectory, modName, out var mod)
|| !_modFileSystem.TryGetValue(mod, out var leaf))
return PenumbraApiEc.ModMissing;
try
{
_modFileSystem.RenameAndMove(leaf, newPath);
return PenumbraApiEc.Success;
}
catch
{
return PenumbraApiEc.PathRenameFailed;
}
}
public Dictionary<string, object?> GetChangedItems(string modDirectory, string modName)
=> _modManager.TryGetMod(modDirectory, modName, out var mod)
? mod.ChangedItems.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToInternalObject())
: [];
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, object?>> GetChangedItemAdapterDictionary()
=> new ModChangedItemAdapter(new WeakReference<ModStorage>(_modManager));
public IReadOnlyList<(string ModDirectory, IReadOnlyDictionary<string, object?> ChangedItems)> GetChangedItemAdapterList()
=> new ModChangedItemAdapter(new WeakReference<ModStorage>(_modManager));
}

View file

@ -0,0 +1,43 @@
using OtterGui.Services;
namespace Penumbra.Api.Api;
public class PenumbraApi(
CollectionApi collection,
EditingApi editing,
GameStateApi gameState,
MetaApi meta,
ModsApi mods,
ModSettingsApi modSettings,
PluginStateApi pluginState,
RedrawApi redraw,
ResolveApi resolve,
ResourceTreeApi resourceTree,
TemporaryApi temporary,
UiApi ui) : IDisposable, IApiService, IPenumbraApi
{
public const int BreakingVersion = 5;
public const int FeatureVersion = 13;
public void Dispose()
{
Valid = false;
}
public (int Breaking, int Feature) ApiVersion
=> (BreakingVersion, FeatureVersion);
public bool Valid { get; private set; } = true;
public IPenumbraApiCollection Collection { get; } = collection;
public IPenumbraApiEditing Editing { get; } = editing;
public IPenumbraApiGameState GameState { get; } = gameState;
public IPenumbraApiMeta Meta { get; } = meta;
public IPenumbraApiMods Mods { get; } = mods;
public IPenumbraApiModSettings ModSettings { get; } = modSettings;
public IPenumbraApiPluginState PluginState { get; } = pluginState;
public IPenumbraApiRedraw Redraw { get; } = redraw;
public IPenumbraApiResolve Resolve { get; } = resolve;
public IPenumbraApiResourceTree ResourceTree { get; } = resourceTree;
public IPenumbraApiTemporary Temporary { get; } = temporary;
public IPenumbraApiUi Ui { get; } = ui;
}

View file

@ -0,0 +1,38 @@
using System.Collections.Frozen;
using Newtonsoft.Json;
using OtterGui.Services;
using Penumbra.Communication;
using Penumbra.Mods;
using Penumbra.Services;
namespace Penumbra.Api.Api;
public class PluginStateApi(Configuration config, CommunicatorService communicator) : IPenumbraApiPluginState, IApiService
{
public string GetModDirectory()
=> config.ModDirectory;
public string GetConfiguration()
=> JsonConvert.SerializeObject(config, Formatting.Indented);
public event Action<string, bool>? ModDirectoryChanged
{
add => communicator.ModDirectoryChanged.Subscribe(value!, Communication.ModDirectoryChanged.Priority.Api);
remove => communicator.ModDirectoryChanged.Unsubscribe(value!);
}
public bool GetEnabledState()
=> config.EnableMods;
public event Action<bool>? EnabledChange
{
add => communicator.EnabledChanged.Subscribe(value!, EnabledChanged.Priority.Api);
remove => communicator.EnabledChanged.Unsubscribe(value!);
}
public FrozenSet<string> SupportedFeatures
=> FeatureChecker.SupportedFeatures.ToFrozenSet();
public string[] CheckSupportedFeatures(IEnumerable<string> requiredFeatures)
=> requiredFeatures.Where(f => !FeatureChecker.Supported(f)).ToArray();
}

View file

@ -0,0 +1,57 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.GameData.Interop;
using Penumbra.Interop.Services;
namespace Penumbra.Api.Api;
public class RedrawApi(RedrawService redrawService, IFramework framework, CollectionManager collections, ObjectManager objects, ApiHelpers helpers) : IPenumbraApiRedraw, IApiService
{
public void RedrawObject(int gameObjectIndex, RedrawType setting)
{
framework.RunOnFrameworkThread(() => redrawService.RedrawObject(gameObjectIndex, setting));
}
public void RedrawObject(string name, RedrawType setting)
{
framework.RunOnFrameworkThread(() => redrawService.RedrawObject(name, setting));
}
public void RedrawObject(IGameObject? gameObject, RedrawType setting)
{
framework.RunOnFrameworkThread(() => redrawService.RedrawObject(gameObject, setting));
}
public void RedrawAll(RedrawType setting)
{
framework.RunOnFrameworkThread(() => redrawService.RedrawAll(setting));
}
public void RedrawCollectionMembers(Guid collectionId, RedrawType setting)
{
if (!collections.Storage.ById(collectionId, out var collection))
collection = ModCollection.Empty;
framework.RunOnFrameworkThread(() =>
{
foreach (var actor in objects.Objects)
{
helpers.AssociatedCollection(actor.ObjectIndex, out var modCollection);
if (collection == modCollection)
{
redrawService.RedrawObject(actor.ObjectIndex, setting);
}
}
});
}
public event GameObjectRedrawnDelegate? GameObjectRedrawn
{
add => redrawService.GameObjectRedrawn += value;
remove => redrawService.GameObjectRedrawn -= value;
}
}

View file

@ -0,0 +1,135 @@
using Dalamud.Plugin.Services;
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.Interop.PathResolving;
using Penumbra.Mods.Manager;
using Penumbra.String.Classes;
namespace Penumbra.Api.Api;
public class ResolveApi(
ModManager modManager,
CollectionManager collectionManager,
Configuration config,
CollectionResolver collectionResolver,
ApiHelpers helpers,
IFramework framework) : IPenumbraApiResolve, IApiService
{
public string ResolveDefaultPath(string gamePath)
=> ResolvePath(gamePath, modManager, collectionManager.Active.Default);
public string ResolveInterfacePath(string gamePath)
=> ResolvePath(gamePath, modManager, collectionManager.Active.Interface);
public string ResolveGameObjectPath(string gamePath, int gameObjectIdx)
{
helpers.AssociatedCollection(gameObjectIdx, out var collection);
return ResolvePath(gamePath, modManager, collection);
}
public string ResolvePlayerPath(string gamePath)
=> ResolvePath(gamePath, modManager, collectionResolver.PlayerCollection());
public string[] ReverseResolveGameObjectPath(string moddedPath, int gameObjectIdx)
{
if (!config.EnableMods)
return [moddedPath];
helpers.AssociatedCollection(gameObjectIdx, out var collection);
var ret = collection.ReverseResolvePath(new FullPath(moddedPath));
return ret.Select(r => r.ToString()).ToArray();
}
public PenumbraApiEc ResolvePath(Guid collectionId, string gamePath, out string resolvedPath)
{
resolvedPath = gamePath;
if (!collectionManager.Storage.ById(collectionId, out var collection))
return PenumbraApiEc.CollectionMissing;
if (!collection.HasCache)
return PenumbraApiEc.CollectionInactive;
resolvedPath = ResolvePath(gamePath, modManager, collection);
return PenumbraApiEc.Success;
}
public string[] ReverseResolvePlayerPath(string moddedPath)
{
if (!config.EnableMods)
return [moddedPath];
var ret = collectionResolver.PlayerCollection().ReverseResolvePath(new FullPath(moddedPath));
return ret.Select(r => r.ToString()).ToArray();
}
public (string[], string[][]) ResolvePlayerPaths(string[] forward, string[] reverse)
{
if (!config.EnableMods)
return (forward, reverse.Select(p => new[]
{
p,
}).ToArray());
var playerCollection = collectionResolver.PlayerCollection();
var resolved = forward.Select(p => ResolvePath(p, modManager, playerCollection)).ToArray();
var reverseResolved = playerCollection.ReverseResolvePaths(reverse);
return (resolved, reverseResolved.Select(a => a.Select(p => p.ToString()).ToArray()).ToArray());
}
public PenumbraApiEc ResolvePaths(Guid collectionId, string[] forward, string[] reverse, out string[] resolvedForward,
out string[][] resolvedReverse)
{
resolvedForward = forward;
resolvedReverse = [];
if (!config.EnableMods)
return PenumbraApiEc.Success;
if (!collectionManager.Storage.ById(collectionId, out var collection))
return PenumbraApiEc.CollectionMissing;
if (!collection.HasCache)
return PenumbraApiEc.CollectionInactive;
resolvedForward = forward.Select(p => ResolvePath(p, modManager, collection)).ToArray();
var reverseResolved = collection.ReverseResolvePaths(reverse);
resolvedReverse = reverseResolved.Select(a => a.Select(p => p.ToString()).ToArray()).ToArray();
return PenumbraApiEc.Success;
}
public async Task<(string[], string[][])> ResolvePlayerPathsAsync(string[] forward, string[] reverse)
{
if (!config.EnableMods)
return (forward, reverse.Select(p => new[]
{
p,
}).ToArray());
return await Task.Run(async () =>
{
var playerCollection = await framework.RunOnFrameworkThread(collectionResolver.PlayerCollection).ConfigureAwait(false);
var forwardTask = Task.Run(() =>
{
var forwardRet = new string[forward.Length];
Parallel.For(0, forward.Length, idx => forwardRet[idx] = ResolvePath(forward[idx], modManager, playerCollection));
return forwardRet;
}).ConfigureAwait(false);
var reverseTask = Task.Run(() => playerCollection.ReverseResolvePaths(reverse)).ConfigureAwait(false);
var reverseResolved = (await reverseTask).Select(a => a.Select(p => p.ToString()).ToArray()).ToArray();
return (await forwardTask, reverseResolved);
}).ConfigureAwait(false);
}
/// <summary> Resolve a path given by string for a specific collection. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private string ResolvePath(string path, ModManager _, ModCollection collection)
{
if (!config.EnableMods)
return path;
var gamePath = Utf8GamePath.FromString(path, out var p) ? p : Utf8GamePath.Empty;
var ret = collection.ResolvePath(gamePath);
return ret?.ToString() ?? path;
}
}

View file

@ -0,0 +1,63 @@
using Dalamud.Game.ClientState.Objects.Types;
using Newtonsoft.Json.Linq;
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Api.Helpers;
using Penumbra.GameData.Interop;
using Penumbra.Interop.ResourceTree;
namespace Penumbra.Api.Api;
public class ResourceTreeApi(ResourceTreeFactory resourceTreeFactory, ObjectManager objects) : IPenumbraApiResourceTree, IApiService
{
public Dictionary<string, HashSet<string>>?[] GetGameObjectResourcePaths(params ushort[] gameObjects)
{
var characters = gameObjects.Select(index => objects.GetDalamudObject((int)index)).OfType<ICharacter>();
var resourceTrees = resourceTreeFactory.FromCharacters(characters, 0);
var pathDictionaries = ResourceTreeApiHelper.GetResourcePathDictionaries(resourceTrees);
return Array.ConvertAll(gameObjects, obj => pathDictionaries.GetValueOrDefault(obj));
}
public Dictionary<ushort, Dictionary<string, HashSet<string>>> GetPlayerResourcePaths()
{
var resourceTrees = resourceTreeFactory.FromObjectTable(ResourceTreeFactory.Flags.LocalPlayerRelatedOnly);
return ResourceTreeApiHelper.GetResourcePathDictionaries(resourceTrees);
}
public GameResourceDict?[] GetGameObjectResourcesOfType(ResourceType type, bool withUiData,
params ushort[] gameObjects)
{
var characters = gameObjects.Select(index => objects.GetDalamudObject((int)index)).OfType<ICharacter>();
var resourceTrees = resourceTreeFactory.FromCharacters(characters, withUiData ? ResourceTreeFactory.Flags.WithUiData : 0);
var resDictionaries = ResourceTreeApiHelper.GetResourcesOfType(resourceTrees, type);
return Array.ConvertAll(gameObjects, obj => resDictionaries.GetValueOrDefault(obj));
}
public Dictionary<ushort, GameResourceDict> GetPlayerResourcesOfType(ResourceType type,
bool withUiData)
{
var resourceTrees = resourceTreeFactory.FromObjectTable(ResourceTreeFactory.Flags.LocalPlayerRelatedOnly
| (withUiData ? ResourceTreeFactory.Flags.WithUiData : 0));
return ResourceTreeApiHelper.GetResourcesOfType(resourceTrees, type);
}
public JObject?[] GetGameObjectResourceTrees(bool withUiData, params ushort[] gameObjects)
{
var characters = gameObjects.Select(index => objects.GetDalamudObject((int)index)).OfType<ICharacter>();
var resourceTrees = resourceTreeFactory.FromCharacters(characters, withUiData ? ResourceTreeFactory.Flags.WithUiData : 0);
var resDictionary = ResourceTreeApiHelper.EncapsulateResourceTrees(resourceTrees);
return Array.ConvertAll(gameObjects, obj => resDictionary.GetValueOrDefault(obj));
}
public Dictionary<ushort, JObject> GetPlayerResourceTrees(bool withUiData)
{
var resourceTrees = resourceTreeFactory.FromObjectTable(ResourceTreeFactory.Flags.LocalPlayerRelatedOnly
| (withUiData ? ResourceTreeFactory.Flags.WithUiData : 0));
var resDictionary = ResourceTreeApiHelper.EncapsulateResourceTrees(resourceTrees);
return resDictionary;
}
}

View file

@ -0,0 +1,338 @@
using OtterGui.Log;
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Interop;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Settings;
using Penumbra.String.Classes;
namespace Penumbra.Api.Api;
public class TemporaryApi(
TempCollectionManager tempCollections,
ObjectManager objects,
ActorManager actors,
CollectionManager collectionManager,
TempModManager tempMods,
ApiHelpers apiHelpers,
ModManager modManager) : IPenumbraApiTemporary, IApiService
{
public (PenumbraApiEc, Guid) CreateTemporaryCollection(string identity, string name)
{
if (!IdentityChecker.Check(identity))
return (PenumbraApiEc.InvalidCredentials, Guid.Empty);
var collection = tempCollections.CreateTemporaryCollection(name);
if (collection == Guid.Empty)
return (PenumbraApiEc.UnknownError, collection);
return (PenumbraApiEc.Success, collection);
}
public PenumbraApiEc DeleteTemporaryCollection(Guid collectionId)
=> tempCollections.RemoveTemporaryCollection(collectionId)
? PenumbraApiEc.Success
: PenumbraApiEc.CollectionMissing;
public PenumbraApiEc AssignTemporaryCollection(Guid collectionId, int actorIndex, bool forceAssignment)
{
var args = ApiHelpers.Args("CollectionId", collectionId, "ActorIndex", actorIndex, "Forced", forceAssignment);
if (actorIndex < 0 || actorIndex >= objects.TotalCount)
return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
var identifier = actors.FromObject(objects[actorIndex], out _, false, false, true);
if (!identifier.IsValid)
return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
if (!tempCollections.CollectionById(collectionId, out var collection))
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
if (forceAssignment)
{
if (tempCollections.Collections.ContainsKey(identifier) && !tempCollections.Collections.Delete(identifier))
return ApiHelpers.Return(PenumbraApiEc.AssignmentDeletionFailed, args);
}
else if (tempCollections.Collections.ContainsKey(identifier)
|| collectionManager.Active.Individuals.ContainsKey(identifier))
{
return ApiHelpers.Return(PenumbraApiEc.CharacterCollectionExists, args);
}
var group = tempCollections.Collections.GetGroup(identifier);
var ret = tempCollections.AddIdentifier(collection, group)
? PenumbraApiEc.Success
: PenumbraApiEc.UnknownError;
return ApiHelpers.Return(ret, args);
}
public PenumbraApiEc AddTemporaryModAll(string tag, Dictionary<string, string> paths, string manipString, int priority)
{
var args = ApiHelpers.Args("Tag", tag, "#Paths", paths.Count, "ManipString", manipString, "Priority", priority);
if (!ConvertPaths(paths, out var p))
return ApiHelpers.Return(PenumbraApiEc.InvalidGamePath, args);
if (!MetaApi.ConvertManips(manipString, out var m, out _))
return ApiHelpers.Return(PenumbraApiEc.InvalidManipulation, args);
var ret = tempMods.Register(tag, null, p, m, new ModPriority(priority)) switch
{
RedirectResult.Success => PenumbraApiEc.Success,
_ => PenumbraApiEc.UnknownError,
};
return ApiHelpers.Return(ret, args);
}
public PenumbraApiEc AddTemporaryMod(string tag, Guid collectionId, Dictionary<string, string> paths, string manipString, int priority)
{
var args = ApiHelpers.Args("Tag", tag, "CollectionId", collectionId, "#Paths", paths.Count, "ManipString",
manipString, "Priority", priority);
if (collectionId == Guid.Empty)
return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
if (!tempCollections.CollectionById(collectionId, out var collection)
&& !collectionManager.Storage.ById(collectionId, out collection))
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
if (!ConvertPaths(paths, out var p))
return ApiHelpers.Return(PenumbraApiEc.InvalidGamePath, args);
if (!MetaApi.ConvertManips(manipString, out var m, out _))
return ApiHelpers.Return(PenumbraApiEc.InvalidManipulation, args);
var ret = tempMods.Register(tag, collection, p, m, new ModPriority(priority)) switch
{
RedirectResult.Success => PenumbraApiEc.Success,
_ => PenumbraApiEc.UnknownError,
};
return ApiHelpers.Return(ret, args);
}
public PenumbraApiEc RemoveTemporaryModAll(string tag, int priority)
{
var ret = tempMods.Unregister(tag, null, new ModPriority(priority)) switch
{
RedirectResult.Success => PenumbraApiEc.Success,
RedirectResult.NotRegistered => PenumbraApiEc.NothingChanged,
_ => PenumbraApiEc.UnknownError,
};
return ApiHelpers.Return(ret, ApiHelpers.Args("Tag", tag, "Priority", priority));
}
public PenumbraApiEc RemoveTemporaryMod(string tag, Guid collectionId, int priority)
{
var args = ApiHelpers.Args("Tag", tag, "CollectionId", collectionId, "Priority", priority);
if (!tempCollections.CollectionById(collectionId, out var collection)
&& !collectionManager.Storage.ById(collectionId, out collection))
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
var ret = tempMods.Unregister(tag, collection, new ModPriority(priority)) switch
{
RedirectResult.Success => PenumbraApiEc.Success,
RedirectResult.NotRegistered => PenumbraApiEc.NothingChanged,
_ => PenumbraApiEc.UnknownError,
};
return ApiHelpers.Return(ret, args);
}
public (PenumbraApiEc, (bool, bool, int, Dictionary<string, List<string>>)?, string) QueryTemporaryModSettings(Guid collectionId,
string modDirectory, string modName, int key)
{
var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName);
if (!collectionManager.Storage.ById(collectionId, out var collection))
return (ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args), null, string.Empty);
return QueryTemporaryModSettings(args, collection, modDirectory, modName, key);
}
public (PenumbraApiEc ErrorCode, (bool, bool, int, Dictionary<string, List<string>>)? Settings, string Source)
QueryTemporaryModSettingsPlayer(int objectIndex,
string modDirectory, string modName, int key)
{
var args = ApiHelpers.Args("ObjectIndex", objectIndex, "ModDirectory", modDirectory, "ModName", modName);
if (!apiHelpers.AssociatedCollection(objectIndex, out var collection))
return (ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args), null, string.Empty);
return QueryTemporaryModSettings(args, collection, modDirectory, modName, key);
}
private (PenumbraApiEc ErrorCode, (bool, bool, int, Dictionary<string, List<string>>)? Settings, string Source) QueryTemporaryModSettings(
in LazyString args, ModCollection collection, string modDirectory, string modName, int key)
{
if (!modManager.TryGetMod(modDirectory, modName, out var mod))
return (ApiHelpers.Return(PenumbraApiEc.ModMissing, args), null, string.Empty);
if (collection.Identity.Index <= 0)
return (ApiHelpers.Return(PenumbraApiEc.Success, args), null, string.Empty);
var settings = collection.GetTempSettings(mod.Index);
if (settings == null)
return (ApiHelpers.Return(PenumbraApiEc.Success, args), null, string.Empty);
if (settings.Lock > 0 && settings.Lock != key)
return (ApiHelpers.Return(PenumbraApiEc.TemporarySettingDisallowed, args), null, settings.Source);
return (ApiHelpers.Return(PenumbraApiEc.Success, args),
(settings.ForceInherit, settings.Enabled, settings.Priority.Value, settings.ConvertToShareable(mod).Settings), settings.Source);
}
public PenumbraApiEc SetTemporaryModSettings(Guid collectionId, string modDirectory, string modName, bool inherit, bool enabled,
int priority,
IReadOnlyDictionary<string, IReadOnlyList<string>> options, string source, int key)
{
var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "Inherit", inherit,
"Enabled", enabled,
"Priority", priority, "Options", options, "Source", source, "Key", key);
if (!collectionManager.Storage.ById(collectionId, out var collection))
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
return SetTemporaryModSettings(args, collection, modDirectory, modName, inherit, enabled, priority, options, source, key);
}
public PenumbraApiEc SetTemporaryModSettingsPlayer(int objectIndex, string modDirectory, string modName, bool inherit, bool enabled,
int priority,
IReadOnlyDictionary<string, IReadOnlyList<string>> options, string source, int key)
{
var args = ApiHelpers.Args("ObjectIndex", objectIndex, "ModDirectory", modDirectory, "ModName", modName, "Inherit", inherit, "Enabled",
enabled,
"Priority", priority, "Options", options, "Source", source, "Key", key);
if (!apiHelpers.AssociatedCollection(objectIndex, out var collection))
return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
return SetTemporaryModSettings(args, collection, modDirectory, modName, inherit, enabled, priority, options, source, key);
}
private PenumbraApiEc SetTemporaryModSettings(in LazyString args, ModCollection collection, string modDirectory, string modName,
bool inherit, bool enabled, int priority, IReadOnlyDictionary<string, IReadOnlyList<string>> options, string source, int key)
{
if (collection.Identity.Index <= 0)
return ApiHelpers.Return(PenumbraApiEc.TemporarySettingImpossible, args);
if (!modManager.TryGetMod(modDirectory, modName, out var mod))
return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
if (!collectionManager.Editor.CanSetTemporarySettings(collection, mod, key))
if (collection.GetTempSettings(mod.Index) is { Lock: > 0 } oldSettings && oldSettings.Lock != key)
return ApiHelpers.Return(PenumbraApiEc.TemporarySettingDisallowed, args);
var newSettings = new TemporaryModSettings()
{
ForceInherit = inherit,
Enabled = enabled,
Priority = new ModPriority(priority),
Lock = key,
Source = source,
Settings = SettingList.Default(mod),
};
foreach (var (groupName, optionNames) in options)
{
var ec = ModSettingsApi.ConvertModSetting(mod, groupName, optionNames, out var groupIdx, out var setting);
if (ec != PenumbraApiEc.Success)
return ApiHelpers.Return(ec, args);
newSettings.Settings[groupIdx] = setting;
}
if (collectionManager.Editor.SetTemporarySettings(collection, mod, newSettings, key))
return ApiHelpers.Return(PenumbraApiEc.Success, args);
// This should not happen since all error cases had been checked before.
return ApiHelpers.Return(PenumbraApiEc.UnknownError, args);
}
public PenumbraApiEc RemoveTemporaryModSettings(Guid collectionId, string modDirectory, string modName, int key)
{
var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "Key", key);
if (!collectionManager.Storage.ById(collectionId, out var collection))
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
return RemoveTemporaryModSettings(args, collection, modDirectory, modName, key);
}
public PenumbraApiEc RemoveTemporaryModSettingsPlayer(int objectIndex, string modDirectory, string modName, int key)
{
var args = ApiHelpers.Args("ObjectIndex", objectIndex, "ModDirectory", modDirectory, "ModName", modName, "Key", key);
if (!apiHelpers.AssociatedCollection(objectIndex, out var collection))
return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
return RemoveTemporaryModSettings(args, collection, modDirectory, modName, key);
}
private PenumbraApiEc RemoveTemporaryModSettings(in LazyString args, ModCollection collection, string modDirectory, string modName, int key)
{
if (collection.Identity.Index <= 0)
return ApiHelpers.Return(PenumbraApiEc.NothingChanged, args);
if (!modManager.TryGetMod(modDirectory, modName, out var mod))
return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
if (collection.GetTempSettings(mod.Index) is null)
return ApiHelpers.Return(PenumbraApiEc.NothingChanged, args);
if (!collectionManager.Editor.SetTemporarySettings(collection, mod, null, key))
return ApiHelpers.Return(PenumbraApiEc.TemporarySettingDisallowed, args);
return ApiHelpers.Return(PenumbraApiEc.Success, args);
}
public PenumbraApiEc RemoveAllTemporaryModSettings(Guid collectionId, int key)
{
var args = ApiHelpers.Args("CollectionId", collectionId, "Key", key);
if (!collectionManager.Storage.ById(collectionId, out var collection))
return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
return RemoveAllTemporaryModSettings(args, collection, key);
}
public PenumbraApiEc RemoveAllTemporaryModSettingsPlayer(int objectIndex, int key)
{
var args = ApiHelpers.Args("ObjectIndex", objectIndex, "Key", key);
if (!apiHelpers.AssociatedCollection(objectIndex, out var collection))
return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
return RemoveAllTemporaryModSettings(args, collection, key);
}
private PenumbraApiEc RemoveAllTemporaryModSettings(in LazyString args, ModCollection collection, int key)
{
if (collection.Identity.Index <= 0)
return ApiHelpers.Return(PenumbraApiEc.NothingChanged, args);
var numRemoved = collectionManager.Editor.ClearTemporarySettings(collection, key);
return ApiHelpers.Return(numRemoved > 0 ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged, args);
}
/// <summary>
/// Convert a dictionary of strings to a dictionary of game paths to full paths.
/// Only returns true if all paths can successfully be converted and added.
/// </summary>
private static bool ConvertPaths(IReadOnlyDictionary<string, string> redirections,
[NotNullWhen(true)] out Dictionary<Utf8GamePath, FullPath>? paths)
{
paths = new Dictionary<Utf8GamePath, FullPath>(redirections.Count);
foreach (var (gString, fString) in redirections)
{
if (!Utf8GamePath.FromString(gString, out var path))
{
paths = null;
return false;
}
var fullPath = new FullPath(fString);
if (!paths.TryAdd(path, fullPath))
{
paths = null;
return false;
}
}
return true;
}
}

113
Penumbra/Api/Api/UiApi.cs Normal file
View file

@ -0,0 +1,113 @@
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Communication;
using Penumbra.GameData.Data;
using Penumbra.Mods.Manager;
using Penumbra.Services;
using Penumbra.UI;
using Penumbra.UI.Integration;
using Penumbra.UI.Tabs;
namespace Penumbra.Api.Api;
public class UiApi : IPenumbraApiUi, IApiService, IDisposable
{
private readonly CommunicatorService _communicator;
private readonly ConfigWindow _configWindow;
private readonly ModManager _modManager;
private readonly IntegrationSettingsRegistry _integrationSettings;
public UiApi(CommunicatorService communicator, ConfigWindow configWindow, ModManager modManager, IntegrationSettingsRegistry integrationSettings)
{
_communicator = communicator;
_configWindow = configWindow;
_modManager = modManager;
_integrationSettings = integrationSettings;
_communicator.ChangedItemHover.Subscribe(OnChangedItemHover, ChangedItemHover.Priority.Default);
_communicator.ChangedItemClick.Subscribe(OnChangedItemClick, ChangedItemClick.Priority.Default);
}
public void Dispose()
{
_communicator.ChangedItemHover.Unsubscribe(OnChangedItemHover);
_communicator.ChangedItemClick.Unsubscribe(OnChangedItemClick);
}
public event Action<ChangedItemType, uint>? ChangedItemTooltip;
public event Action<MouseButton, ChangedItemType, uint>? ChangedItemClicked;
public event Action<string, float, float>? PreSettingsTabBarDraw
{
add => _communicator.PreSettingsTabBarDraw.Subscribe(value!, Communication.PreSettingsTabBarDraw.Priority.Default);
remove => _communicator.PreSettingsTabBarDraw.Unsubscribe(value!);
}
public event Action<string>? PreSettingsPanelDraw
{
add => _communicator.PreSettingsPanelDraw.Subscribe(value!, Communication.PreSettingsPanelDraw.Priority.Default);
remove => _communicator.PreSettingsPanelDraw.Unsubscribe(value!);
}
public event Action<string>? PostEnabledDraw
{
add => _communicator.PostEnabledDraw.Subscribe(value!, Communication.PostEnabledDraw.Priority.Default);
remove => _communicator.PostEnabledDraw.Unsubscribe(value!);
}
public event Action<string>? PostSettingsPanelDraw
{
add => _communicator.PostSettingsPanelDraw.Subscribe(value!, Communication.PostSettingsPanelDraw.Priority.Default);
remove => _communicator.PostSettingsPanelDraw.Unsubscribe(value!);
}
public PenumbraApiEc OpenMainWindow(TabType tab, string modDirectory, string modName)
{
_configWindow.IsOpen = true;
if (!Enum.IsDefined(tab))
return PenumbraApiEc.InvalidArgument;
if (tab == TabType.Mods && (modDirectory.Length > 0 || modName.Length > 0))
{
if (_modManager.TryGetMod(modDirectory, modName, out var mod))
_communicator.SelectTab.Invoke(tab, mod);
else
return PenumbraApiEc.ModMissing;
}
else if (tab != TabType.None)
{
_communicator.SelectTab.Invoke(tab, null);
}
return PenumbraApiEc.Success;
}
public void CloseMainWindow()
=> _configWindow.IsOpen = false;
private void OnChangedItemClick(MouseButton button, IIdentifiedObjectData data)
{
if (ChangedItemClicked == null)
return;
var (type, id) = data.ToApiObject();
ChangedItemClicked.Invoke(button, type, id);
}
private void OnChangedItemHover(IIdentifiedObjectData data)
{
if (ChangedItemTooltip == null)
return;
var (type, id) = data.ToApiObject();
ChangedItemTooltip.Invoke(type, id);
}
public PenumbraApiEc RegisterSettingsSection(Action draw)
=> _integrationSettings.RegisterSection(draw);
public PenumbraApiEc UnregisterSettingsSection(Action draw)
=> _integrationSettings.UnregisterSection(draw)
? PenumbraApiEc.Success
: PenumbraApiEc.NothingChanged;
}

View file

@ -0,0 +1,161 @@
using Dalamud.Interface;
using Dalamud.Plugin.Services;
using OtterGui.Services;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.Communication;
using Penumbra.Mods.Editor;
using Penumbra.Services;
using Penumbra.String.Classes;
namespace Penumbra.Api;
public class DalamudSubstitutionProvider : IDisposable, IApiService
{
private readonly ITextureSubstitutionProvider _substitution;
private readonly IUiBuilder _uiBuilder;
private readonly ActiveCollectionData _activeCollectionData;
private readonly Configuration _config;
private readonly CommunicatorService _communicator;
public bool Enabled
=> _config.UseDalamudUiTextureRedirection;
public DalamudSubstitutionProvider(ITextureSubstitutionProvider substitution, ActiveCollectionData activeCollectionData,
Configuration config, CommunicatorService communicator, IUiBuilder ui)
{
_substitution = substitution;
_uiBuilder = ui;
_activeCollectionData = activeCollectionData;
_config = config;
_communicator = communicator;
if (Enabled)
Subscribe();
}
public void Set(bool value)
{
if (value)
Enable();
else
Disable();
}
public void ResetSubstitutions(IEnumerable<Utf8GamePath> paths)
{
if (!_uiBuilder.UiPrepared)
return;
var transformed = paths
.Where(p => (p.Path.StartsWith("ui/"u8) || p.Path.StartsWith("common/font/"u8)) && p.Path.EndsWith(".tex"u8))
.Select(p => p.ToString());
_substitution.InvalidatePaths(transformed);
}
public void Enable()
{
if (Enabled)
return;
_config.UseDalamudUiTextureRedirection = true;
_config.Save();
Subscribe();
}
public void Disable()
{
if (!Enabled)
return;
Unsubscribe();
_config.UseDalamudUiTextureRedirection = false;
_config.Save();
}
public void Dispose()
=> Unsubscribe();
private void OnCollectionChange(CollectionType type, ModCollection? oldCollection, ModCollection? newCollection, string _)
{
if (type is not CollectionType.Interface)
return;
var enumerable = oldCollection?.ResolvedFiles.Keys ?? Array.Empty<Utf8GamePath>().AsEnumerable();
enumerable = enumerable.Concat(newCollection?.ResolvedFiles.Keys ?? Array.Empty<Utf8GamePath>().AsEnumerable());
ResetSubstitutions(enumerable);
}
private void OnResolvedFileChange(ModCollection collection, ResolvedFileChanged.Type type, Utf8GamePath key, FullPath _1, FullPath _2,
IMod? _3)
{
if (_activeCollectionData.Interface != collection)
return;
switch (type)
{
case ResolvedFileChanged.Type.Added:
case ResolvedFileChanged.Type.Removed:
case ResolvedFileChanged.Type.Replaced:
ResetSubstitutions([key]);
break;
case ResolvedFileChanged.Type.FullRecomputeStart:
case ResolvedFileChanged.Type.FullRecomputeFinished:
ResetSubstitutions(collection.ResolvedFiles.Keys);
break;
}
}
private void OnEnabledChange(bool state)
{
if (state)
OnCollectionChange(CollectionType.Interface, null, _activeCollectionData.Interface, string.Empty);
else
OnCollectionChange(CollectionType.Interface, _activeCollectionData.Interface, null, string.Empty);
}
private void Substitute(string path, ref string? replacementPath)
{
// Do not replace when not enabled.
if (!_config.EnableMods)
return;
// Let other plugins prioritize replacement paths.
if (replacementPath != null)
return;
// Only replace interface textures.
if (!path.StartsWith("ui/") && !path.StartsWith("common/font/"))
return;
try
{
if (!Utf8GamePath.FromString(path, out var utf8Path))
return;
var resolved = _activeCollectionData.Interface.ResolvePath(utf8Path);
replacementPath = resolved?.FullName;
}
catch
{
// ignored
}
}
private void Subscribe()
{
_substitution.InterceptTexDataLoad += Substitute;
_communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.DalamudSubstitutionProvider);
_communicator.ResolvedFileChanged.Subscribe(OnResolvedFileChange, ResolvedFileChanged.Priority.DalamudSubstitutionProvider);
_communicator.EnabledChanged.Subscribe(OnEnabledChange, EnabledChanged.Priority.DalamudSubstitutionProvider);
OnCollectionChange(CollectionType.Interface, null, _activeCollectionData.Interface, string.Empty);
}
private void Unsubscribe()
{
_substitution.InterceptTexDataLoad -= Substitute;
_communicator.CollectionChange.Unsubscribe(OnCollectionChange);
_communicator.ResolvedFileChanged.Unsubscribe(OnResolvedFileChange);
_communicator.EnabledChanged.Unsubscribe(OnEnabledChange);
OnCollectionChange(CollectionType.Interface, _activeCollectionData.Interface, null, string.Empty);
}
}

203
Penumbra/Api/HttpApi.cs Normal file
View file

@ -0,0 +1,203 @@
using Dalamud.Plugin.Services;
using EmbedIO;
using EmbedIO.Routing;
using EmbedIO.WebApi;
using OtterGui.Services;
using Penumbra.Api.Api;
using Penumbra.Api.Enums;
using Penumbra.Mods.Settings;
namespace Penumbra.Api;
public class HttpApi : IDisposable, IApiService
{
private partial class Controller : WebApiController
{
// @formatter:off
[Route( HttpVerbs.Get, "/moddirectory" )] public partial string GetModDirectory();
[Route( HttpVerbs.Get, "/mods" )] public partial object? GetMods();
[Route( HttpVerbs.Post, "/redraw" )] public partial Task Redraw();
[Route( HttpVerbs.Post, "/redrawAll" )] public partial Task RedrawAll();
[Route( HttpVerbs.Post, "/reloadmod" )] public partial Task ReloadMod();
[Route( HttpVerbs.Post, "/installmod" )] public partial Task InstallMod();
[Route( HttpVerbs.Post, "/openwindow" )] public partial void OpenWindow();
[Route( HttpVerbs.Post, "/focusmod" )] public partial Task FocusMod();
[Route( HttpVerbs.Post, "/setmodsettings")] public partial Task SetModSettings();
// @formatter:on
}
public const string Prefix = "http://localhost:42069/";
private readonly IPenumbraApi _api;
private readonly IFramework _framework;
private WebServer? _server;
public HttpApi(Configuration config, IPenumbraApi api, IFramework framework)
{
_api = api;
_framework = framework;
if (config.EnableHttpApi)
CreateWebServer();
}
public bool Enabled
=> _server != null;
public void CreateWebServer()
{
ShutdownWebServer();
_server = new WebServer(o => o
.WithUrlPrefix(Prefix)
.WithMode(HttpListenerMode.EmbedIO))
.WithCors(Prefix)
.WithWebApi("/api", m => m.WithController(() => new Controller(_api, _framework)));
_server.StateChanged += (_, e) => Penumbra.Log.Information($"WebServer New State - {e.NewState}");
_server.RunAsync();
}
public void ShutdownWebServer()
{
_server?.Dispose();
_server = null;
}
public void Dispose()
=> ShutdownWebServer();
private partial class Controller(IPenumbraApi api, IFramework framework)
{
public partial string GetModDirectory()
{
Penumbra.Log.Debug($"[HTTP] {nameof(GetModDirectory)} triggered.");
return api.PluginState.GetModDirectory();
}
public partial object? GetMods()
{
Penumbra.Log.Debug($"[HTTP] {nameof(GetMods)} triggered.");
return api.Mods.GetModList();
}
public async partial Task Redraw()
{
var data = await HttpContext.GetRequestDataAsync<RedrawData>().ConfigureAwait(false);
Penumbra.Log.Debug($"[HTTP] [{Environment.CurrentManagedThreadId}] {nameof(Redraw)} triggered with {data}.");
await framework.RunOnFrameworkThread(() =>
{
if (data.ObjectTableIndex >= 0)
api.Redraw.RedrawObject(data.ObjectTableIndex, data.Type);
else
api.Redraw.RedrawAll(data.Type);
}).ConfigureAwait(false);
}
public async partial Task RedrawAll()
{
Penumbra.Log.Debug($"[HTTP] {nameof(RedrawAll)} triggered.");
await framework.RunOnFrameworkThread(() => { api.Redraw.RedrawAll(RedrawType.Redraw); }).ConfigureAwait(false);
}
public async partial Task ReloadMod()
{
var data = await HttpContext.GetRequestDataAsync<ModReloadData>().ConfigureAwait(false);
Penumbra.Log.Debug($"[HTTP] {nameof(ReloadMod)} triggered with {data}.");
// Add the mod if it is not already loaded and if the directory name is given.
// AddMod returns Success if the mod is already loaded.
if (data.Path.Length != 0)
api.Mods.AddMod(data.Path);
// Reload the mod by path or name, which will also remove no-longer existing mods.
api.Mods.ReloadMod(data.Path, data.Name);
}
public async partial Task InstallMod()
{
var data = await HttpContext.GetRequestDataAsync<ModInstallData>().ConfigureAwait(false);
Penumbra.Log.Debug($"[HTTP] {nameof(InstallMod)} triggered with {data}.");
if (data.Path.Length != 0)
api.Mods.InstallMod(data.Path);
}
public partial void OpenWindow()
{
Penumbra.Log.Debug($"[HTTP] {nameof(OpenWindow)} triggered.");
api.Ui.OpenMainWindow(TabType.Mods, string.Empty, string.Empty);
}
public async partial Task FocusMod()
{
var data = await HttpContext.GetRequestDataAsync<ModFocusData>().ConfigureAwait(false);
Penumbra.Log.Debug($"[HTTP] {nameof(FocusMod)} triggered.");
if (data.Path.Length != 0)
api.Ui.OpenMainWindow(TabType.Mods, data.Path, data.Name);
}
public async partial Task SetModSettings()
{
var data = await HttpContext.GetRequestDataAsync<SetModSettingsData>().ConfigureAwait(false);
Penumbra.Log.Debug($"[HTTP] {nameof(SetModSettings)} triggered.");
await framework.RunOnFrameworkThread(() =>
{
var collection = data.CollectionId ?? api.Collection.GetCollection(ApiCollectionType.Current)!.Value.Id;
if (data.Inherit.HasValue)
{
api.ModSettings.TryInheritMod(collection, data.ModPath, data.ModName, data.Inherit.Value);
if (data.Inherit.Value)
return;
}
if (data.State.HasValue)
api.ModSettings.TrySetMod(collection, data.ModPath, data.ModName, data.State.Value);
if (data.Priority.HasValue)
api.ModSettings.TrySetModPriority(collection, data.ModPath, data.ModName, data.Priority.Value);
foreach (var (group, settings) in data.Settings ?? [])
api.ModSettings.TrySetModSettings(collection, data.ModPath, data.ModName, group, settings);
}
).ConfigureAwait(false);
}
private record ModReloadData(string Path, string Name)
{
public ModReloadData()
: this(string.Empty, string.Empty)
{ }
}
private record ModFocusData(string Path, string Name)
{
public ModFocusData()
: this(string.Empty, string.Empty)
{ }
}
private record ModInstallData(string Path)
{
public ModInstallData()
: this(string.Empty)
{ }
}
private record RedrawData(string Name, RedrawType Type, int ObjectTableIndex)
{
public RedrawData()
: this(string.Empty, RedrawType.Redraw, -1)
{ }
}
private record SetModSettingsData(
Guid? CollectionId,
string ModPath,
string ModName,
bool? Inherit,
bool? State,
int? Priority,
Dictionary<string, List<string>>? Settings)
{
public SetModSettingsData()
: this(null, string.Empty, string.Empty, null, null, null, null)
{}
}
}
}

View file

@ -1,47 +0,0 @@
using Dalamud.Game.ClientState.Objects.Types;
using Lumina.Data;
using Penumbra.GameData.Enums;
namespace Penumbra.Api
{
public interface IPenumbraApiBase
{
public int ApiVersion { get; }
public bool Valid { get; }
}
public delegate void ChangedItemHover( object? item );
public delegate void ChangedItemClick( MouseButton button, object? item );
public interface IPenumbraApi : IPenumbraApiBase
{
// Triggered when the user hovers over a listed changed object in a mod tab.
// Can be used to append tooltips.
public event ChangedItemHover? ChangedItemTooltip;
// Triggered when the user clicks a listed changed object in a mod tab.
public event ChangedItemClick? ChangedItemClicked;
// Queue redrawing of all actors of the given name with the given RedrawType.
public void RedrawObject( string name, RedrawType setting );
// Queue redrawing of the specific actor with the given RedrawType. Should only be used when the actor is sure to be valid.
public void RedrawObject( GameObject gameObject, RedrawType setting );
// Queue redrawing of all currently available actors with the given RedrawType.
public void RedrawAll( RedrawType setting );
// Resolve a given gamePath via Penumbra using the Default and Forced collections.
// Returns the given gamePath if penumbra would not manipulate it.
public string ResolvePath(string gamePath);
// Resolve a given gamePath via Penumbra using the character collection for the given name (if it exists) and the Forced collections.
// Returns the given gamePath if penumbra would not manipulate it.
public string ResolvePath( string gamePath, string characterName );
// Try to load a given gamePath with the resolved path from Penumbra.
public T? GetFile< T >( string gamePath ) where T : FileResource;
// Try to load a given gamePath with the resolved path from Penumbra.
public T? GetFile<T>( string gamePath, string characterName ) where T : FileResource;
}
}

View file

@ -0,0 +1,28 @@
using Dalamud.Plugin;
using OtterGui.Log;
using OtterGui.Services;
using Penumbra.Api.Api;
using Serilog.Events;
namespace Penumbra.Api;
public sealed class IpcLaunchingProvider : IApiService
{
public IpcLaunchingProvider(IDalamudPluginInterface pi, Logger log)
{
try
{
using var subscriber = log.MainLogger.IsEnabled(LogEventLevel.Debug)
? IpcSubscribers.Launching.Subscriber(pi,
(major, minor) => log.Debug($"[IPC] Invoked Penumbra.Launching IPC with API Version {major}.{minor}."))
: null;
using var provider = IpcSubscribers.Launching.Provider(pi);
provider.Invoke(PenumbraApi.BreakingVersion, PenumbraApi.FeatureVersion);
}
catch (Exception ex)
{
log.Error($"[IPC] Could not invoke Penumbra.Launching IPC:\n{ex}");
}
}
}

View file

@ -0,0 +1,161 @@
using Dalamud.Plugin;
using OtterGui.Services;
using Penumbra.Api.Api;
using Penumbra.Api.Helpers;
using Penumbra.Communication;
using CharacterUtility = Penumbra.Interop.Services.CharacterUtility;
namespace Penumbra.Api;
public sealed class IpcProviders : IDisposable, IApiService
{
private readonly List<IDisposable> _providers;
private readonly EventProvider _disposedProvider;
private readonly EventProvider _initializedProvider;
private readonly CharacterUtility _characterUtility;
public IpcProviders(IDalamudPluginInterface pi, IPenumbraApi api, CharacterUtility characterUtility)
{
_characterUtility = characterUtility;
_disposedProvider = IpcSubscribers.Disposed.Provider(pi);
_initializedProvider = IpcSubscribers.Initialized.Provider(pi);
_providers =
[
IpcSubscribers.GetCollections.Provider(pi, api.Collection),
IpcSubscribers.GetCollectionsByIdentifier.Provider(pi, api.Collection),
IpcSubscribers.GetChangedItemsForCollection.Provider(pi, api.Collection),
IpcSubscribers.GetCollection.Provider(pi, api.Collection),
IpcSubscribers.GetCollectionForObject.Provider(pi, api.Collection),
IpcSubscribers.SetCollection.Provider(pi, api.Collection),
IpcSubscribers.SetCollectionForObject.Provider(pi, api.Collection),
IpcSubscribers.CheckCurrentChangedItemFunc.Provider(pi, api.Collection),
IpcSubscribers.ConvertTextureFile.Provider(pi, api.Editing),
IpcSubscribers.ConvertTextureData.Provider(pi, api.Editing),
IpcSubscribers.GetDrawObjectInfo.Provider(pi, api.GameState),
IpcSubscribers.GetCutsceneParentIndex.Provider(pi, api.GameState),
IpcSubscribers.SetCutsceneParentIndex.Provider(pi, api.GameState),
IpcSubscribers.CreatingCharacterBase.Provider(pi, api.GameState),
IpcSubscribers.CreatedCharacterBase.Provider(pi, api.GameState),
IpcSubscribers.GameObjectResourcePathResolved.Provider(pi, api.GameState),
IpcSubscribers.GetCutsceneParentIndexFunc.Provider(pi, api.GameState),
IpcSubscribers.GetGameObjectFromDrawObjectFunc.Provider(pi, api.GameState),
IpcSubscribers.GetPlayerMetaManipulations.Provider(pi, api.Meta),
IpcSubscribers.GetMetaManipulations.Provider(pi, api.Meta),
IpcSubscribers.GetModList.Provider(pi, api.Mods),
IpcSubscribers.InstallMod.Provider(pi, api.Mods),
IpcSubscribers.ReloadMod.Provider(pi, api.Mods),
IpcSubscribers.AddMod.Provider(pi, api.Mods),
IpcSubscribers.DeleteMod.Provider(pi, api.Mods),
IpcSubscribers.ModDeleted.Provider(pi, api.Mods),
IpcSubscribers.ModAdded.Provider(pi, api.Mods),
IpcSubscribers.ModMoved.Provider(pi, api.Mods),
IpcSubscribers.CreatingPcp.Provider(pi, api.Mods),
IpcSubscribers.ParsingPcp.Provider(pi, api.Mods),
IpcSubscribers.GetModPath.Provider(pi, api.Mods),
IpcSubscribers.SetModPath.Provider(pi, api.Mods),
IpcSubscribers.GetChangedItems.Provider(pi, api.Mods),
IpcSubscribers.GetChangedItemAdapterDictionary.Provider(pi, api.Mods),
IpcSubscribers.GetChangedItemAdapterList.Provider(pi, api.Mods),
IpcSubscribers.GetAvailableModSettings.Provider(pi, api.ModSettings),
IpcSubscribers.GetCurrentModSettings.Provider(pi, api.ModSettings),
IpcSubscribers.GetCurrentModSettingsWithTemp.Provider(pi, api.ModSettings),
IpcSubscribers.GetAllModSettings.Provider(pi, api.ModSettings),
IpcSubscribers.GetSettingsInAllCollections.Provider(pi, api.ModSettings),
IpcSubscribers.TryInheritMod.Provider(pi, api.ModSettings),
IpcSubscribers.TrySetMod.Provider(pi, api.ModSettings),
IpcSubscribers.TrySetModPriority.Provider(pi, api.ModSettings),
IpcSubscribers.TrySetModSetting.Provider(pi, api.ModSettings),
IpcSubscribers.TrySetModSettings.Provider(pi, api.ModSettings),
IpcSubscribers.ModSettingChanged.Provider(pi, api.ModSettings),
IpcSubscribers.CopyModSettings.Provider(pi, api.ModSettings),
IpcSubscribers.ApiVersion.Provider(pi, api),
new FuncProvider<(int Major, int Minor)>(pi, "Penumbra.ApiVersions", () => api.ApiVersion), // backward compatibility
new FuncProvider<int>(pi, "Penumbra.ApiVersion", () => api.ApiVersion.Breaking), // backward compatibility
IpcSubscribers.GetModDirectory.Provider(pi, api.PluginState),
IpcSubscribers.GetConfiguration.Provider(pi, api.PluginState),
IpcSubscribers.ModDirectoryChanged.Provider(pi, api.PluginState),
IpcSubscribers.GetEnabledState.Provider(pi, api.PluginState),
IpcSubscribers.EnabledChange.Provider(pi, api.PluginState),
IpcSubscribers.SupportedFeatures.Provider(pi, api.PluginState),
IpcSubscribers.CheckSupportedFeatures.Provider(pi, api.PluginState),
IpcSubscribers.RedrawObject.Provider(pi, api.Redraw),
IpcSubscribers.RedrawAll.Provider(pi, api.Redraw),
IpcSubscribers.GameObjectRedrawn.Provider(pi, api.Redraw),
IpcSubscribers.RedrawCollectionMembers.Provider(pi, api.Redraw),
IpcSubscribers.ResolveDefaultPath.Provider(pi, api.Resolve),
IpcSubscribers.ResolveInterfacePath.Provider(pi, api.Resolve),
IpcSubscribers.ResolveGameObjectPath.Provider(pi, api.Resolve),
IpcSubscribers.ResolvePlayerPath.Provider(pi, api.Resolve),
IpcSubscribers.ReverseResolveGameObjectPath.Provider(pi, api.Resolve),
IpcSubscribers.ReverseResolvePlayerPath.Provider(pi, api.Resolve),
IpcSubscribers.ResolvePlayerPaths.Provider(pi, api.Resolve),
IpcSubscribers.ResolvePlayerPathsAsync.Provider(pi, api.Resolve),
IpcSubscribers.ResolvePath.Provider(pi, api.Resolve),
IpcSubscribers.ResolvePaths.Provider(pi, api.Resolve),
IpcSubscribers.GetGameObjectResourcePaths.Provider(pi, api.ResourceTree),
IpcSubscribers.GetPlayerResourcePaths.Provider(pi, api.ResourceTree),
IpcSubscribers.GetGameObjectResourcesOfType.Provider(pi, api.ResourceTree),
IpcSubscribers.GetPlayerResourcesOfType.Provider(pi, api.ResourceTree),
IpcSubscribers.GetGameObjectResourceTrees.Provider(pi, api.ResourceTree),
IpcSubscribers.GetPlayerResourceTrees.Provider(pi, api.ResourceTree),
IpcSubscribers.CreateTemporaryCollection.Provider(pi, api.Temporary),
IpcSubscribers.DeleteTemporaryCollection.Provider(pi, api.Temporary),
IpcSubscribers.AssignTemporaryCollection.Provider(pi, api.Temporary),
IpcSubscribers.AddTemporaryModAll.Provider(pi, api.Temporary),
IpcSubscribers.AddTemporaryMod.Provider(pi, api.Temporary),
IpcSubscribers.RemoveTemporaryModAll.Provider(pi, api.Temporary),
IpcSubscribers.RemoveTemporaryMod.Provider(pi, api.Temporary),
IpcSubscribers.SetTemporaryModSettings.Provider(pi, api.Temporary),
IpcSubscribers.SetTemporaryModSettingsPlayer.Provider(pi, api.Temporary),
IpcSubscribers.RemoveTemporaryModSettings.Provider(pi, api.Temporary),
IpcSubscribers.RemoveTemporaryModSettingsPlayer.Provider(pi, api.Temporary),
IpcSubscribers.RemoveAllTemporaryModSettings.Provider(pi, api.Temporary),
IpcSubscribers.RemoveAllTemporaryModSettingsPlayer.Provider(pi, api.Temporary),
IpcSubscribers.QueryTemporaryModSettings.Provider(pi, api.Temporary),
IpcSubscribers.QueryTemporaryModSettingsPlayer.Provider(pi, api.Temporary),
IpcSubscribers.ChangedItemTooltip.Provider(pi, api.Ui),
IpcSubscribers.ChangedItemClicked.Provider(pi, api.Ui),
IpcSubscribers.PreSettingsTabBarDraw.Provider(pi, api.Ui),
IpcSubscribers.PreSettingsDraw.Provider(pi, api.Ui),
IpcSubscribers.PostEnabledDraw.Provider(pi, api.Ui),
IpcSubscribers.PostSettingsDraw.Provider(pi, api.Ui),
IpcSubscribers.OpenMainWindow.Provider(pi, api.Ui),
IpcSubscribers.CloseMainWindow.Provider(pi, api.Ui),
IpcSubscribers.RegisterSettingsSection.Provider(pi, api.Ui),
IpcSubscribers.UnregisterSettingsSection.Provider(pi, api.Ui),
];
if (_characterUtility.Ready)
_initializedProvider.Invoke();
else
_characterUtility.LoadingFinished.Subscribe(OnCharacterUtilityReady, CharacterUtilityFinished.Priority.IpcProvider);
}
private void OnCharacterUtilityReady()
{
_initializedProvider.Invoke();
_characterUtility.LoadingFinished.Unsubscribe(OnCharacterUtilityReady);
}
public void Dispose()
{
_characterUtility.LoadingFinished.Unsubscribe(OnCharacterUtilityReady);
foreach (var provider in _providers)
provider.Dispose();
_providers.Clear();
_initializedProvider.Dispose();
_disposedProvider.Invoke();
_disposedProvider.Dispose();
}
}

View file

@ -0,0 +1,189 @@
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Dalamud.Plugin;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Api.IpcSubscribers;
using Penumbra.Collections.Manager;
using Penumbra.GameData.Data;
using ImGuiClip = OtterGui.ImGuiClip;
namespace Penumbra.Api.IpcTester;
public class CollectionsIpcTester(IDalamudPluginInterface pi) : IUiService
{
private int _objectIdx;
private string _collectionIdString = string.Empty;
private Guid? _collectionId;
private bool _allowCreation = true;
private bool _allowDeletion = true;
private ApiCollectionType _type = ApiCollectionType.Yourself;
private Dictionary<Guid, string> _collections = [];
private (string, ChangedItemType, uint)[] _changedItems = [];
private PenumbraApiEc _returnCode = PenumbraApiEc.Success;
private (Guid Id, string Name)? _oldCollection;
public void Draw()
{
using var _ = ImRaii.TreeNode("Collections");
if (!_)
return;
ImGuiUtil.GenericEnumCombo("Collection Type", 200, _type, out _type, t => ((CollectionType)t).ToName());
ImGui.InputInt("Object Index##Collections", ref _objectIdx, 0, 0);
ImGuiUtil.GuidInput("Collection Id##Collections", "Collection Identifier...", string.Empty, ref _collectionId, ref _collectionIdString);
ImGui.Checkbox("Allow Assignment Creation", ref _allowCreation);
ImGui.SameLine();
ImGui.Checkbox("Allow Assignment Deletion", ref _allowDeletion);
using var table = ImRaii.Table(string.Empty, 4, ImGuiTableFlags.SizingFixedFit);
if (!table)
return;
IpcTester.DrawIntro("Last Return Code", _returnCode.ToString());
if (_oldCollection != null)
ImGui.TextUnformatted(!_oldCollection.HasValue ? "Created" : _oldCollection.ToString());
IpcTester.DrawIntro(GetCollectionsByIdentifier.Label, "Collection Identifier");
var collectionList = new GetCollectionsByIdentifier(pi).Invoke(_collectionIdString);
if (collectionList.Count == 0)
{
DrawCollection(null);
}
else
{
DrawCollection(collectionList[0]);
foreach (var pair in collectionList.Skip(1))
{
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.TableNextColumn();
ImGui.TableNextColumn();
DrawCollection(pair);
}
}
IpcTester.DrawIntro(GetCollection.Label, "Current Collection");
DrawCollection(new GetCollection(pi).Invoke(ApiCollectionType.Current));
IpcTester.DrawIntro(GetCollection.Label, "Default Collection");
DrawCollection(new GetCollection(pi).Invoke(ApiCollectionType.Default));
IpcTester.DrawIntro(GetCollection.Label, "Interface Collection");
DrawCollection(new GetCollection(pi).Invoke(ApiCollectionType.Interface));
IpcTester.DrawIntro(GetCollection.Label, "Special Collection");
DrawCollection(new GetCollection(pi).Invoke(_type));
IpcTester.DrawIntro(GetCollections.Label, "Collections");
DrawCollectionPopup();
if (ImGui.Button("Get##Collections"))
{
_collections = new GetCollections(pi).Invoke();
ImGui.OpenPopup("Collections");
}
IpcTester.DrawIntro(GetCollectionForObject.Label, "Get Object Collection");
var (valid, individual, effectiveCollection) = new GetCollectionForObject(pi).Invoke(_objectIdx);
DrawCollection(effectiveCollection);
ImGui.SameLine();
ImGui.TextUnformatted($"({(valid ? "Valid" : "Invalid")} Object{(individual ? ", Individual Assignment)" : ")")}");
IpcTester.DrawIntro(SetCollection.Label, "Set Special Collection");
if (ImGui.Button("Set##SpecialCollection"))
(_returnCode, _oldCollection) =
new SetCollection(pi).Invoke(_type, _collectionId.GetValueOrDefault(Guid.Empty), _allowCreation, _allowDeletion);
ImGui.TableNextColumn();
if (ImGui.Button("Remove##SpecialCollection"))
(_returnCode, _oldCollection) = new SetCollection(pi).Invoke(_type, null, _allowCreation, _allowDeletion);
IpcTester.DrawIntro(SetCollectionForObject.Label, "Set Object Collection");
if (ImGui.Button("Set##ObjectCollection"))
(_returnCode, _oldCollection) = new SetCollectionForObject(pi).Invoke(_objectIdx, _collectionId.GetValueOrDefault(Guid.Empty),
_allowCreation, _allowDeletion);
ImGui.TableNextColumn();
if (ImGui.Button("Remove##ObjectCollection"))
(_returnCode, _oldCollection) = new SetCollectionForObject(pi).Invoke(_objectIdx, null, _allowCreation, _allowDeletion);
IpcTester.DrawIntro(GetChangedItemsForCollection.Label, "Changed Item List");
DrawChangedItemPopup();
if (ImGui.Button("Get##ChangedItems"))
{
var items = new GetChangedItemsForCollection(pi).Invoke(_collectionId.GetValueOrDefault(Guid.Empty));
_changedItems = items.Select(kvp =>
{
var (type, id) = kvp.Value.ToApiObject();
return (kvp.Key, type, id);
}).ToArray();
ImGui.OpenPopup("Changed Item List");
}
IpcTester.DrawIntro(RedrawCollectionMembers.Label, "Redraw Collection Members");
if (ImGui.Button("Redraw##ObjectCollection"))
new RedrawCollectionMembers(pi).Invoke(collectionList[0].Id, RedrawType.Redraw);
}
private void DrawChangedItemPopup()
{
ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
using var p = ImRaii.Popup("Changed Item List");
if (!p)
return;
using (var table = ImRaii.Table("##ChangedItems", 3, ImGuiTableFlags.SizingFixedFit))
{
if (table)
ImGuiClip.ClippedDraw(_changedItems, t =>
{
ImGuiUtil.DrawTableColumn(t.Item1);
ImGuiUtil.DrawTableColumn(t.Item2.ToString());
ImGuiUtil.DrawTableColumn(t.Item3.ToString());
}, ImGui.GetTextLineHeightWithSpacing());
}
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
ImGui.CloseCurrentPopup();
}
private void DrawCollectionPopup()
{
ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
using var p = ImRaii.Popup("Collections");
if (!p)
return;
using (var t = ImRaii.Table("collections", 2, ImGuiTableFlags.SizingFixedFit))
{
if (t)
foreach (var collection in _collections)
{
ImGui.TableNextColumn();
DrawCollection((collection.Key, collection.Value));
}
}
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
ImGui.CloseCurrentPopup();
}
private static void DrawCollection((Guid Id, string Name)? collection)
{
if (collection == null)
{
ImGui.TextUnformatted("<Unassigned>");
ImGui.TableNextColumn();
return;
}
ImGui.TextUnformatted(collection.Value.Name);
ImGui.TableNextColumn();
using (ImRaii.PushFont(UiBuilder.MonoFont))
{
ImGuiUtil.CopyOnClickSelectable(collection.Value.Id.ToString());
}
}
}

View file

@ -0,0 +1,70 @@
using Dalamud.Bindings.ImGui;
using Dalamud.Plugin;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Api.IpcSubscribers;
namespace Penumbra.Api.IpcTester;
public class EditingIpcTester(IDalamudPluginInterface pi) : IUiService
{
private string _inputPath = string.Empty;
private string _inputPath2 = string.Empty;
private string _outputPath = string.Empty;
private string _outputPath2 = string.Empty;
private TextureType _typeSelector;
private bool _mipMaps = true;
private Task? _task1;
private Task? _task2;
public void Draw()
{
using var _ = ImRaii.TreeNode("Editing");
if (!_)
return;
ImGui.InputTextWithHint("##inputPath", "Input Texture Path...", ref _inputPath, 256);
ImGui.InputTextWithHint("##outputPath", "Output Texture Path...", ref _outputPath, 256);
ImGui.InputTextWithHint("##inputPath2", "Input Texture Path 2...", ref _inputPath2, 256);
ImGui.InputTextWithHint("##outputPath2", "Output Texture Path 2...", ref _outputPath2, 256);
TypeCombo();
ImGui.Checkbox("Add MipMaps", ref _mipMaps);
using var table = ImRaii.Table("...", 3, ImGuiTableFlags.SizingFixedFit);
if (!table)
return;
IpcTester.DrawIntro(ConvertTextureFile.Label, (string)"Convert Texture 1");
if (ImGuiUtil.DrawDisabledButton("Save 1", Vector2.Zero, string.Empty, _task1 is { IsCompleted: false }))
_task1 = new ConvertTextureFile(pi).Invoke(_inputPath, _outputPath, _typeSelector, _mipMaps);
ImGui.SameLine();
ImGui.TextUnformatted(_task1 == null ? "Not Initiated" : _task1.Status.ToString());
if (ImGui.IsItemHovered() && _task1?.Status == TaskStatus.Faulted)
ImGui.SetTooltip(_task1.Exception?.ToString());
IpcTester.DrawIntro(ConvertTextureFile.Label, (string)"Convert Texture 2");
if (ImGuiUtil.DrawDisabledButton("Save 2", Vector2.Zero, string.Empty, _task2 is { IsCompleted: false }))
_task2 = new ConvertTextureFile(pi).Invoke(_inputPath2, _outputPath2, _typeSelector, _mipMaps);
ImGui.SameLine();
ImGui.TextUnformatted(_task2 == null ? "Not Initiated" : _task2.Status.ToString());
if (ImGui.IsItemHovered() && _task2?.Status == TaskStatus.Faulted)
ImGui.SetTooltip(_task2.Exception?.ToString());
}
private void TypeCombo()
{
using var combo = ImRaii.Combo("Convert To", _typeSelector.ToString());
if (!combo)
return;
foreach (var value in Enum.GetValues<TextureType>())
{
if (ImGui.Selectable(value.ToString(), _typeSelector == value))
_typeSelector = value;
}
}
}

View file

@ -0,0 +1,139 @@
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Plugin;
using OtterGui.Raii;
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Api.Helpers;
using Penumbra.Api.IpcSubscribers;
using Penumbra.String;
namespace Penumbra.Api.IpcTester;
public class GameStateIpcTester : IUiService, IDisposable
{
private readonly IDalamudPluginInterface _pi;
public readonly EventSubscriber<nint, Guid, nint, nint, nint> CharacterBaseCreating;
public readonly EventSubscriber<nint, Guid, nint> CharacterBaseCreated;
public readonly EventSubscriber<nint, string, string> GameObjectResourcePathResolved;
private string _lastCreatedGameObjectName = string.Empty;
private nint _lastCreatedDrawObject = nint.Zero;
private DateTimeOffset _lastCreatedGameObjectTime = DateTimeOffset.MaxValue;
private string _lastResolvedGamePath = string.Empty;
private string _lastResolvedFullPath = string.Empty;
private string _lastResolvedObject = string.Empty;
private DateTimeOffset _lastResolvedGamePathTime = DateTimeOffset.MaxValue;
private string _currentDrawObjectString = string.Empty;
private nint _currentDrawObject = nint.Zero;
private int _currentCutsceneActor;
private int _currentCutsceneParent;
private PenumbraApiEc _cutsceneError = PenumbraApiEc.Success;
public GameStateIpcTester(IDalamudPluginInterface pi)
{
_pi = pi;
CharacterBaseCreating = IpcSubscribers.CreatingCharacterBase.Subscriber(pi, UpdateLastCreated);
CharacterBaseCreated = IpcSubscribers.CreatedCharacterBase.Subscriber(pi, UpdateLastCreated2);
GameObjectResourcePathResolved = IpcSubscribers.GameObjectResourcePathResolved.Subscriber(pi, UpdateGameObjectResourcePath);
CharacterBaseCreating.Disable();
CharacterBaseCreated.Disable();
GameObjectResourcePathResolved.Disable();
}
public void Dispose()
{
CharacterBaseCreating.Dispose();
CharacterBaseCreated.Dispose();
GameObjectResourcePathResolved.Dispose();
}
public void Draw()
{
using var _ = ImRaii.TreeNode("Game State");
if (!_)
return;
if (ImGui.InputTextWithHint("##drawObject", "Draw Object Address..", ref _currentDrawObjectString, 16,
ImGuiInputTextFlags.CharsHexadecimal))
_currentDrawObject = nint.TryParse(_currentDrawObjectString, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
out var tmp)
? tmp
: nint.Zero;
ImGui.InputInt("Cutscene Actor", ref _currentCutsceneActor, 0);
ImGui.InputInt("Cutscene Parent", ref _currentCutsceneParent, 0);
if (_cutsceneError is not PenumbraApiEc.Success)
{
ImGui.SameLine();
ImGui.TextUnformatted("Invalid Argument on last Call");
}
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
if (!table)
return;
IpcTester.DrawIntro(GetDrawObjectInfo.Label, "Draw Object Info");
if (_currentDrawObject == nint.Zero)
{
ImGui.TextUnformatted("Invalid");
}
else
{
var (ptr, (collectionId, collectionName)) = new GetDrawObjectInfo(_pi).Invoke(_currentDrawObject);
ImGui.TextUnformatted(ptr == nint.Zero ? $"No Actor Associated, {collectionName}" : $"{ptr:X}, {collectionName}");
ImGui.SameLine();
using (ImRaii.PushFont(UiBuilder.MonoFont))
{
ImGui.TextUnformatted(collectionId.ToString());
}
}
IpcTester.DrawIntro(GetCutsceneParentIndex.Label, "Cutscene Parent");
ImGui.TextUnformatted(new GetCutsceneParentIndex(_pi).Invoke(_currentCutsceneActor).ToString());
IpcTester.DrawIntro(SetCutsceneParentIndex.Label, "Cutscene Parent");
if (ImGui.Button("Set Parent"))
_cutsceneError = new SetCutsceneParentIndex(_pi)
.Invoke(_currentCutsceneActor, _currentCutsceneParent);
IpcTester.DrawIntro(CreatingCharacterBase.Label, "Last Drawobject created");
if (_lastCreatedGameObjectTime < DateTimeOffset.Now)
ImGui.TextUnformatted(_lastCreatedDrawObject != nint.Zero
? $"0x{_lastCreatedDrawObject:X} for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}"
: $"NULL for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}");
IpcTester.DrawIntro(IpcSubscribers.GameObjectResourcePathResolved.Label, "Last GamePath resolved");
if (_lastResolvedGamePathTime < DateTimeOffset.Now)
ImGui.TextUnformatted(
$"{_lastResolvedGamePath} -> {_lastResolvedFullPath} for <{_lastResolvedObject}> at {_lastResolvedGamePathTime}");
}
private void UpdateLastCreated(nint gameObject, Guid _, nint _2, nint _3, nint _4)
{
_lastCreatedGameObjectName = GetObjectName(gameObject);
_lastCreatedGameObjectTime = DateTimeOffset.Now;
_lastCreatedDrawObject = nint.Zero;
}
private void UpdateLastCreated2(nint gameObject, Guid _, nint drawObject)
{
_lastCreatedGameObjectName = GetObjectName(gameObject);
_lastCreatedGameObjectTime = DateTimeOffset.Now;
_lastCreatedDrawObject = drawObject;
}
private void UpdateGameObjectResourcePath(nint gameObject, string gamePath, string fullPath)
{
_lastResolvedObject = GetObjectName(gameObject);
_lastResolvedGamePath = gamePath;
_lastResolvedFullPath = fullPath;
_lastResolvedGamePathTime = DateTimeOffset.Now;
}
private static unsafe string GetObjectName(nint gameObject)
{
var obj = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gameObject;
return obj != null && obj->Name[0] != 0 ? new ByteString(obj->Name).ToString() : "Unknown";
}
}

View file

@ -0,0 +1,133 @@
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using Dalamud.Bindings.ImGui;
using OtterGui.Services;
using Penumbra.Api.Api;
namespace Penumbra.Api.IpcTester;
public class IpcTester(
IpcProviders ipcProviders,
IPenumbraApi api,
PluginStateIpcTester pluginStateIpcTester,
UiIpcTester uiIpcTester,
RedrawingIpcTester redrawingIpcTester,
GameStateIpcTester gameStateIpcTester,
ResolveIpcTester resolveIpcTester,
CollectionsIpcTester collectionsIpcTester,
MetaIpcTester metaIpcTester,
ModsIpcTester modsIpcTester,
ModSettingsIpcTester modSettingsIpcTester,
EditingIpcTester editingIpcTester,
TemporaryIpcTester temporaryIpcTester,
ResourceTreeIpcTester resourceTreeIpcTester,
IFramework framework) : IUiService
{
private readonly IpcProviders _ipcProviders = ipcProviders;
private DateTime _lastUpdate;
private bool _subscribed = false;
public void Draw()
{
try
{
_lastUpdate = framework.LastUpdateUTC.AddSeconds(1);
Subscribe();
ImGui.TextUnformatted($"API Version: {api.ApiVersion.Breaking}.{api.ApiVersion.Feature:D4}");
collectionsIpcTester.Draw();
editingIpcTester.Draw();
gameStateIpcTester.Draw();
metaIpcTester.Draw();
modSettingsIpcTester.Draw();
modsIpcTester.Draw();
pluginStateIpcTester.Draw();
redrawingIpcTester.Draw();
resolveIpcTester.Draw();
resourceTreeIpcTester.Draw();
uiIpcTester.Draw();
temporaryIpcTester.Draw();
temporaryIpcTester.DrawCollections();
temporaryIpcTester.DrawMods();
}
catch (Exception e)
{
Penumbra.Log.Error($"Error during IPC Tests:\n{e}");
}
}
internal static void DrawIntro(string label, string info)
{
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.TextUnformatted(label);
ImGui.TableNextColumn();
ImGui.TextUnformatted(info);
ImGui.TableNextColumn();
}
private void Subscribe()
{
if (_subscribed)
return;
Penumbra.Log.Debug("[IPCTester] Subscribed to IPC events for IPC tester.");
gameStateIpcTester.GameObjectResourcePathResolved.Enable();
gameStateIpcTester.CharacterBaseCreated.Enable();
gameStateIpcTester.CharacterBaseCreating.Enable();
modSettingsIpcTester.SettingChanged.Enable();
modsIpcTester.DeleteSubscriber.Enable();
modsIpcTester.AddSubscriber.Enable();
modsIpcTester.MoveSubscriber.Enable();
pluginStateIpcTester.ModDirectoryChanged.Enable();
pluginStateIpcTester.Initialized.Enable();
pluginStateIpcTester.Disposed.Enable();
pluginStateIpcTester.EnabledChange.Enable();
redrawingIpcTester.Redrawn.Enable();
uiIpcTester.PreSettingsTabBar.Enable();
uiIpcTester.PreSettingsPanel.Enable();
uiIpcTester.PostEnabled.Enable();
uiIpcTester.PostSettingsPanelDraw.Enable();
uiIpcTester.ChangedItemTooltip.Enable();
uiIpcTester.ChangedItemClicked.Enable();
framework.Update += CheckUnsubscribe;
_subscribed = true;
}
private void CheckUnsubscribe(IFramework framework1)
{
if (_lastUpdate > framework.LastUpdateUTC)
return;
Unsubscribe();
framework.Update -= CheckUnsubscribe;
}
private void Unsubscribe()
{
if (!_subscribed)
return;
Penumbra.Log.Debug("[IPCTester] Unsubscribed from IPC events for IPC tester.");
_subscribed = false;
gameStateIpcTester.GameObjectResourcePathResolved.Disable();
gameStateIpcTester.CharacterBaseCreated.Disable();
gameStateIpcTester.CharacterBaseCreating.Disable();
modSettingsIpcTester.SettingChanged.Disable();
modsIpcTester.DeleteSubscriber.Disable();
modsIpcTester.AddSubscriber.Disable();
modsIpcTester.MoveSubscriber.Disable();
pluginStateIpcTester.ModDirectoryChanged.Disable();
pluginStateIpcTester.Initialized.Disable();
pluginStateIpcTester.Disposed.Disable();
pluginStateIpcTester.EnabledChange.Disable();
redrawingIpcTester.Redrawn.Disable();
uiIpcTester.PreSettingsTabBar.Disable();
uiIpcTester.PreSettingsPanel.Disable();
uiIpcTester.PostEnabled.Disable();
uiIpcTester.PostSettingsPanelDraw.Disable();
uiIpcTester.ChangedItemTooltip.Disable();
uiIpcTester.ChangedItemClicked.Disable();
}
}

View file

@ -0,0 +1,52 @@
using Dalamud.Bindings.ImGui;
using Dalamud.Plugin;
using OtterGui.Raii;
using OtterGui.Services;
using OtterGui.Text;
using Penumbra.Api.Api;
using Penumbra.Api.IpcSubscribers;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Api.IpcTester;
public class MetaIpcTester(IDalamudPluginInterface pi) : IUiService
{
private int _gameObjectIndex;
private string _metaBase64 = string.Empty;
private MetaDictionary _metaDict = new();
private byte _parsedVersion = byte.MaxValue;
public void Draw()
{
using var _ = ImRaii.TreeNode("Meta");
if (!_)
return;
ImGui.InputInt("##metaIdx", ref _gameObjectIndex, 0, 0);
if (ImUtf8.InputText("##metaText"u8, ref _metaBase64, "Base64 Metadata..."u8))
if (!MetaApi.ConvertManips(_metaBase64, out _metaDict!, out _parsedVersion))
_metaDict ??= new MetaDictionary();
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
if (!table)
return;
IpcTester.DrawIntro(GetPlayerMetaManipulations.Label, "Player Meta Manipulations");
if (ImGui.Button("Copy to Clipboard##Player"))
{
var base64 = new GetPlayerMetaManipulations(pi).Invoke();
ImGui.SetClipboardText(base64);
}
IpcTester.DrawIntro(GetMetaManipulations.Label, "Game Object Manipulations");
if (ImGui.Button("Copy to Clipboard##GameObject"))
{
var base64 = new GetMetaManipulations(pi).Invoke(_gameObjectIndex);
ImGui.SetClipboardText(base64);
}
IpcTester.DrawIntro(string.Empty, "Parsed Data");
ImUtf8.Text($"Version: {_parsedVersion}, Count: {_metaDict.Count}");
}
}

View file

@ -0,0 +1,224 @@
using Dalamud.Bindings.ImGui;
using Dalamud.Plugin;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Services;
using OtterGui.Text;
using Penumbra.Api.Enums;
using Penumbra.Api.Helpers;
using Penumbra.Api.IpcSubscribers;
using Penumbra.UI;
namespace Penumbra.Api.IpcTester;
public class ModSettingsIpcTester : IUiService, IDisposable
{
private readonly IDalamudPluginInterface _pi;
public readonly EventSubscriber<ModSettingChange, Guid, string, bool> SettingChanged;
private PenumbraApiEc _lastSettingsError = PenumbraApiEc.Success;
private ModSettingChange _lastSettingChangeType;
private Guid _lastSettingChangeCollection = Guid.Empty;
private string _lastSettingChangeMod = string.Empty;
private bool _lastSettingChangeInherited;
private DateTimeOffset _lastSettingChange;
private string _settingsModDirectory = string.Empty;
private string _settingsModName = string.Empty;
private Guid? _settingsCollection;
private string _settingsCollectionName = string.Empty;
private bool _settingsIgnoreInheritance;
private bool _settingsIgnoreTemporary;
private int _settingsKey;
private bool _settingsInherit;
private bool _settingsTemporary;
private bool _settingsEnabled;
private int _settingsPriority;
private IReadOnlyDictionary<string, (string[], GroupType)>? _availableSettings;
private Dictionary<string, List<string>>? _currentSettings;
private Dictionary<string, (bool, int, Dictionary<string, List<string>>, bool, bool)>? _allSettings;
public ModSettingsIpcTester(IDalamudPluginInterface pi)
{
_pi = pi;
SettingChanged = ModSettingChanged.Subscriber(pi, UpdateLastModSetting);
SettingChanged.Disable();
}
public void Dispose()
{
SettingChanged.Dispose();
}
public void Draw()
{
using var _ = ImRaii.TreeNode("Mod Settings");
if (!_)
return;
ImGui.InputTextWithHint("##settingsDir", "Mod Directory Name...", ref _settingsModDirectory, 100);
ImGui.InputTextWithHint("##settingsName", "Mod Name...", ref _settingsModName, 100);
ImGuiUtil.GuidInput("##settingsCollection", "Collection...", string.Empty, ref _settingsCollection, ref _settingsCollectionName);
ImUtf8.Checkbox("Ignore Inheritance"u8, ref _settingsIgnoreInheritance);
ImUtf8.Checkbox("Ignore Temporary"u8, ref _settingsIgnoreTemporary);
ImUtf8.InputScalar("Key"u8, ref _settingsKey);
var collection = _settingsCollection.GetValueOrDefault(Guid.Empty);
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
if (!table)
return;
IpcTester.DrawIntro("Last Error", _lastSettingsError.ToString());
IpcTester.DrawIntro(ModSettingChanged.Label, "Last Mod Setting Changed");
ImGui.TextUnformatted(_lastSettingChangeMod.Length > 0
? $"{_lastSettingChangeType} of {_lastSettingChangeMod} in {_lastSettingChangeCollection}{(_lastSettingChangeInherited ? " (Inherited)" : string.Empty)} at {_lastSettingChange}"
: "None");
IpcTester.DrawIntro(GetAvailableModSettings.Label, "Get Available Settings");
if (ImGui.Button("Get##Available"))
{
_availableSettings = new GetAvailableModSettings(_pi).Invoke(_settingsModDirectory, _settingsModName);
_lastSettingsError = _availableSettings == null ? PenumbraApiEc.ModMissing : PenumbraApiEc.Success;
}
IpcTester.DrawIntro(GetCurrentModSettings.Label, "Get Current Settings");
if (ImGui.Button("Get##Current"))
{
var ret = new GetCurrentModSettings(_pi)
.Invoke(collection, _settingsModDirectory, _settingsModName, _settingsIgnoreInheritance);
_lastSettingsError = ret.Item1;
if (ret.Item1 == PenumbraApiEc.Success)
{
_settingsEnabled = ret.Item2?.Item1 ?? false;
_settingsInherit = ret.Item2?.Item4 ?? true;
_settingsTemporary = false;
_settingsPriority = ret.Item2?.Item2 ?? 0;
_currentSettings = ret.Item2?.Item3;
}
else
{
_currentSettings = null;
}
}
IpcTester.DrawIntro(GetCurrentModSettingsWithTemp.Label, "Get Current Settings With Temp");
if (ImGui.Button("Get##CurrentTemp"))
{
var ret = new GetCurrentModSettingsWithTemp(_pi)
.Invoke(collection, _settingsModDirectory, _settingsModName, _settingsIgnoreInheritance, _settingsIgnoreTemporary, _settingsKey);
_lastSettingsError = ret.Item1;
if (ret.Item1 == PenumbraApiEc.Success)
{
_settingsEnabled = ret.Item2?.Item1 ?? false;
_settingsInherit = ret.Item2?.Item4 ?? true;
_settingsTemporary = ret.Item2?.Item5 ?? false;
_settingsPriority = ret.Item2?.Item2 ?? 0;
_currentSettings = ret.Item2?.Item3;
}
else
{
_currentSettings = null;
}
}
IpcTester.DrawIntro(GetAllModSettings.Label, "Get All Mod Settings");
if (ImGui.Button("Get##All"))
{
var ret = new GetAllModSettings(_pi).Invoke(collection, _settingsIgnoreInheritance, _settingsIgnoreTemporary, _settingsKey);
_lastSettingsError = ret.Item1;
_allSettings = ret.Item2;
}
if (_allSettings != null)
{
ImGui.SameLine();
ImUtf8.Text($"{_allSettings.Count} Mods");
}
IpcTester.DrawIntro(TryInheritMod.Label, "Inherit Mod");
ImGui.Checkbox("##inherit", ref _settingsInherit);
ImGui.SameLine();
if (ImGui.Button("Set##Inherit"))
_lastSettingsError = new TryInheritMod(_pi)
.Invoke(collection, _settingsModDirectory, _settingsInherit, _settingsModName);
IpcTester.DrawIntro(TrySetMod.Label, "Set Enabled");
ImGui.Checkbox("##enabled", ref _settingsEnabled);
ImGui.SameLine();
if (ImGui.Button("Set##Enabled"))
_lastSettingsError = new TrySetMod(_pi)
.Invoke(collection, _settingsModDirectory, _settingsEnabled, _settingsModName);
IpcTester.DrawIntro(TrySetModPriority.Label, "Set Priority");
ImGui.SetNextItemWidth(200 * UiHelpers.Scale);
ImGui.DragInt("##Priority", ref _settingsPriority);
ImGui.SameLine();
if (ImGui.Button("Set##Priority"))
_lastSettingsError = new TrySetModPriority(_pi)
.Invoke(collection, _settingsModDirectory, _settingsPriority, _settingsModName);
IpcTester.DrawIntro(CopyModSettings.Label, "Copy Mod Settings");
if (ImGui.Button("Copy Settings"))
_lastSettingsError = new CopyModSettings(_pi)
.Invoke(_settingsCollection, _settingsModDirectory, _settingsModName);
ImGuiUtil.HoverTooltip("Copy settings from Mod Directory Name to Mod Name (as directory) in collection.");
IpcTester.DrawIntro(TrySetModSetting.Label, "Set Setting(s)");
if (_availableSettings == null)
return;
foreach (var (group, (list, type)) in _availableSettings)
{
using var id = ImRaii.PushId(group);
var preview = list.Length > 0 ? list[0] : string.Empty;
if (_currentSettings != null && _currentSettings.TryGetValue(group, out var current) && current.Count > 0)
{
preview = current[0];
}
else
{
current = [];
if (_currentSettings != null)
_currentSettings[group] = current;
}
ImGui.SetNextItemWidth(200 * UiHelpers.Scale);
using (var c = ImRaii.Combo("##group", preview))
{
if (c)
foreach (var s in list)
{
var contained = current.Contains(s);
if (ImGui.Checkbox(s, ref contained))
{
if (contained)
current.Add(s);
else
current.Remove(s);
}
}
}
ImGui.SameLine();
if (ImGui.Button("Set##setting"))
_lastSettingsError = type == GroupType.Single
? new TrySetModSetting(_pi).Invoke(collection, _settingsModDirectory, group, current.Count > 0 ? current[0] : string.Empty,
_settingsModName)
: new TrySetModSettings(_pi).Invoke(collection, _settingsModDirectory, group, current.ToArray(), _settingsModName);
ImGui.SameLine();
ImGui.TextUnformatted(group);
}
}
private void UpdateLastModSetting(ModSettingChange type, Guid collection, string mod, bool inherited)
{
_lastSettingChangeType = type;
_lastSettingChangeCollection = collection;
_lastSettingChangeMod = mod;
_lastSettingChangeInherited = inherited;
_lastSettingChange = DateTimeOffset.Now;
}
}

View file

@ -0,0 +1,184 @@
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility;
using Dalamud.Plugin;
using OtterGui.Raii;
using OtterGui.Services;
using OtterGui.Text;
using Penumbra.Api.Enums;
using Penumbra.Api.Helpers;
using Penumbra.Api.IpcSubscribers;
namespace Penumbra.Api.IpcTester;
public class ModsIpcTester : IUiService, IDisposable
{
private readonly IDalamudPluginInterface _pi;
private string _modDirectory = string.Empty;
private string _modName = string.Empty;
private string _pathInput = string.Empty;
private string _newInstallPath = string.Empty;
private PenumbraApiEc _lastReloadEc;
private PenumbraApiEc _lastAddEc;
private PenumbraApiEc _lastDeleteEc;
private PenumbraApiEc _lastSetPathEc;
private PenumbraApiEc _lastInstallEc;
private Dictionary<string, string> _mods = [];
private Dictionary<string, object?> _changedItems = [];
public readonly EventSubscriber<string> DeleteSubscriber;
public readonly EventSubscriber<string> AddSubscriber;
public readonly EventSubscriber<string, string> MoveSubscriber;
private DateTimeOffset _lastDeletedModTime = DateTimeOffset.UnixEpoch;
private string _lastDeletedMod = string.Empty;
private DateTimeOffset _lastAddedModTime = DateTimeOffset.UnixEpoch;
private string _lastAddedMod = string.Empty;
private DateTimeOffset _lastMovedModTime = DateTimeOffset.UnixEpoch;
private string _lastMovedModFrom = string.Empty;
private string _lastMovedModTo = string.Empty;
public ModsIpcTester(IDalamudPluginInterface pi)
{
_pi = pi;
DeleteSubscriber = ModDeleted.Subscriber(pi, s =>
{
_lastDeletedModTime = DateTimeOffset.UtcNow;
_lastDeletedMod = s;
});
AddSubscriber = ModAdded.Subscriber(pi, s =>
{
_lastAddedModTime = DateTimeOffset.UtcNow;
_lastAddedMod = s;
});
MoveSubscriber = ModMoved.Subscriber(pi, (s1, s2) =>
{
_lastMovedModTime = DateTimeOffset.UtcNow;
_lastMovedModFrom = s1;
_lastMovedModTo = s2;
});
DeleteSubscriber.Disable();
AddSubscriber.Disable();
MoveSubscriber.Disable();
}
public void Dispose()
{
DeleteSubscriber.Dispose();
DeleteSubscriber.Disable();
AddSubscriber.Dispose();
AddSubscriber.Disable();
MoveSubscriber.Dispose();
MoveSubscriber.Disable();
}
public void Draw()
{
using var _ = ImRaii.TreeNode("Mods");
if (!_)
return;
ImGui.InputTextWithHint("##install", "Install File Path...", ref _newInstallPath, 100);
ImGui.InputTextWithHint("##modDir", "Mod Directory Name...", ref _modDirectory, 100);
ImGui.InputTextWithHint("##modName", "Mod Name...", ref _modName, 100);
ImGui.InputTextWithHint("##path", "New Path...", ref _pathInput, 100);
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
if (!table)
return;
IpcTester.DrawIntro(GetModList.Label, "Mods");
DrawModsPopup();
if (ImGui.Button("Get##Mods"))
{
_mods = new GetModList(_pi).Invoke();
ImGui.OpenPopup("Mods");
}
IpcTester.DrawIntro(ReloadMod.Label, "Reload Mod");
if (ImGui.Button("Reload"))
_lastReloadEc = new ReloadMod(_pi).Invoke(_modDirectory, _modName);
ImGui.SameLine();
ImGui.TextUnformatted(_lastReloadEc.ToString());
IpcTester.DrawIntro(InstallMod.Label, "Install Mod");
if (ImGui.Button("Install"))
_lastInstallEc = new InstallMod(_pi).Invoke(_newInstallPath);
ImGui.SameLine();
ImGui.TextUnformatted(_lastInstallEc.ToString());
IpcTester.DrawIntro(AddMod.Label, "Add Mod");
if (ImGui.Button("Add"))
_lastAddEc = new AddMod(_pi).Invoke(_modDirectory);
ImGui.SameLine();
ImGui.TextUnformatted(_lastAddEc.ToString());
IpcTester.DrawIntro(DeleteMod.Label, "Delete Mod");
if (ImGui.Button("Delete"))
_lastDeleteEc = new DeleteMod(_pi).Invoke(_modDirectory, _modName);
ImGui.SameLine();
ImGui.TextUnformatted(_lastDeleteEc.ToString());
IpcTester.DrawIntro(GetChangedItems.Label, "Get Changed Items");
DrawChangedItemsPopup();
if (ImUtf8.Button("Get##ChangedItems"u8))
{
_changedItems = new GetChangedItems(_pi).Invoke(_modDirectory, _modName);
ImUtf8.OpenPopup("ChangedItems"u8);
}
IpcTester.DrawIntro(GetModPath.Label, "Current Path");
var (ec, path, def, nameDef) = new GetModPath(_pi).Invoke(_modDirectory, _modName);
ImGui.TextUnformatted($"{path} ({(def ? "Custom" : "Default")} Path, {(nameDef ? "Custom" : "Default")} Name) [{ec}]");
IpcTester.DrawIntro(SetModPath.Label, "Set Path");
if (ImGui.Button("Set"))
_lastSetPathEc = new SetModPath(_pi).Invoke(_modDirectory, _pathInput, _modName);
ImGui.SameLine();
ImGui.TextUnformatted(_lastSetPathEc.ToString());
IpcTester.DrawIntro(ModDeleted.Label, "Last Mod Deleted");
if (_lastDeletedModTime > DateTimeOffset.UnixEpoch)
ImGui.TextUnformatted($"{_lastDeletedMod} at {_lastDeletedModTime}");
IpcTester.DrawIntro(ModAdded.Label, "Last Mod Added");
if (_lastAddedModTime > DateTimeOffset.UnixEpoch)
ImGui.TextUnformatted($"{_lastAddedMod} at {_lastAddedModTime}");
IpcTester.DrawIntro(ModMoved.Label, "Last Mod Moved");
if (_lastMovedModTime > DateTimeOffset.UnixEpoch)
ImGui.TextUnformatted($"{_lastMovedModFrom} -> {_lastMovedModTo} at {_lastMovedModTime}");
}
private void DrawModsPopup()
{
ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
using var p = ImRaii.Popup("Mods");
if (!p)
return;
foreach (var (modDir, modName) in _mods)
ImGui.TextUnformatted($"{modDir}: {modName}");
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
ImGui.CloseCurrentPopup();
}
private void DrawChangedItemsPopup()
{
ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
using var p = ImUtf8.Popup("ChangedItems"u8);
if (!p)
return;
foreach (var (name, data) in _changedItems)
ImUtf8.Text($"{name}: {data}");
if (ImUtf8.Button("Close"u8, -Vector2.UnitX) || !ImGui.IsWindowFocused())
ImGui.CloseCurrentPopup();
}
}

View file

@ -0,0 +1,147 @@
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Dalamud.Plugin;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Services;
using OtterGui.Text;
using Penumbra.Api.Helpers;
using Penumbra.Api.IpcSubscribers;
namespace Penumbra.Api.IpcTester;
public class PluginStateIpcTester : IUiService, IDisposable
{
private readonly IDalamudPluginInterface _pi;
public readonly EventSubscriber<string, bool> ModDirectoryChanged;
public readonly EventSubscriber Initialized;
public readonly EventSubscriber Disposed;
public readonly EventSubscriber<bool> EnabledChange;
private string _currentConfiguration = string.Empty;
private string _lastModDirectory = string.Empty;
private bool _lastModDirectoryValid;
private DateTimeOffset _lastModDirectoryTime = DateTimeOffset.MinValue;
private readonly List<DateTimeOffset> _initializedList = [];
private readonly List<DateTimeOffset> _disposedList = [];
private string _requiredFeatureString = string.Empty;
private string[] _requiredFeatures = [];
private DateTimeOffset _lastEnabledChange = DateTimeOffset.UnixEpoch;
private bool? _lastEnabledValue;
public PluginStateIpcTester(IDalamudPluginInterface pi)
{
_pi = pi;
ModDirectoryChanged = IpcSubscribers.ModDirectoryChanged.Subscriber(pi, UpdateModDirectoryChanged);
Initialized = IpcSubscribers.Initialized.Subscriber(pi, AddInitialized);
Disposed = IpcSubscribers.Disposed.Subscriber(pi, AddDisposed);
EnabledChange = IpcSubscribers.EnabledChange.Subscriber(pi, SetLastEnabled);
ModDirectoryChanged.Disable();
EnabledChange.Disable();
}
public void Dispose()
{
ModDirectoryChanged.Dispose();
Initialized.Dispose();
Disposed.Dispose();
EnabledChange.Dispose();
}
public void Draw()
{
using var _ = ImRaii.TreeNode("Plugin State");
if (!_)
return;
if (ImUtf8.InputText("Required Features"u8, ref _requiredFeatureString))
_requiredFeatures = _requiredFeatureString.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
if (!table)
return;
DrawList(IpcSubscribers.Initialized.Label, "Last Initialized", _initializedList);
DrawList(IpcSubscribers.Disposed.Label, "Last Disposed", _disposedList);
IpcTester.DrawIntro(ApiVersion.Label, "Current Version");
var (breaking, features) = new ApiVersion(_pi).Invoke();
ImGui.TextUnformatted($"{breaking}.{features:D4}");
IpcTester.DrawIntro(GetEnabledState.Label, "Current State");
ImGui.TextUnformatted($"{new GetEnabledState(_pi).Invoke()}");
IpcTester.DrawIntro(IpcSubscribers.EnabledChange.Label, "Last Change");
ImGui.TextUnformatted(_lastEnabledValue is { } v ? $"{_lastEnabledChange} (to {v})" : "Never");
IpcTester.DrawIntro(SupportedFeatures.Label, "Supported Features");
ImUtf8.Text(string.Join(", ", new SupportedFeatures(_pi).Invoke()));
IpcTester.DrawIntro(CheckSupportedFeatures.Label, "Missing Features");
ImUtf8.Text(string.Join(", ", new CheckSupportedFeatures(_pi).Invoke(_requiredFeatures)));
DrawConfigPopup();
IpcTester.DrawIntro(GetConfiguration.Label, "Configuration");
if (ImGui.Button("Get"))
{
_currentConfiguration = new GetConfiguration(_pi).Invoke();
ImGui.OpenPopup("Config Popup");
}
IpcTester.DrawIntro(GetModDirectory.Label, "Current Mod Directory");
ImGui.TextUnformatted(new GetModDirectory(_pi).Invoke());
IpcTester.DrawIntro(IpcSubscribers.ModDirectoryChanged.Label, "Last Mod Directory Change");
ImGui.TextUnformatted(_lastModDirectoryTime > DateTimeOffset.MinValue
? $"{_lastModDirectory} ({(_lastModDirectoryValid ? "Valid" : "Invalid")}) at {_lastModDirectoryTime}"
: "None");
void DrawList(string label, string text, List<DateTimeOffset> list)
{
IpcTester.DrawIntro(label, text);
if (list.Count == 0)
{
ImGui.TextUnformatted("Never");
}
else
{
ImGui.TextUnformatted(list[^1].LocalDateTime.ToString(CultureInfo.CurrentCulture));
if (list.Count > 1 && ImGui.IsItemHovered())
ImGui.SetTooltip(string.Join("\n",
list.SkipLast(1).Select(t => t.LocalDateTime.ToString(CultureInfo.CurrentCulture))));
}
}
}
private void DrawConfigPopup()
{
ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
using var popup = ImRaii.Popup("Config Popup");
if (!popup)
return;
using (ImRaii.PushFont(UiBuilder.MonoFont))
{
ImGuiUtil.TextWrapped(_currentConfiguration);
}
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
ImGui.CloseCurrentPopup();
}
private void UpdateModDirectoryChanged(string path, bool valid)
=> (_lastModDirectory, _lastModDirectoryValid, _lastModDirectoryTime) = (path, valid, DateTimeOffset.Now);
private void AddInitialized()
=> _initializedList.Add(DateTimeOffset.UtcNow);
private void AddDisposed()
=> _disposedList.Add(DateTimeOffset.UtcNow);
private void SetLastEnabled(bool val)
=> (_lastEnabledChange, _lastEnabledValue) = (DateTimeOffset.Now, val);
}

View file

@ -0,0 +1,73 @@
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Dalamud.Bindings.ImGui;
using OtterGui.Raii;
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Api.Helpers;
using Penumbra.Api.IpcSubscribers;
using Penumbra.GameData.Interop;
using Penumbra.UI;
namespace Penumbra.Api.IpcTester;
public class RedrawingIpcTester : IUiService, IDisposable
{
private readonly IDalamudPluginInterface _pi;
private readonly ObjectManager _objects;
public readonly EventSubscriber<nint, int> Redrawn;
private int _redrawIndex;
private string _lastRedrawnString = "None";
public RedrawingIpcTester(IDalamudPluginInterface pi, ObjectManager objects)
{
_pi = pi;
_objects = objects;
Redrawn = GameObjectRedrawn.Subscriber(_pi, SetLastRedrawn);
Redrawn.Disable();
}
public void Dispose()
{
Redrawn.Dispose();
}
public void Draw()
{
using var _ = ImRaii.TreeNode("Redrawing");
if (!_)
return;
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
if (!table)
return;
IpcTester.DrawIntro(RedrawObject.Label, "Redraw by Index");
var tmp = _redrawIndex;
ImGui.SetNextItemWidth(100 * UiHelpers.Scale);
if (ImGui.DragInt("##redrawIndex", ref tmp, 0.1f, 0, _objects.TotalCount))
_redrawIndex = Math.Clamp(tmp, 0, _objects.TotalCount);
ImGui.SameLine();
if (ImGui.Button("Redraw##Index"))
new RedrawObject(_pi).Invoke(_redrawIndex);
IpcTester.DrawIntro(RedrawAll.Label, "Redraw All");
if (ImGui.Button("Redraw##All"))
new RedrawAll(_pi).Invoke();
IpcTester.DrawIntro(GameObjectRedrawn.Label, "Last Redrawn Object:");
ImGui.TextUnformatted(_lastRedrawnString);
}
private void SetLastRedrawn(nint address, int index)
{
if (index < 0
|| index > _objects.TotalCount
|| address == nint.Zero
|| _objects[index].Address != address)
_lastRedrawnString = "Invalid";
_lastRedrawnString = $"{_objects[index].Utf8Name} (0x{address:X}, {index})";
}
}

View file

@ -0,0 +1,114 @@
using Dalamud.Plugin;
using Dalamud.Bindings.ImGui;
using OtterGui.Raii;
using OtterGui.Services;
using Penumbra.Api.IpcSubscribers;
using Penumbra.String.Classes;
namespace Penumbra.Api.IpcTester;
public class ResolveIpcTester(IDalamudPluginInterface pi) : IUiService
{
private string _currentResolvePath = string.Empty;
private string _currentReversePath = string.Empty;
private int _currentReverseIdx;
private Task<(string[], string[][])> _task = Task.FromResult<(string[], string[][])>(([], []));
public void Draw()
{
using var tree = ImRaii.TreeNode("Resolving");
if (!tree)
return;
ImGui.InputTextWithHint("##resolvePath", "Resolve this game path...", ref _currentResolvePath, Utf8GamePath.MaxGamePathLength);
ImGui.InputTextWithHint("##resolveInversePath", "Reverse-resolve this path...", ref _currentReversePath,
Utf8GamePath.MaxGamePathLength);
ImGui.InputInt("##resolveIdx", ref _currentReverseIdx, 0, 0);
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
if (!table)
return;
IpcTester.DrawIntro(ResolveDefaultPath.Label, "Default Collection Resolve");
if (_currentResolvePath.Length != 0)
ImGui.TextUnformatted(new ResolveDefaultPath(pi).Invoke(_currentResolvePath));
IpcTester.DrawIntro(ResolveInterfacePath.Label, "Interface Collection Resolve");
if (_currentResolvePath.Length != 0)
ImGui.TextUnformatted(new ResolveInterfacePath(pi).Invoke(_currentResolvePath));
IpcTester.DrawIntro(ResolvePlayerPath.Label, "Player Collection Resolve");
if (_currentResolvePath.Length != 0)
ImGui.TextUnformatted(new ResolvePlayerPath(pi).Invoke(_currentResolvePath));
IpcTester.DrawIntro(ResolveGameObjectPath.Label, "Game Object Collection Resolve");
if (_currentResolvePath.Length != 0)
ImGui.TextUnformatted(new ResolveGameObjectPath(pi).Invoke(_currentResolvePath, _currentReverseIdx));
IpcTester.DrawIntro(ReverseResolvePlayerPath.Label, "Reversed Game Paths (Player)");
if (_currentReversePath.Length > 0)
{
var list = new ReverseResolvePlayerPath(pi).Invoke(_currentReversePath);
if (list.Length > 0)
{
ImGui.TextUnformatted(list[0]);
if (list.Length > 1 && ImGui.IsItemHovered())
ImGui.SetTooltip(string.Join("\n", list.Skip(1)));
}
}
IpcTester.DrawIntro(ReverseResolveGameObjectPath.Label, "Reversed Game Paths (Game Object)");
if (_currentReversePath.Length > 0)
{
var list = new ReverseResolveGameObjectPath(pi).Invoke(_currentReversePath, _currentReverseIdx);
if (list.Length > 0)
{
ImGui.TextUnformatted(list[0]);
if (list.Length > 1 && ImGui.IsItemHovered())
ImGui.SetTooltip(string.Join("\n", list.Skip(1)));
}
}
var forwardArray = _currentResolvePath.Length > 0
? [_currentResolvePath]
: Array.Empty<string>();
var reverseArray = _currentReversePath.Length > 0
? [_currentReversePath]
: Array.Empty<string>();
IpcTester.DrawIntro(ResolvePlayerPaths.Label, "Resolved Paths (Player)");
if (forwardArray.Length > 0 || reverseArray.Length > 0)
{
var ret = new ResolvePlayerPaths(pi).Invoke(forwardArray, reverseArray);
ImGui.TextUnformatted(ConvertText(ret));
}
IpcTester.DrawIntro(ResolvePlayerPathsAsync.Label, "Resolved Paths Async (Player)");
if (ImGui.Button("Start"))
_task = new ResolvePlayerPathsAsync(pi).Invoke(forwardArray, reverseArray);
var hovered = ImGui.IsItemHovered();
ImGui.SameLine();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(_task.Status.ToString());
if ((hovered || ImGui.IsItemHovered()) && _task.IsCompletedSuccessfully)
ImGui.SetTooltip(ConvertText(_task.Result));
return;
static string ConvertText((string[], string[][]) data)
{
var text = string.Empty;
if (data.Item1.Length > 0)
{
if (data.Item2.Length > 0)
text = $"Forward: {data.Item1[0]} | Reverse: {string.Join("; ", data.Item2[0])}.";
else
text = $"Forward: {data.Item1[0]}.";
}
else if (data.Item2.Length > 0)
{
text = $"Reverse: {string.Join("; ", data.Item2[0])}.";
}
return text;
}
}
}

View file

@ -0,0 +1,350 @@
using Dalamud.Bindings.ImGui;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Dalamud.Plugin;
using OtterGui;
using OtterGui.Extensions;
using OtterGui.Raii;
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Api.Helpers;
using Penumbra.Api.IpcSubscribers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop;
using Penumbra.GameData.Structs;
namespace Penumbra.Api.IpcTester;
public class ResourceTreeIpcTester(IDalamudPluginInterface pi, ObjectManager objects) : IUiService
{
private readonly Stopwatch _stopwatch = new();
private string _gameObjectIndices = "0";
private ResourceType _type = ResourceType.Mtrl;
private bool _withUiData;
private (string, Dictionary<string, HashSet<string>>?)[]? _lastGameObjectResourcePaths;
private (string, Dictionary<string, HashSet<string>>?)[]? _lastPlayerResourcePaths;
private (string, IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?)[]? _lastGameObjectResourcesOfType;
private (string, IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?)[]? _lastPlayerResourcesOfType;
private (string, ResourceTreeDto?)[]? _lastGameObjectResourceTrees;
private (string, ResourceTreeDto)[]? _lastPlayerResourceTrees;
private TimeSpan _lastCallDuration;
public void Draw()
{
using var _ = ImRaii.TreeNode("Resource Tree");
if (!_)
return;
ImGui.InputText("GameObject indices", ref _gameObjectIndices, 511);
ImGuiUtil.GenericEnumCombo("Resource type", ImGui.CalcItemWidth(), _type, out _type, Enum.GetValues<ResourceType>());
ImGui.Checkbox("Also get names and icons", ref _withUiData);
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
if (!table)
return;
IpcTester.DrawIntro(GetGameObjectResourcePaths.Label, "Get GameObject resource paths");
if (ImGui.Button("Get##GameObjectResourcePaths"))
{
var gameObjects = GetSelectedGameObjects();
var subscriber = new GetGameObjectResourcePaths(pi);
_stopwatch.Restart();
var resourcePaths = subscriber.Invoke(gameObjects);
_lastCallDuration = _stopwatch.Elapsed;
_lastGameObjectResourcePaths = gameObjects
.Select(i => GameObjectToString(i))
.Zip(resourcePaths)
.ToArray();
ImGui.OpenPopup(nameof(GetGameObjectResourcePaths));
}
IpcTester.DrawIntro(GetPlayerResourcePaths.Label, "Get local player resource paths");
if (ImGui.Button("Get##PlayerResourcePaths"))
{
var subscriber = new GetPlayerResourcePaths(pi);
_stopwatch.Restart();
var resourcePaths = subscriber.Invoke();
_lastCallDuration = _stopwatch.Elapsed;
_lastPlayerResourcePaths = resourcePaths
.Select(pair => (GameObjectToString(pair.Key), pair.Value))
.ToArray()!;
ImGui.OpenPopup(nameof(GetPlayerResourcePaths));
}
IpcTester.DrawIntro(GetGameObjectResourcesOfType.Label, "Get GameObject resources of type");
if (ImGui.Button("Get##GameObjectResourcesOfType"))
{
var gameObjects = GetSelectedGameObjects();
var subscriber = new GetGameObjectResourcesOfType(pi);
_stopwatch.Restart();
var resourcesOfType = subscriber.Invoke(_type, _withUiData, gameObjects);
_lastCallDuration = _stopwatch.Elapsed;
_lastGameObjectResourcesOfType = gameObjects
.Select(i => GameObjectToString(i))
.Zip(resourcesOfType)
.ToArray();
ImGui.OpenPopup(nameof(GetGameObjectResourcesOfType));
}
IpcTester.DrawIntro(GetPlayerResourcesOfType.Label, "Get local player resources of type");
if (ImGui.Button("Get##PlayerResourcesOfType"))
{
var subscriber = new GetPlayerResourcesOfType(pi);
_stopwatch.Restart();
var resourcesOfType = subscriber.Invoke(_type, _withUiData);
_lastCallDuration = _stopwatch.Elapsed;
_lastPlayerResourcesOfType = resourcesOfType
.Select(pair => (GameObjectToString(pair.Key), (IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?)pair.Value))
.ToArray();
ImGui.OpenPopup(nameof(GetPlayerResourcesOfType));
}
IpcTester.DrawIntro(GetGameObjectResourceTrees.Label, "Get GameObject resource trees");
if (ImGui.Button("Get##GameObjectResourceTrees"))
{
var gameObjects = GetSelectedGameObjects();
var subscriber = new GetGameObjectResourceTrees(pi);
_stopwatch.Restart();
var trees = subscriber.Invoke(_withUiData, gameObjects);
_lastCallDuration = _stopwatch.Elapsed;
_lastGameObjectResourceTrees = gameObjects
.Select(i => GameObjectToString(i))
.Zip(trees)
.ToArray();
ImGui.OpenPopup(nameof(GetGameObjectResourceTrees));
}
IpcTester.DrawIntro(GetPlayerResourceTrees.Label, "Get local player resource trees");
if (ImGui.Button("Get##PlayerResourceTrees"))
{
var subscriber = new GetPlayerResourceTrees(pi);
_stopwatch.Restart();
var trees = subscriber.Invoke(_withUiData);
_lastCallDuration = _stopwatch.Elapsed;
_lastPlayerResourceTrees = trees
.Select(pair => (GameObjectToString(pair.Key), pair.Value))
.ToArray();
ImGui.OpenPopup(nameof(GetPlayerResourceTrees));
}
DrawPopup(nameof(GetGameObjectResourcePaths), ref _lastGameObjectResourcePaths, DrawResourcePaths,
_lastCallDuration);
DrawPopup(nameof(GetPlayerResourcePaths), ref _lastPlayerResourcePaths!, DrawResourcePaths, _lastCallDuration);
DrawPopup(nameof(GetGameObjectResourcesOfType), ref _lastGameObjectResourcesOfType, DrawResourcesOfType,
_lastCallDuration);
DrawPopup(nameof(GetPlayerResourcesOfType), ref _lastPlayerResourcesOfType, DrawResourcesOfType,
_lastCallDuration);
DrawPopup(nameof(GetGameObjectResourceTrees), ref _lastGameObjectResourceTrees, DrawResourceTrees,
_lastCallDuration);
DrawPopup(nameof(GetPlayerResourceTrees), ref _lastPlayerResourceTrees, DrawResourceTrees!, _lastCallDuration);
}
private static void DrawPopup<T>(string popupId, ref T? result, Action<T> drawResult, TimeSpan duration) where T : class
{
ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(1000, 500));
using var popup = ImRaii.Popup(popupId);
if (!popup)
{
result = null;
return;
}
if (result == null)
{
ImGui.CloseCurrentPopup();
return;
}
drawResult(result);
ImGui.TextUnformatted($"Invoked in {duration.TotalMilliseconds} ms");
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
{
result = null;
ImGui.CloseCurrentPopup();
}
}
private static void DrawWithHeaders<T>((string, T?)[] result, Action<T> drawItem) where T : class
{
var firstSeen = new Dictionary<T, string>();
foreach (var (label, item) in result)
{
if (item == null)
{
ImRaii.TreeNode($"{label}: null", ImGuiTreeNodeFlags.Leaf).Dispose();
continue;
}
if (firstSeen.TryGetValue(item, out var firstLabel))
{
ImRaii.TreeNode($"{label}: same as {firstLabel}", ImGuiTreeNodeFlags.Leaf).Dispose();
continue;
}
firstSeen.Add(item, label);
using var header = ImRaii.TreeNode(label);
if (!header)
continue;
drawItem(item);
}
}
private static void DrawResourcePaths((string, Dictionary<string, HashSet<string>>?)[] result)
{
DrawWithHeaders(result, paths =>
{
using var table = ImRaii.Table(string.Empty, 2, ImGuiTableFlags.SizingFixedFit);
if (!table)
return;
ImGui.TableSetupColumn("Actual Path", ImGuiTableColumnFlags.WidthStretch, 0.6f);
ImGui.TableSetupColumn("Game Paths", ImGuiTableColumnFlags.WidthStretch, 0.4f);
ImGui.TableHeadersRow();
foreach (var (actualPath, gamePaths) in paths)
{
ImGui.TableNextColumn();
ImGui.TextUnformatted(actualPath);
ImGui.TableNextColumn();
foreach (var gamePath in gamePaths)
ImGui.TextUnformatted(gamePath);
}
});
}
private void DrawResourcesOfType((string, IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?)[] result)
{
DrawWithHeaders(result, resources =>
{
using var table = ImRaii.Table(string.Empty, _withUiData ? 3 : 2, ImGuiTableFlags.SizingFixedFit);
if (!table)
return;
ImGui.TableSetupColumn("Resource Handle", ImGuiTableColumnFlags.WidthStretch, 0.15f);
ImGui.TableSetupColumn("Actual Path", ImGuiTableColumnFlags.WidthStretch, _withUiData ? 0.55f : 0.85f);
if (_withUiData)
ImGui.TableSetupColumn("Icon & Name", ImGuiTableColumnFlags.WidthStretch, 0.3f);
ImGui.TableHeadersRow();
foreach (var (resourceHandle, (actualPath, name, icon)) in resources)
{
ImGui.TableNextColumn();
TextUnformattedMono($"0x{resourceHandle:X}");
ImGui.TableNextColumn();
ImGui.TextUnformatted(actualPath);
if (_withUiData)
{
ImGui.TableNextColumn();
TextUnformattedMono(icon.ToString());
ImGui.SameLine();
ImGui.TextUnformatted(name);
}
}
});
}
private void DrawResourceTrees((string, ResourceTreeDto?)[] result)
{
DrawWithHeaders(result, tree =>
{
ImGui.TextUnformatted($"Name: {tree.Name}\nRaceCode: {(GenderRace)tree.RaceCode}");
using var table = ImRaii.Table(string.Empty, _withUiData ? 7 : 5, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Resizable);
if (!table)
return;
if (_withUiData)
{
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch, 0.5f);
ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthStretch, 0.1f);
ImGui.TableSetupColumn("Icon", ImGuiTableColumnFlags.WidthStretch, 0.15f);
}
else
{
ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthStretch, 0.5f);
}
ImGui.TableSetupColumn("Game Path", ImGuiTableColumnFlags.WidthStretch, 0.5f);
ImGui.TableSetupColumn("Actual Path", ImGuiTableColumnFlags.WidthStretch, 0.5f);
ImGui.TableSetupColumn("Object Address", ImGuiTableColumnFlags.WidthStretch, 0.2f);
ImGui.TableSetupColumn("Resource Handle", ImGuiTableColumnFlags.WidthStretch, 0.2f);
ImGui.TableHeadersRow();
void DrawNode(ResourceNodeDto node)
{
ImGui.TableNextRow();
ImGui.TableNextColumn();
var hasChildren = node.Children.Any();
using var treeNode = ImRaii.TreeNode(
$"{(_withUiData ? node.Name ?? "Unknown" : node.Type)}##{node.ObjectAddress:X8}",
hasChildren
? ImGuiTreeNodeFlags.SpanFullWidth
: ImGuiTreeNodeFlags.SpanFullWidth | ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.NoTreePushOnOpen);
if (_withUiData)
{
ImGui.TableNextColumn();
TextUnformattedMono(node.Type.ToString());
ImGui.TableNextColumn();
TextUnformattedMono(node.Icon.ToString());
}
ImGui.TableNextColumn();
ImGui.TextUnformatted(node.GamePath ?? "Unknown");
ImGui.TableNextColumn();
ImGui.TextUnformatted(node.ActualPath);
ImGui.TableNextColumn();
TextUnformattedMono($"0x{node.ObjectAddress:X8}");
ImGui.TableNextColumn();
TextUnformattedMono($"0x{node.ResourceHandle:X8}");
if (treeNode)
foreach (var child in node.Children)
DrawNode(child);
}
foreach (var node in tree.Nodes)
DrawNode(node);
});
}
private static void TextUnformattedMono(string text)
{
using var _ = ImRaii.PushFont(UiBuilder.MonoFont);
ImGui.TextUnformatted(text);
}
private ushort[] GetSelectedGameObjects()
=> _gameObjectIndices.Split(',')
.SelectWhere(index => (ushort.TryParse(index.Trim(), out var i), i))
.ToArray();
private unsafe string GameObjectToString(ObjectIndex gameObjectIndex)
{
var gameObject = objects[gameObjectIndex];
return gameObject.Valid
? $"[{gameObjectIndex}] {gameObject.Utf8Name} ({(ObjectKind)gameObject.AsObject->ObjectKind})"
: $"[{gameObjectIndex}] null";
}
}

View file

@ -0,0 +1,319 @@
using Dalamud.Interface;
using Dalamud.Plugin;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Extensions;
using OtterGui.Raii;
using OtterGui.Services;
using OtterGui.Text;
using Penumbra.Api.Api;
using Penumbra.Api.Enums;
using Penumbra.Api.IpcSubscribers;
using Penumbra.Collections.Manager;
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Services;
namespace Penumbra.Api.IpcTester;
public class TemporaryIpcTester(
IDalamudPluginInterface pi,
ModManager modManager,
CollectionManager collections,
TempModManager tempMods,
TempCollectionManager tempCollections,
SaveService saveService,
Configuration config)
: IUiService
{
public Guid LastCreatedCollectionId = Guid.Empty;
private readonly bool _debug = Assembly.GetAssembly(typeof(TemporaryIpcTester))?.GetName().Version?.Major >= 9;
private Guid? _tempGuid;
private string _tempCollectionName = string.Empty;
private string _tempCollectionGuidName = string.Empty;
private string _tempModName = string.Empty;
private string _modDirectory = string.Empty;
private string _tempGamePath = "test/game/path.mtrl";
private string _tempFilePath = "test/success.mtrl";
private string _tempManipulation = string.Empty;
private string _identity = string.Empty;
private PenumbraApiEc _lastTempError;
private int _tempActorIndex;
private bool _forceOverwrite;
public void Draw()
{
using var _ = ImRaii.TreeNode("Temporary");
if (!_)
return;
ImGui.InputTextWithHint("##identity", "Identity...", ref _identity, 128);
ImGui.InputTextWithHint("##tempCollection", "Collection Name...", ref _tempCollectionName, 128);
ImGuiUtil.GuidInput("##guid", "Collection GUID...", string.Empty, ref _tempGuid, ref _tempCollectionGuidName);
ImGui.InputInt("##tempActorIndex", ref _tempActorIndex, 0, 0);
ImGui.InputTextWithHint("##tempMod", "Temporary Mod Name...", ref _tempModName, 32);
ImGui.InputTextWithHint("##mod", "Existing Mod Name...", ref _modDirectory, 256);
ImGui.InputTextWithHint("##tempGame", "Game Path...", ref _tempGamePath, 256);
ImGui.InputTextWithHint("##tempFile", "File Path...", ref _tempFilePath, 256);
ImUtf8.InputText("##tempManip"u8, ref _tempManipulation, "Manipulation Base64 String..."u8);
ImGui.Checkbox("Force Character Collection Overwrite", ref _forceOverwrite);
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
if (!table)
return;
IpcTester.DrawIntro("Last Error", _lastTempError.ToString());
ImGuiUtil.DrawTableColumn("Last Created Collection");
ImGui.TableNextColumn();
using (ImRaii.PushFont(UiBuilder.MonoFont))
{
ImGuiUtil.CopyOnClickSelectable(LastCreatedCollectionId.ToString());
}
IpcTester.DrawIntro(CreateTemporaryCollection.Label, "Create Temporary Collection");
if (ImGui.Button("Create##Collection"))
{
_lastTempError = new CreateTemporaryCollection(pi).Invoke(_identity, _tempCollectionName, out LastCreatedCollectionId);
if (_tempGuid == null)
{
_tempGuid = LastCreatedCollectionId;
_tempCollectionGuidName = LastCreatedCollectionId.ToString();
}
}
var guid = _tempGuid.GetValueOrDefault(Guid.Empty);
IpcTester.DrawIntro(DeleteTemporaryCollection.Label, "Delete Temporary Collection");
if (ImGui.Button("Delete##Collection"))
_lastTempError = new DeleteTemporaryCollection(pi).Invoke(guid);
ImGui.SameLine();
if (ImGui.Button("Delete Last##Collection"))
_lastTempError = new DeleteTemporaryCollection(pi).Invoke(LastCreatedCollectionId);
IpcTester.DrawIntro(AssignTemporaryCollection.Label, "Assign Temporary Collection");
if (ImGui.Button("Assign##NamedCollection"))
_lastTempError = new AssignTemporaryCollection(pi).Invoke(guid, _tempActorIndex, _forceOverwrite);
IpcTester.DrawIntro(AddTemporaryMod.Label, "Add Temporary Mod to specific Collection");
if (ImGui.Button("Add##Mod"))
_lastTempError = new AddTemporaryMod(pi).Invoke(_tempModName, guid,
new Dictionary<string, string> { { _tempGamePath, _tempFilePath } },
_tempManipulation.Length > 0 ? _tempManipulation : string.Empty, int.MaxValue);
IpcTester.DrawIntro(CreateTemporaryCollection.Label, "Copy Existing Collection");
if (ImGuiUtil.DrawDisabledButton("Copy##Collection", Vector2.Zero,
"Copies the effective list from the collection named in Temporary Mod Name...",
!collections.Storage.ByName(_tempModName, out var copyCollection))
&& copyCollection is { HasCache: true })
{
var files = copyCollection.ResolvedFiles.ToDictionary(kvp => kvp.Key.ToString(), kvp => kvp.Value.Path.ToString());
var manips = MetaApi.CompressMetaManipulations(copyCollection);
_lastTempError = new AddTemporaryMod(pi).Invoke(_tempModName, guid, files, manips, 999);
}
IpcTester.DrawIntro(AddTemporaryModAll.Label, "Add Temporary Mod to all Collections");
if (ImGui.Button("Add##All"))
_lastTempError = new AddTemporaryModAll(pi).Invoke(_tempModName,
new Dictionary<string, string> { { _tempGamePath, _tempFilePath } },
_tempManipulation.Length > 0 ? _tempManipulation : string.Empty, int.MaxValue);
IpcTester.DrawIntro(RemoveTemporaryMod.Label, "Remove Temporary Mod from specific Collection");
if (ImGui.Button("Remove##Mod"))
_lastTempError = new RemoveTemporaryMod(pi).Invoke(_tempModName, guid, int.MaxValue);
IpcTester.DrawIntro(RemoveTemporaryModAll.Label, "Remove Temporary Mod from all Collections");
if (ImGui.Button("Remove##ModAll"))
_lastTempError = new RemoveTemporaryModAll(pi).Invoke(_tempModName, int.MaxValue);
IpcTester.DrawIntro(SetTemporaryModSettings.Label, "Set Temporary Mod Settings (to default) in specific Collection");
if (ImUtf8.Button("Set##SetTemporary"u8))
_lastTempError = new SetTemporaryModSettings(pi).Invoke(guid, _modDirectory, false, true, 1337,
new Dictionary<string, IReadOnlyList<string>>(),
"IPC Tester", 1337);
IpcTester.DrawIntro(SetTemporaryModSettingsPlayer.Label, "Set Temporary Mod Settings (to default) in game object collection");
if (ImUtf8.Button("Set##SetTemporaryPlayer"u8))
_lastTempError = new SetTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, _modDirectory, false, true, 1337,
new Dictionary<string, IReadOnlyList<string>>(),
"IPC Tester", 1337);
IpcTester.DrawIntro(RemoveTemporaryModSettings.Label, "Remove Temporary Mod Settings from specific Collection");
if (ImUtf8.Button("Remove##RemoveTemporary"u8))
_lastTempError = new RemoveTemporaryModSettings(pi).Invoke(guid, _modDirectory, 1337);
ImGui.SameLine();
if (ImUtf8.Button("Remove (Wrong Key)##RemoveTemporary"u8))
_lastTempError = new RemoveTemporaryModSettings(pi).Invoke(guid, _modDirectory, 1338);
IpcTester.DrawIntro(RemoveTemporaryModSettingsPlayer.Label, "Remove Temporary Mod Settings from game object Collection");
if (ImUtf8.Button("Remove##RemoveTemporaryPlayer"u8))
_lastTempError = new RemoveTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, _modDirectory, 1337);
ImGui.SameLine();
if (ImUtf8.Button("Remove (Wrong Key)##RemoveTemporaryPlayer"u8))
_lastTempError = new RemoveTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, _modDirectory, 1338);
IpcTester.DrawIntro(RemoveAllTemporaryModSettings.Label, "Remove All Temporary Mod Settings from specific Collection");
if (ImUtf8.Button("Remove##RemoveAllTemporary"u8))
_lastTempError = new RemoveAllTemporaryModSettings(pi).Invoke(guid, 1337);
ImGui.SameLine();
if (ImUtf8.Button("Remove (Wrong Key)##RemoveAllTemporary"u8))
_lastTempError = new RemoveAllTemporaryModSettings(pi).Invoke(guid, 1338);
IpcTester.DrawIntro(RemoveAllTemporaryModSettingsPlayer.Label, "Remove All Temporary Mod Settings from game object Collection");
if (ImUtf8.Button("Remove##RemoveAllTemporaryPlayer"u8))
_lastTempError = new RemoveAllTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, 1337);
ImGui.SameLine();
if (ImUtf8.Button("Remove (Wrong Key)##RemoveAllTemporaryPlayer"u8))
_lastTempError = new RemoveAllTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, 1338);
IpcTester.DrawIntro(QueryTemporaryModSettings.Label, "Query Temporary Mod Settings from specific Collection");
ImUtf8.Button("Query##QueryTemporaryModSettings"u8);
if (ImGui.IsItemHovered())
{
_lastTempError = new QueryTemporaryModSettings(pi).Invoke(guid, _modDirectory, out var settings, out var source, 1337);
DrawTooltip(settings, source);
}
ImGui.SameLine();
ImUtf8.Button("Query (Wrong Key)##RemoveAllTemporary"u8);
if (ImGui.IsItemHovered())
{
_lastTempError = new QueryTemporaryModSettings(pi).Invoke(guid, _modDirectory, out var settings, out var source, 1338);
DrawTooltip(settings, source);
}
IpcTester.DrawIntro(QueryTemporaryModSettingsPlayer.Label, "Query Temporary Mod Settings from game object Collection");
ImUtf8.Button("Query##QueryTemporaryModSettingsPlayer"u8);
if (ImGui.IsItemHovered())
{
_lastTempError =
new QueryTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, _modDirectory, out var settings, out var source, 1337);
DrawTooltip(settings, source);
}
ImGui.SameLine();
ImUtf8.Button("Query (Wrong Key)##RemoveAllTemporaryPlayer"u8);
if (ImGui.IsItemHovered())
{
_lastTempError =
new QueryTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, _modDirectory, out var settings, out var source, 1338);
DrawTooltip(settings, source);
}
void DrawTooltip((bool ForceInherit, bool Enabled, int Priority, Dictionary<string, List<string>> Settings)? settings, string source)
{
using var tt = ImUtf8.Tooltip();
ImUtf8.Text($"Query returned {_lastTempError}");
if (settings != null)
ImUtf8.Text($"Settings created by {(source.Length == 0 ? "Unknown Source" : source)}:");
else
ImUtf8.Text(source.Length > 0 ? $"Locked by {source}." : "No settings exist.");
ImGui.Separator();
if (settings == null)
{
return;
}
using (ImUtf8.Group())
{
ImUtf8.Text("Force Inherit"u8);
ImUtf8.Text("Enabled"u8);
ImUtf8.Text("Priority"u8);
foreach (var group in settings.Value.Settings.Keys)
ImUtf8.Text(group);
}
ImGui.SameLine();
using (ImUtf8.Group())
{
ImUtf8.Text($"{settings.Value.ForceInherit}");
ImUtf8.Text($"{settings.Value.Enabled}");
ImUtf8.Text($"{settings.Value.Priority}");
foreach (var group in settings.Value.Settings.Values)
ImUtf8.Text(string.Join("; ", group));
}
}
}
public void DrawCollections()
{
using var collTree = ImUtf8.TreeNode("Temporary Collections##TempCollections"u8);
if (!collTree)
return;
using var table = ImUtf8.Table("##collTree"u8, 6, ImGuiTableFlags.SizingFixedFit);
if (!table)
return;
foreach (var (collection, idx) in tempCollections.Values.WithIndex())
{
using var id = ImRaii.PushId(idx);
ImGui.TableNextColumn();
var character = tempCollections.Collections.Where(p => p.Collection == collection).Select(p => p.DisplayName)
.FirstOrDefault()
?? "Unknown";
if (_debug && ImUtf8.Button("Save##Collection"u8))
TemporaryMod.SaveTempCollection(config, saveService, modManager, collection, character);
using (ImRaii.PushFont(UiBuilder.MonoFont))
{
ImGui.TableNextColumn();
ImGuiUtil.CopyOnClickSelectable(collection.Identity.Identifier);
}
ImGuiUtil.DrawTableColumn(collection.Identity.Name);
ImGuiUtil.DrawTableColumn(collection.ResolvedFiles.Count.ToString());
ImGuiUtil.DrawTableColumn(collection.MetaCache?.Count.ToString() ?? "0");
ImGuiUtil.DrawTableColumn(string.Join(", ",
tempCollections.Collections.Where(p => p.Collection == collection).Select(c => c.DisplayName)));
}
}
public void DrawMods()
{
using var modTree = ImRaii.TreeNode("Temporary Mods##TempMods");
if (!modTree)
return;
using var table = ImRaii.Table("##modTree", 5, ImGuiTableFlags.SizingFixedFit);
void PrintList(string collectionName, IReadOnlyList<TemporaryMod> list)
{
foreach (var mod in list)
{
ImGui.TableNextColumn();
ImGui.TextUnformatted(mod.Name.Text);
ImGui.TableNextColumn();
ImGui.TextUnformatted(mod.Priority.ToString());
ImGui.TableNextColumn();
ImGui.TextUnformatted(collectionName);
ImGui.TableNextColumn();
ImGui.TextUnformatted(mod.Default.Files.Count.ToString());
if (ImGui.IsItemHovered())
{
using var tt = ImRaii.Tooltip();
foreach (var (path, file) in mod.Default.Files)
ImGui.TextUnformatted($"{path} -> {file}");
}
ImGui.TableNextColumn();
ImGui.TextUnformatted(mod.TotalManipulations.ToString());
if (ImGui.IsItemHovered())
{
using var tt = ImRaii.Tooltip();
foreach (var identifier in mod.Default.Manipulations.Identifiers)
ImGui.TextUnformatted(identifier.ToString());
}
}
}
if (table)
{
PrintList("All", tempMods.ModsForAllCollections);
foreach (var (collection, list) in tempMods.Mods)
PrintList(collection.Identity.Name, list);
}
}
}

View file

@ -0,0 +1,133 @@
using Dalamud.Plugin;
using Dalamud.Bindings.ImGui;
using OtterGui.Raii;
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Api.Helpers;
using Penumbra.Api.IpcSubscribers;
namespace Penumbra.Api.IpcTester;
public class UiIpcTester : IUiService, IDisposable
{
private readonly IDalamudPluginInterface _pi;
public readonly EventSubscriber<string, float, float> PreSettingsTabBar;
public readonly EventSubscriber<string> PreSettingsPanel;
public readonly EventSubscriber<string> PostEnabled;
public readonly EventSubscriber<string> PostSettingsPanelDraw;
public readonly EventSubscriber<ChangedItemType, uint> ChangedItemTooltip;
public readonly EventSubscriber<MouseButton, ChangedItemType, uint> ChangedItemClicked;
private string _lastDrawnMod = string.Empty;
private DateTimeOffset _lastDrawnModTime = DateTimeOffset.MinValue;
private bool _subscribedToTooltip;
private bool _subscribedToClick;
private string _lastClicked = string.Empty;
private string _lastHovered = string.Empty;
private TabType _selectTab = TabType.None;
private string _modName = string.Empty;
private PenumbraApiEc _ec = PenumbraApiEc.Success;
public UiIpcTester(IDalamudPluginInterface pi)
{
_pi = pi;
PreSettingsTabBar = IpcSubscribers.PreSettingsTabBarDraw.Subscriber(pi, UpdateLastDrawnMod);
PreSettingsPanel = IpcSubscribers.PreSettingsDraw.Subscriber(pi, UpdateLastDrawnMod);
PostEnabled = IpcSubscribers.PostEnabledDraw.Subscriber(pi, UpdateLastDrawnMod);
PostSettingsPanelDraw = IpcSubscribers.PostSettingsDraw.Subscriber(pi, UpdateLastDrawnMod);
ChangedItemTooltip = IpcSubscribers.ChangedItemTooltip.Subscriber(pi, AddedTooltip);
ChangedItemClicked = IpcSubscribers.ChangedItemClicked.Subscriber(pi, AddedClick);
PreSettingsTabBar.Disable();
PreSettingsPanel.Disable();
PostEnabled.Disable();
PostSettingsPanelDraw.Disable();
ChangedItemTooltip.Disable();
ChangedItemClicked.Disable();
}
public void Dispose()
{
PreSettingsTabBar.Dispose();
PreSettingsPanel.Dispose();
PostEnabled.Dispose();
PostSettingsPanelDraw.Dispose();
ChangedItemTooltip.Dispose();
ChangedItemClicked.Dispose();
}
public void Draw()
{
using var _ = ImRaii.TreeNode("UI");
if (!_)
return;
using (var combo = ImRaii.Combo("Tab to Open at", _selectTab.ToString()))
{
if (combo)
foreach (var val in Enum.GetValues<TabType>())
{
if (ImGui.Selectable(val.ToString(), _selectTab == val))
_selectTab = val;
}
}
ImGui.InputTextWithHint("##openMod", "Mod to Open at...", ref _modName, 256);
using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
if (!table)
return;
IpcTester.DrawIntro(IpcSubscribers.PostSettingsDraw.Label, "Last Drawn Mod");
ImGui.TextUnformatted(_lastDrawnMod.Length > 0 ? $"{_lastDrawnMod} at {_lastDrawnModTime}" : "None");
IpcTester.DrawIntro(IpcSubscribers.ChangedItemTooltip.Label, "Add Tooltip");
if (ImGui.Checkbox("##tooltip", ref _subscribedToTooltip))
{
if (_subscribedToTooltip)
ChangedItemTooltip.Enable();
else
ChangedItemTooltip.Disable();
}
ImGui.SameLine();
ImGui.TextUnformatted(_lastHovered);
IpcTester.DrawIntro(IpcSubscribers.ChangedItemClicked.Label, "Subscribe Click");
if (ImGui.Checkbox("##click", ref _subscribedToClick))
{
if (_subscribedToClick)
ChangedItemClicked.Enable();
else
ChangedItemClicked.Disable();
}
ImGui.SameLine();
ImGui.TextUnformatted(_lastClicked);
IpcTester.DrawIntro(OpenMainWindow.Label, "Open Mod Window");
if (ImGui.Button("Open##window"))
_ec = new OpenMainWindow(_pi).Invoke(_selectTab, _modName, _modName);
ImGui.SameLine();
ImGui.TextUnformatted(_ec.ToString());
IpcTester.DrawIntro(CloseMainWindow.Label, "Close Mod Window");
if (ImGui.Button("Close##window"))
new CloseMainWindow(_pi).Invoke();
}
private void UpdateLastDrawnMod(string name)
=> (_lastDrawnMod, _lastDrawnModTime) = (name, DateTimeOffset.Now);
private void UpdateLastDrawnMod(string name, float _1, float _2)
=> (_lastDrawnMod, _lastDrawnModTime) = (name, DateTimeOffset.Now);
private void AddedTooltip(ChangedItemType type, uint id)
{
_lastHovered = $"{type} {id} at {DateTime.UtcNow.ToLocalTime().ToString(CultureInfo.CurrentCulture)}";
ImGui.TextUnformatted("IPC Test Successful");
}
private void AddedClick(MouseButton button, ChangedItemType type, uint id)
{
_lastClicked = $"{button}-click on {type} {id} at {DateTime.UtcNow.ToLocalTime().ToString(CultureInfo.CurrentCulture)}";
}
}

View file

@ -0,0 +1,103 @@
using Penumbra.GameData.Data;
using Penumbra.Mods.Manager;
namespace Penumbra.Api;
public sealed class ModChangedItemAdapter(WeakReference<ModStorage> storage)
: IReadOnlyDictionary<string, IReadOnlyDictionary<string, object?>>,
IReadOnlyList<(string ModDirectory, IReadOnlyDictionary<string, object?> ChangedItems)>
{
IEnumerator<(string ModDirectory, IReadOnlyDictionary<string, object?> ChangedItems)>
IEnumerable<(string ModDirectory, IReadOnlyDictionary<string, object?> ChangedItems)>.GetEnumerator()
=> Storage.Select(m => (m.Identifier, (IReadOnlyDictionary<string, object?>)new ChangedItemDictionaryAdapter(m.ChangedItems)))
.GetEnumerator();
public IEnumerator<KeyValuePair<string, IReadOnlyDictionary<string, object?>>> GetEnumerator()
=> Storage.Select(m => new KeyValuePair<string, IReadOnlyDictionary<string, object?>>(m.Identifier,
new ChangedItemDictionaryAdapter(m.ChangedItems)))
.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public int Count
=> Storage.Count;
public bool ContainsKey(string key)
=> Storage.TryGetMod(key, string.Empty, out _);
public bool TryGetValue(string key, [NotNullWhen(true)] out IReadOnlyDictionary<string, object?>? value)
{
if (Storage.TryGetMod(key, string.Empty, out var mod))
{
value = new ChangedItemDictionaryAdapter(mod.ChangedItems);
return true;
}
value = null;
return false;
}
public IReadOnlyDictionary<string, object?> this[string key]
=> TryGetValue(key, out var v) ? v : throw new KeyNotFoundException();
(string ModDirectory, IReadOnlyDictionary<string, object?> ChangedItems)
IReadOnlyList<(string ModDirectory, IReadOnlyDictionary<string, object?> ChangedItems)>.this[int index]
{
get
{
var m = Storage[index];
return (m.Identifier, new ChangedItemDictionaryAdapter(m.ChangedItems));
}
}
public IEnumerable<string> Keys
=> Storage.Select(m => m.Identifier);
public IEnumerable<IReadOnlyDictionary<string, object?>> Values
=> Storage.Select(m => new ChangedItemDictionaryAdapter(m.ChangedItems));
private ModStorage Storage
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
get => storage.TryGetTarget(out var t)
? t
: throw new ObjectDisposedException("The underlying mod storage of this IPC container was disposed.");
}
private sealed class ChangedItemDictionaryAdapter(SortedList<string, IIdentifiedObjectData> data) : IReadOnlyDictionary<string, object?>
{
public IEnumerator<KeyValuePair<string, object?>> GetEnumerator()
=> data.Select(d => new KeyValuePair<string, object?>(d.Key, d.Value?.ToInternalObject())).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public int Count
=> data.Count;
public bool ContainsKey(string key)
=> data.ContainsKey(key);
public bool TryGetValue(string key, out object? value)
{
if (data.TryGetValue(key, out var v))
{
value = v?.ToInternalObject();
return true;
}
value = null;
return false;
}
public object? this[string key]
=> data[key]?.ToInternalObject();
public IEnumerable<string> Keys
=> data.Keys;
public IEnumerable<object?> Values
=> data.Values.Select(v => v?.ToInternalObject());
}
}

View file

@ -1,135 +0,0 @@
using System;
using System.IO;
using System.Reflection;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Logging;
using Lumina.Data;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Util;
using Penumbra.Mods;
using Penumbra.Util;
namespace Penumbra.Api
{
public class PenumbraApi : IDisposable, IPenumbraApi
{
public int ApiVersion { get; } = 3;
private Penumbra? _penumbra;
private Lumina.GameData? _lumina;
public bool Valid
=> _penumbra != null;
public PenumbraApi( Penumbra penumbra )
{
_penumbra = penumbra;
_lumina = ( Lumina.GameData? )Dalamud.GameData.GetType()
.GetField( "gameData", BindingFlags.Instance | BindingFlags.NonPublic )
?.GetValue( Dalamud.GameData );
}
public void Dispose()
{
_penumbra = null;
_lumina = null;
}
public event ChangedItemClick? ChangedItemClicked;
public event ChangedItemHover? ChangedItemTooltip;
internal bool HasTooltip
=> ChangedItemTooltip != null;
internal void InvokeTooltip( object? it )
=> ChangedItemTooltip?.Invoke( it );
internal void InvokeClick( MouseButton button, object? it )
=> ChangedItemClicked?.Invoke( button, it );
private void CheckInitialized()
{
if( !Valid )
{
throw new Exception( "PluginShare is not initialized." );
}
}
public void RedrawObject( string name, RedrawType setting )
{
CheckInitialized();
_penumbra!.ObjectReloader.RedrawObject( name, setting );
}
public void RedrawObject( GameObject? gameObject, RedrawType setting )
{
CheckInitialized();
_penumbra!.ObjectReloader.RedrawObject( gameObject, setting );
}
public void RedrawAll( RedrawType setting )
{
CheckInitialized();
_penumbra!.ObjectReloader.RedrawAll( setting );
}
private static string ResolvePath( string path, ModManager manager, ModCollection collection )
{
if( !Penumbra.Config.IsEnabled )
{
return path;
}
var gamePath = new GamePath( path );
var ret = collection.Cache?.ResolveSwappedOrReplacementPath( gamePath );
ret ??= manager.Collections.ForcedCollection.Cache?.ResolveSwappedOrReplacementPath( gamePath );
ret ??= path;
return ret;
}
public string ResolvePath( string path )
{
CheckInitialized();
var modManager = Service< ModManager >.Get();
return ResolvePath( path, modManager, modManager.Collections.DefaultCollection );
}
public string ResolvePath( string path, string characterName )
{
CheckInitialized();
var modManager = Service< ModManager >.Get();
return ResolvePath( path, modManager,
modManager.Collections.CharacterCollection.TryGetValue( characterName, out var collection )
? collection
: ModCollection.Empty );
}
private T? GetFileIntern< T >( string resolvedPath ) where T : FileResource
{
CheckInitialized();
try
{
if( Path.IsPathRooted( resolvedPath ) )
{
return _lumina?.GetFileFromDisk< T >( resolvedPath );
}
return Dalamud.GameData.GetFile< T >( resolvedPath );
}
catch( Exception e )
{
PluginLog.Warning( $"Could not load file {resolvedPath}:\n{e}" );
return null;
}
}
public T? GetFile< T >( string gamePath ) where T : FileResource
=> GetFileIntern< T >( ResolvePath( gamePath ) );
public T? GetFile< T >( string gamePath, string characterName ) where T : FileResource
=> GetFileIntern< T >( ResolvePath( gamePath, characterName ) );
}
}

View file

@ -1,154 +0,0 @@
using System;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Logging;
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
using Penumbra.GameData.Enums;
namespace Penumbra.Api
{
public class PenumbraIpc : IDisposable
{
public const string LabelProviderApiVersion = "Penumbra.ApiVersion";
public const string LabelProviderRedrawName = "Penumbra.RedrawObjectByName";
public const string LabelProviderRedrawObject = "Penumbra.RedrawObject";
public const string LabelProviderRedrawAll = "Penumbra.RedrawAll";
public const string LabelProviderResolveDefault = "Penumbra.ResolveDefaultPath";
public const string LabelProviderResolveCharacter = "Penumbra.ResolveCharacterPath";
public const string LabelProviderChangedItemTooltip = "Penumbra.ChangedItemTooltip";
public const string LabelProviderChangedItemClick = "Penumbra.ChangedItemClick";
internal ICallGateProvider< int >? ProviderApiVersion;
internal ICallGateProvider< string, int, object >? ProviderRedrawName;
internal ICallGateProvider< GameObject, int, object >? ProviderRedrawObject;
internal ICallGateProvider< int, object >? ProviderRedrawAll;
internal ICallGateProvider< string, string >? ProviderResolveDefault;
internal ICallGateProvider< string, string, string >? ProviderResolveCharacter;
internal ICallGateProvider< ChangedItemType, uint, object >? ProviderChangedItemTooltip;
internal ICallGateProvider< MouseButton, ChangedItemType, uint, object >? ProviderChangedItemClick;
internal readonly IPenumbraApi Api;
private static RedrawType CheckRedrawType( int value )
{
var type = ( RedrawType )value;
if( Enum.IsDefined( type ) )
{
return type;
}
throw new Exception( "The integer provided for a Redraw Function was not a valid RedrawType." );
}
private void OnClick( MouseButton click, object? item )
{
var (type, id) = ChangedItemExtensions.ChangedItemToTypeAndId( item );
ProviderChangedItemClick?.SendMessage( click, type, id );
}
private void OnTooltip( object? item )
{
var (type, id) = ChangedItemExtensions.ChangedItemToTypeAndId( item );
ProviderChangedItemTooltip?.SendMessage( type, id );
}
public PenumbraIpc( DalamudPluginInterface pi, IPenumbraApi api )
{
Api = api;
try
{
ProviderApiVersion = pi.GetIpcProvider< int >( LabelProviderApiVersion );
ProviderApiVersion.RegisterFunc( () => api.ApiVersion );
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderApiVersion}:\n{e}" );
}
try
{
ProviderRedrawName = pi.GetIpcProvider< string, int, object >( LabelProviderRedrawName );
ProviderRedrawName.RegisterAction( ( s, i ) => api.RedrawObject( s, CheckRedrawType( i ) ) );
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderRedrawName}:\n{e}" );
}
try
{
ProviderRedrawObject = pi.GetIpcProvider< GameObject, int, object >( LabelProviderRedrawObject );
ProviderRedrawObject.RegisterAction( ( o, i ) => api.RedrawObject( o, CheckRedrawType( i ) ) );
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderRedrawObject}:\n{e}" );
}
try
{
ProviderRedrawAll = pi.GetIpcProvider< int, object >( LabelProviderRedrawAll );
ProviderRedrawAll.RegisterAction( i => api.RedrawAll( CheckRedrawType( i ) ) );
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderRedrawAll}:\n{e}" );
}
try
{
ProviderResolveDefault = pi.GetIpcProvider< string, string >( LabelProviderResolveDefault );
ProviderResolveDefault.RegisterFunc( api.ResolvePath );
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderResolveDefault}:\n{e}" );
}
try
{
ProviderResolveCharacter = pi.GetIpcProvider< string, string, string >( LabelProviderResolveCharacter );
ProviderResolveCharacter.RegisterFunc( api.ResolvePath );
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderResolveCharacter}:\n{e}" );
}
try
{
ProviderChangedItemTooltip = pi.GetIpcProvider< ChangedItemType, uint, object >( LabelProviderChangedItemTooltip );
api.ChangedItemTooltip += OnTooltip;
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderChangedItemTooltip}:\n{e}" );
}
try
{
ProviderChangedItemClick = pi.GetIpcProvider< MouseButton, ChangedItemType, uint, object >( LabelProviderChangedItemClick );
api.ChangedItemClicked += OnClick;
}
catch( Exception e )
{
PluginLog.Error( $"Error registering IPC provider for {LabelProviderChangedItemClick}:\n{e}" );
}
}
public void Dispose()
{
ProviderApiVersion?.UnregisterFunc();
ProviderRedrawName?.UnregisterAction();
ProviderRedrawObject?.UnregisterAction();
ProviderRedrawAll?.UnregisterAction();
ProviderResolveDefault?.UnregisterFunc();
ProviderResolveCharacter?.UnregisterFunc();
Api.ChangedItemClicked -= OnClick;
Api.ChangedItemTooltip -= OnTooltip;
}
}
}

View file

@ -0,0 +1,163 @@
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods;
using Penumbra.Services;
using Penumbra.String.Classes;
using Penumbra.Collections.Manager;
using Penumbra.Communication;
using Penumbra.Mods.Settings;
namespace Penumbra.Api;
public enum RedirectResult
{
Success = 0,
IdenticalFileRegistered = 1,
NotRegistered = 2,
FilteredGamePath = 3,
}
public class TempModManager : IDisposable, IService
{
private readonly CommunicatorService _communicator;
private readonly Dictionary<ModCollection, List<TemporaryMod>> _mods = [];
private readonly List<TemporaryMod> _modsForAllCollections = [];
public TempModManager(CommunicatorService communicator)
{
_communicator = communicator;
_communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.TempModManager);
}
public void Dispose()
{
_communicator.CollectionChange.Unsubscribe(OnCollectionChange);
}
public IReadOnlyDictionary<ModCollection, List<TemporaryMod>> Mods
=> _mods;
public IReadOnlyList<TemporaryMod> ModsForAllCollections
=> _modsForAllCollections;
public RedirectResult Register(string tag, ModCollection? collection, Dictionary<Utf8GamePath, FullPath> dict,
MetaDictionary manips, ModPriority priority)
{
var mod = GetOrCreateMod(tag, collection, priority, out var created);
Penumbra.Log.Verbose($"{(created ? "Created" : "Changed")} temporary Mod {mod.Name}.");
mod.SetAll(dict, manips);
ApplyModChange(mod, collection, created, false);
return RedirectResult.Success;
}
public RedirectResult Unregister(string tag, ModCollection? collection, ModPriority? priority)
{
Penumbra.Log.Verbose($"Removing temporary mod with tag {tag}...");
var list = collection == null ? _modsForAllCollections : _mods.GetValueOrDefault(collection);
if (list == null)
return RedirectResult.NotRegistered;
var removed = list.RemoveAll(m =>
{
if (m.Name != tag || priority != null && m.Priority != priority.Value)
return false;
ApplyModChange(m, collection, false, true);
return true;
});
if (removed == 0)
return RedirectResult.NotRegistered;
if (list.Count == 0 && collection != null)
_mods.Remove(collection);
return RedirectResult.Success;
}
// Apply any new changes to the temporary mod.
private void ApplyModChange(TemporaryMod mod, ModCollection? collection, bool created, bool removed)
{
if (collection != null)
{
if (removed)
{
Penumbra.Log.Verbose($"Removing temporary Mod {mod.Name} from {collection.Identity.AnonymizedName}.");
collection.Remove(mod);
_communicator.ModSettingChanged.Invoke(collection, ModSettingChange.TemporaryMod, null, Setting.False, 0, false);
}
else
{
Penumbra.Log.Verbose($"Adding {(created ? "new " : string.Empty)}temporary Mod {mod.Name} to {collection.Identity.AnonymizedName}.");
collection.Apply(mod, created);
_communicator.ModSettingChanged.Invoke(collection, ModSettingChange.TemporaryMod, null, Setting.True, 0, false);
}
}
else
{
Penumbra.Log.Verbose($"Triggering global mod change for {(created ? "new " : string.Empty)}temporary Mod {mod.Name}.");
_communicator.TemporaryGlobalModChange.Invoke(mod, created, removed);
}
}
/// <summary>
/// Apply a mod change to a set of collections.
/// </summary>
public static void OnGlobalModChange(IEnumerable<ModCollection> collections, TemporaryMod mod, bool created, bool removed)
{
if (removed)
foreach (var c in collections)
c.Remove(mod);
else
foreach (var c in collections)
c.Apply(mod, created);
}
// Find or create a mod with the given tag as name and the given priority, for the given collection (or all collections).
// Returns the found or created mod and whether it was newly created.
private TemporaryMod GetOrCreateMod(string tag, ModCollection? collection, ModPriority priority, out bool created)
{
List<TemporaryMod> list;
if (collection == null)
{
list = _modsForAllCollections;
}
else if (_mods.TryGetValue(collection, out var l))
{
list = l;
}
else
{
list = [];
_mods.Add(collection, list);
}
var mod = list.Find(m => m.Priority == priority && m.Name == tag);
if (mod == null)
{
mod = new TemporaryMod
{
Name = tag,
Priority = priority,
};
list.Add(mod);
created = true;
}
else
{
created = false;
}
return mod;
}
private void OnCollectionChange(CollectionType collectionType, ModCollection? oldCollection, ModCollection? newCollection,
string _)
{
if (collectionType is CollectionType.Temporary or CollectionType.Inactive && newCollection == null && oldCollection != null)
_mods.Remove(oldCollection);
}
}

View file

@ -0,0 +1,57 @@
using Dalamud.Bindings.ImGui;
using OtterGui.Text;
namespace Penumbra;
public enum ChangedItemMode
{
GroupedCollapsed,
GroupedExpanded,
Alphabetical,
}
public static class ChangedItemModeExtensions
{
public static ReadOnlySpan<byte> ToName(this ChangedItemMode mode)
=> mode switch
{
ChangedItemMode.GroupedCollapsed => "Grouped (Collapsed)"u8,
ChangedItemMode.GroupedExpanded => "Grouped (Expanded)"u8,
ChangedItemMode.Alphabetical => "Alphabetical"u8,
_ => "Error"u8,
};
public static ReadOnlySpan<byte> ToTooltip(this ChangedItemMode mode)
=> mode switch
{
ChangedItemMode.GroupedCollapsed =>
"Display items as groups by their model and slot. Collapse those groups to a single item by default. Prefers items with more changes affecting them or configured items as the main item."u8,
ChangedItemMode.GroupedExpanded =>
"Display items as groups by their model and slot. Expand those groups showing all items by default. Prefers items with more changes affecting them or configured items as the main item."u8,
ChangedItemMode.Alphabetical => "Display all changed items in a single list sorted alphabetically."u8,
_ => ""u8,
};
public static bool DrawCombo(ReadOnlySpan<byte> label, ChangedItemMode value, float width, Action<ChangedItemMode> setter)
{
ImGui.SetNextItemWidth(width);
using var combo = ImUtf8.Combo(label, value.ToName());
if (!combo)
return false;
var ret = false;
foreach (var newValue in Enum.GetValues<ChangedItemMode>())
{
var selected = ImUtf8.Selectable(newValue.ToName(), newValue == value);
if (selected)
{
ret = true;
setter(newValue);
}
ImUtf8.HoverTooltip(newValue.ToTooltip());
}
return ret;
}
}

View file

@ -0,0 +1,121 @@
using Penumbra.GameData.Enums;
using Penumbra.GameData.Files;
using Penumbra.GameData.Files.AtchStructs;
using Penumbra.Meta;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Collections.Cache;
public sealed class AtchCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<AtchIdentifier, AtchEntry>(manager, collection)
{
private readonly Dictionary<GenderRace, (AtchFile, HashSet<AtchIdentifier>)> _atchFiles = [];
public bool HasFile(GenderRace gr)
=> _atchFiles.ContainsKey(gr);
public bool GetFile(GenderRace gr, [NotNullWhen(true)] out AtchFile? file)
{
if (!_atchFiles.TryGetValue(gr, out var p))
{
file = null;
return false;
}
file = p.Item1;
return true;
}
public void Reset()
{
foreach (var (_, (_, set)) in _atchFiles)
set.Clear();
_atchFiles.Clear();
Clear();
}
protected override void ApplyModInternal(AtchIdentifier identifier, AtchEntry entry)
{
Collection.Counters.IncrementAtch();
ApplyFile(identifier, entry);
}
private void ApplyFile(AtchIdentifier identifier, AtchEntry entry)
{
try
{
if (!_atchFiles.TryGetValue(identifier.GenderRace, out var pair))
{
if (!Manager.AtchManager.AtchFileBase.TryGetValue(identifier.GenderRace, out var baseFile))
throw new Exception($"Invalid Atch File for {identifier.GenderRace.ToName()} requested.");
pair = (baseFile.Clone(), []);
}
if (!Apply(pair.Item1, identifier, entry))
return;
pair.Item2.Add(identifier);
_atchFiles[identifier.GenderRace] = pair;
}
catch (Exception e)
{
Penumbra.Log.Error($"Could not apply ATCH Manipulation {identifier}:\n{e}");
}
}
protected override void RevertModInternal(AtchIdentifier identifier)
{
Collection.Counters.IncrementAtch();
if (!_atchFiles.TryGetValue(identifier.GenderRace, out var pair))
return;
if (!pair.Item2.Remove(identifier))
return;
if (pair.Item2.Count == 0)
{
_atchFiles.Remove(identifier.GenderRace);
return;
}
var def = GetDefault(Manager, identifier);
if (def == null)
throw new Exception($"Reverting an .atch mod had no default value for the identifier to revert to.");
Apply(pair.Item1, identifier, def.Value);
}
public static AtchEntry? GetDefault(MetaFileManager manager, AtchIdentifier identifier)
{
if (!manager.AtchManager.AtchFileBase.TryGetValue(identifier.GenderRace, out var baseFile))
return null;
if (baseFile.Points.FirstOrDefault(p => p.Type == identifier.Type) is not { } point)
return null;
if (point.Entries.Length <= identifier.EntryIndex)
return null;
return point.Entries[identifier.EntryIndex];
}
public static bool Apply(AtchFile file, AtchIdentifier identifier, in AtchEntry entry)
{
if (file.Points.FirstOrDefault(p => p.Type == identifier.Type) is not { } point)
return false;
if (point.Entries.Length <= identifier.EntryIndex)
return false;
point.Entries[identifier.EntryIndex] = entry;
return true;
}
protected override void Dispose(bool _)
{
Clear();
_atchFiles.Clear();
}
}

View file

@ -0,0 +1,65 @@
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Meta;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Collections.Cache;
public sealed class AtrCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<AtrIdentifier, AtrEntry>(manager, collection)
{
public bool ShouldBeDisabled(in ShapeAttributeString attribute, HumanSlot slot, PrimaryId id, GenderRace genderRace)
=> DisabledCount > 0 && _atrData.TryGetValue(attribute, out var value) && value.CheckEntry(slot, id, genderRace) is false;
public int EnabledCount { get; private set; }
public int DisabledCount { get; private set; }
internal IReadOnlyDictionary<ShapeAttributeString, ShapeAttributeHashSet> Data
=> _atrData;
private readonly Dictionary<ShapeAttributeString, ShapeAttributeHashSet> _atrData = [];
public void Reset()
{
Clear();
_atrData.Clear();
DisabledCount = 0;
EnabledCount = 0;
}
protected override void Dispose(bool _)
=> Reset();
protected override void ApplyModInternal(AtrIdentifier identifier, AtrEntry entry)
{
if (!_atrData.TryGetValue(identifier.Attribute, out var value))
{
value = [];
_atrData.Add(identifier.Attribute, value);
}
if (value.TrySet(identifier.Slot, identifier.Id, identifier.GenderRaceCondition, entry.Value, out _))
{
if (entry.Value)
++EnabledCount;
else
++DisabledCount;
}
}
protected override void RevertModInternal(AtrIdentifier identifier)
{
if (!_atrData.TryGetValue(identifier.Attribute, out var value))
return;
if (value.TrySet(identifier.Slot, identifier.Id, identifier.GenderRaceCondition, null, out var which))
{
if (which)
--EnabledCount;
else
--DisabledCount;
if (value.IsEmpty)
_atrData.Remove(identifier.Attribute);
}
}
}

View file

@ -0,0 +1,547 @@
using Dalamud.Interface.ImGuiNotification;
using OtterGui.Classes;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods;
using Penumbra.Communication;
using Penumbra.Mods.Editor;
using Penumbra.String.Classes;
using Penumbra.Util;
using Penumbra.GameData.Data;
using OtterGui.Extensions;
namespace Penumbra.Collections.Cache;
public record struct ModPath(IMod Mod, FullPath Path);
public record ModConflicts(IMod Mod2, List<object> Conflicts, bool HasPriority, bool Solved);
/// <summary>
/// The Cache contains all required temporary data to use a collection.
/// It will only be setup if a collection gets activated in any way.
/// </summary>
public sealed class CollectionCache : IDisposable
{
private readonly CollectionCacheManager _manager;
private readonly ModCollection _collection;
public readonly CollectionModData ModData = new();
private readonly SortedList<string, (SingleArray<IMod>, IIdentifiedObjectData)> _changedItems = [];
public readonly ConcurrentDictionary<Utf8GamePath, ModPath> ResolvedFiles = new();
public readonly CustomResourceCache CustomResources;
public readonly MetaCache Meta;
public readonly Dictionary<IMod, SingleArray<ModConflicts>> ConflictDict = [];
public int Calculating = -1;
public string AnonymizedName
=> _collection.Identity.AnonymizedName;
public IEnumerable<SingleArray<ModConflicts>> AllConflicts
=> ConflictDict.Values;
public SingleArray<ModConflicts> Conflicts(IMod mod)
=> ConflictDict.TryGetValue(mod, out var c) ? c : new SingleArray<ModConflicts>();
private int _changedItemsSaveCounter = -1;
// Obtain currently changed items. Computes them if they haven't been computed before.
public IReadOnlyDictionary<string, (SingleArray<IMod>, IIdentifiedObjectData)> ChangedItems
{
get
{
SetChangedItems();
return _changedItems;
}
}
// The cache reacts through events on its collection changing.
public CollectionCache(CollectionCacheManager manager, ModCollection collection)
{
_manager = manager;
_collection = collection;
Meta = new MetaCache(manager.MetaFileManager, _collection);
CustomResources = new CustomResourceCache(manager.ResourceLoader);
}
public void Dispose()
{
Meta.Dispose();
CustomResources.Dispose();
GC.SuppressFinalize(this);
}
~CollectionCache()
=> Dispose();
// Resolve a given game path according to this collection.
public FullPath? ResolvePath(Utf8GamePath gameResourcePath)
{
if (!ResolvedFiles.TryGetValue(gameResourcePath, out var candidate))
return null;
if (candidate.Path.InternalName.Length > Utf8GamePath.MaxGamePathLength
|| candidate.Path is { IsRooted: true, Exists: false })
return null;
return candidate.Path;
}
// For a given full path, find all game paths that currently use this file.
public IEnumerable<Utf8GamePath> ReverseResolvePath(FullPath localFilePath)
{
var needle = localFilePath.FullName.ToLower();
if (localFilePath.IsRooted)
needle = needle.Replace('/', '\\');
var iterator = ResolvedFiles
.Where(f => string.Equals(f.Value.Path.FullName, needle, StringComparison.OrdinalIgnoreCase))
.Select(kvp => kvp.Key);
// For files that are not rooted, try to add themselves.
if (!localFilePath.IsRooted && Utf8GamePath.FromString(localFilePath.FullName, out var utf8))
iterator = iterator.Prepend(utf8);
return iterator;
}
// Reverse resolve multiple paths at once for efficiency.
public HashSet<Utf8GamePath>[] ReverseResolvePaths(IReadOnlyCollection<string> fullPaths)
{
if (fullPaths.Count == 0)
return [];
var ret = new HashSet<Utf8GamePath>[fullPaths.Count];
var dict = new Dictionary<FullPath, int>(fullPaths.Count);
foreach (var (path, idx) in fullPaths.WithIndex())
{
dict[new FullPath(path)] = idx;
ret[idx] = !Path.IsPathRooted(path) && Utf8GamePath.FromString(path, out var utf8)
? [utf8]
: [];
}
foreach (var (game, full) in ResolvedFiles)
{
if (dict.TryGetValue(full.Path, out var idx))
ret[idx].Add(game);
}
return ret;
}
public void ReloadMod(IMod mod, bool addMetaChanges)
=> _manager.AddChange(ChangeData.ModReload(this, mod, addMetaChanges));
public void AddMod(IMod mod, bool addMetaChanges)
=> _manager.AddChange(ChangeData.ModAddition(this, mod, addMetaChanges));
public void RemoveMod(IMod mod, bool addMetaChanges)
=> _manager.AddChange(ChangeData.ModRemoval(this, mod, addMetaChanges));
/// <summary> Force a file to be resolved to a specific path regardless of conflicts. </summary>
internal void ForceFileSync(Utf8GamePath path, FullPath fullPath)
{
if (!CheckFullPath(path, fullPath))
return;
if (ResolvedFiles.Remove(path, out var modPath))
{
ModData.RemovePath(modPath.Mod, path);
if (fullPath.FullName.Length > 0)
{
ResolvedFiles.TryAdd(path, new ModPath(Mod.ForcedFiles, fullPath));
CustomResources.Invalidate(path);
InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Replaced, path, fullPath, modPath.Path,
Mod.ForcedFiles);
}
else
{
CustomResources.Invalidate(path);
InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Removed, path, FullPath.Empty, modPath.Path, null);
}
}
else if (fullPath.FullName.Length > 0)
{
ResolvedFiles.TryAdd(path, new ModPath(Mod.ForcedFiles, fullPath));
CustomResources.Invalidate(path);
InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Added, path, fullPath, FullPath.Empty, Mod.ForcedFiles);
}
}
private void ReloadModSync(IMod mod, bool addMetaChanges)
{
RemoveModSync(mod, addMetaChanges);
AddModSync(mod, addMetaChanges);
}
internal void RemoveModSync(IMod mod, bool addMetaChanges)
{
var conflicts = Conflicts(mod);
var (paths, manipulations) = ModData.RemoveMod(mod);
if (addMetaChanges)
_collection.Counters.IncrementChange();
foreach (var path in paths)
{
if (ResolvedFiles.Remove(path, out var mp))
{
CustomResources.Invalidate(path);
if (mp.Mod != mod)
Penumbra.Log.Warning(
$"Invalid mod state, removing {mod.Name} and associated file {path} returned current mod {mp.Mod.Name}.");
else
_manager.ResolvedFileChanged.Invoke(_collection, ResolvedFileChanged.Type.Removed, path, FullPath.Empty, mp.Path, mp.Mod);
}
}
foreach (var manipulation in manipulations)
{
if (Meta.RevertMod(manipulation, out var mp) && mp != mod)
Penumbra.Log.Warning(
$"Invalid mod state, removing {mod.Name} and associated manipulation {manipulation} returned current mod {mp.Name}.");
}
ConflictDict.Remove(mod);
foreach (var conflict in conflicts)
{
if (conflict.HasPriority)
{
ReloadModSync(conflict.Mod2, false);
}
else
{
var newConflicts = Conflicts(conflict.Mod2).Remove(c => c.Mod2 == mod);
if (newConflicts.Count > 0)
ConflictDict[conflict.Mod2] = newConflicts;
else
ConflictDict.Remove(conflict.Mod2);
}
}
if (addMetaChanges)
_manager.MetaFileManager.ApplyDefaultFiles(_collection);
}
/// <summary> Add all files and possibly manipulations of a given mod according to its settings in this collection. </summary>
internal void AddModSync(IMod mod, bool addMetaChanges)
{
var files = GetFiles(mod);
foreach (var (path, file) in files.FileRedirections)
AddFile(path, file, mod);
if (files.Manipulations.Count > 0)
{
foreach (var (identifier, entry) in files.Manipulations.Eqp)
AddManipulation(mod, identifier, entry);
foreach (var (identifier, entry) in files.Manipulations.Eqdp)
AddManipulation(mod, identifier, entry);
foreach (var (identifier, entry) in files.Manipulations.Est)
AddManipulation(mod, identifier, entry);
foreach (var (identifier, entry) in files.Manipulations.Gmp)
AddManipulation(mod, identifier, entry);
foreach (var (identifier, entry) in files.Manipulations.Rsp)
AddManipulation(mod, identifier, entry);
foreach (var (identifier, entry) in files.Manipulations.Imc)
AddManipulation(mod, identifier, entry);
foreach (var (identifier, entry) in files.Manipulations.Atch)
AddManipulation(mod, identifier, entry);
foreach (var (identifier, entry) in files.Manipulations.Shp)
AddManipulation(mod, identifier, entry);
foreach (var (identifier, entry) in files.Manipulations.Atr)
AddManipulation(mod, identifier, entry);
foreach (var identifier in files.Manipulations.GlobalEqp)
AddManipulation(mod, identifier, null!);
}
if (addMetaChanges)
{
_collection.Counters.IncrementChange();
_manager.MetaFileManager.ApplyDefaultFiles(_collection);
}
}
private AppliedModData GetFiles(IMod mod)
{
if (mod.Index < 0)
return mod.GetData();
var settings = _collection.GetActualSettings(mod.Index).Settings;
return settings is not { Enabled: true }
? AppliedModData.Empty
: mod.GetData(settings);
}
/// <summary> Invoke only if not in a full recalculation. </summary>
private void InvokeResolvedFileChange(ModCollection collection, ResolvedFileChanged.Type type, Utf8GamePath key, FullPath value,
FullPath old, IMod? mod)
{
if (Calculating == -1)
_manager.ResolvedFileChanged.Invoke(collection, type, key, value, old, mod);
}
private static bool IsRedirectionSupported(Utf8GamePath path, IMod mod)
{
var ext = path.Extension().AsciiToLower().ToString();
switch (ext)
{
case ".atch" or ".eqp" or ".eqdp" or ".est" or ".gmp" or ".cmp" or ".imc":
Penumbra.Messager.NotificationMessage(
$"Redirection of {ext} files for {mod.Name} is unsupported. This probably means that the mod is outdated and may not work correctly.\n\nPlease tell the mod creator to use the corresponding meta manipulations instead.",
NotificationType.Warning);
return false;
case ".lvb" or ".lgb" or ".sgb":
Penumbra.Messager.NotificationMessage($"Redirection of {ext} files for {mod.Name} is unsupported as this breaks the game.\n\nThis mod will probably not work correctly.",
NotificationType.Warning);
return false;
default: return true;
}
}
// Add a specific file redirection, handling potential conflicts.
// For different mods, higher mod priority takes precedence before option group priority,
// which takes precedence before option priority, which takes precedence before ordering.
// Inside the same mod, conflicts are not recorded.
private void AddFile(Utf8GamePath path, FullPath file, IMod mod)
{
if (!CheckFullPath(path, file))
return;
if (!IsRedirectionSupported(path, mod))
return;
try
{
if (ResolvedFiles.TryAdd(path, new ModPath(mod, file)))
{
ModData.AddPath(mod, path);
CustomResources.Invalidate(path);
InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Added, path, file, FullPath.Empty, mod);
return;
}
var modPath = ResolvedFiles[path];
// Lower prioritized option in the same mod.
if (mod == modPath.Mod)
return;
if (AddConflict(path, mod, modPath.Mod))
{
ModData.RemovePath(modPath.Mod, path);
ResolvedFiles[path] = new ModPath(mod, file);
ModData.AddPath(mod, path);
CustomResources.Invalidate(path);
InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Replaced, path, file, modPath.Path, mod);
}
}
catch (Exception ex)
{
Penumbra.Log.Error(
$"[{Environment.CurrentManagedThreadId}] Error adding redirection {file} -> {path} for mod {mod.Name} to collection cache {AnonymizedName}:\n{ex}");
}
}
// Remove all empty conflict sets for a given mod with the given conflicts.
// If transitive is true, also removes the corresponding version of the other mod.
private void RemoveEmptyConflicts(IMod mod, SingleArray<ModConflicts> oldConflicts, bool transitive)
{
var changedConflicts = oldConflicts.Remove(c =>
{
if (c.Conflicts.Count == 0)
{
if (transitive)
RemoveEmptyConflicts(c.Mod2, Conflicts(c.Mod2), false);
return true;
}
return false;
});
if (changedConflicts.Count == 0)
ConflictDict.Remove(mod);
else
ConflictDict[mod] = changedConflicts;
}
// Add a new conflict between the added mod and the existing mod.
// Update all other existing conflicts between the existing mod and other mods if necessary.
// Returns if the added mod takes priority before the existing mod.
private bool AddConflict(object data, IMod addedMod, IMod existingMod)
{
var addedPriority = addedMod.Index >= 0 ? _collection.GetActualSettings(addedMod.Index).Settings!.Priority : addedMod.Priority;
var existingPriority =
existingMod.Index >= 0 ? _collection.GetActualSettings(existingMod.Index).Settings!.Priority : existingMod.Priority;
if (existingPriority < addedPriority)
{
var tmpConflicts = Conflicts(existingMod);
foreach (var conflict in tmpConflicts)
{
if (data is Utf8GamePath path && conflict.Conflicts.RemoveAll(p => p is Utf8GamePath x && x.Equals(path)) > 0
|| data is IMetaIdentifier meta && conflict.Conflicts.RemoveAll(m => m.Equals(meta)) > 0)
AddConflict(data, addedMod, conflict.Mod2);
}
RemoveEmptyConflicts(existingMod, tmpConflicts, true);
}
var addedConflicts = Conflicts(addedMod);
var existingConflicts = Conflicts(existingMod);
if (addedConflicts.FindFirst(c => c.Mod2 == existingMod, out var oldConflicts))
{
// Only need to change one list since both conflict lists refer to the same list.
oldConflicts.Conflicts.Add(data);
}
else
{
// Add the same conflict list to both conflict directions.
var conflictList = new List<object> { data };
ConflictDict[addedMod] = addedConflicts.Append(new ModConflicts(existingMod, conflictList, existingPriority < addedPriority,
existingPriority != addedPriority));
ConflictDict[existingMod] = existingConflicts.Append(new ModConflicts(addedMod, conflictList,
existingPriority >= addedPriority,
existingPriority != addedPriority));
}
return existingPriority < addedPriority;
}
// Add a specific manipulation, handling potential conflicts.
// For different mods, higher mod priority takes precedence before option group priority,
// which takes precedence before option priority, which takes precedence before ordering.
// Inside the same mod, conflicts are not recorded.
private void AddManipulation(IMod mod, IMetaIdentifier identifier, object entry)
{
if (!Meta.TryGetMod(identifier, out var existingMod))
{
Meta.ApplyMod(mod, identifier, entry);
ModData.AddManip(mod, identifier);
return;
}
// Lower prioritized option in the same mod.
if (mod == existingMod)
return;
if (AddConflict(identifier, mod, existingMod))
{
ModData.RemoveManip(existingMod, identifier);
Meta.ApplyMod(mod, identifier, entry);
ModData.AddManip(mod, identifier);
}
}
// Identify and record all manipulated objects for this entire collection.
private void SetChangedItems()
{
if (_changedItemsSaveCounter == _collection.Counters.Change)
return;
try
{
_changedItemsSaveCounter = _collection.Counters.Change;
_changedItems.Clear();
// Skip IMCs because they would result in far too many false-positive items,
// since they are per set instead of per item-slot/item/variant.
var identifier = _manager.MetaFileManager.Identifier;
var items = new SortedList<string, IIdentifiedObjectData>(512);
void AddItems(IMod mod)
{
foreach (var (name, obj) in items)
{
if (!_changedItems.TryGetValue(name, out var data))
_changedItems.Add(name, (new SingleArray<IMod>(mod), obj));
else if (!data.Item1.Contains(mod))
_changedItems[name] = (data.Item1.Append(mod),
obj is IdentifiedCounter x && data.Item2 is IdentifiedCounter y ? x + y : obj);
else if (obj is IdentifiedCounter x && data.Item2 is IdentifiedCounter y)
_changedItems[name] = (data.Item1, x + y);
}
items.Clear();
}
foreach (var (resolved, modPath) in ResolvedFiles.Where(file => !file.Key.Path.EndsWith("imc"u8)))
{
identifier.Identify(items, resolved.ToString());
AddItems(modPath.Mod);
}
foreach (var (manip, mod) in Meta.IdentifierSources)
{
manip.AddChangedItems(identifier, items);
AddItems(mod);
}
if (_manager.Config.HideMachinistOffhandFromChangedItems)
_changedItems.RemoveMachinistOffhands();
}
catch (Exception e)
{
Penumbra.Log.Error($"Unknown Error:\n{e}");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool CheckFullPath(Utf8GamePath path, FullPath fullPath)
{
if (fullPath.InternalName.Length < Utf8GamePath.MaxGamePathLength)
return true;
Penumbra.Log.Error($"The redirected path is too long to add the redirection\n\t{path}\n\t--> {fullPath}");
return false;
}
public readonly record struct ChangeData
{
public readonly CollectionCache Cache;
public readonly Utf8GamePath Path;
public readonly FullPath FullPath;
public readonly IMod Mod;
public readonly byte Type;
public readonly bool AddMetaChanges;
private ChangeData(CollectionCache cache, Utf8GamePath p, FullPath fp, IMod m, byte t, bool a)
{
Cache = cache;
Path = p;
FullPath = fp;
Mod = m;
Type = t;
AddMetaChanges = a;
}
public static ChangeData ModRemoval(CollectionCache cache, IMod mod, bool addMetaChanges)
=> new(cache, Utf8GamePath.Empty, FullPath.Empty, mod, 0, addMetaChanges);
public static ChangeData ModAddition(CollectionCache cache, IMod mod, bool addMetaChanges)
=> new(cache, Utf8GamePath.Empty, FullPath.Empty, mod, 1, addMetaChanges);
public static ChangeData ModReload(CollectionCache cache, IMod mod, bool addMetaChanges)
=> new(cache, Utf8GamePath.Empty, FullPath.Empty, mod, 2, addMetaChanges);
public static ChangeData ForcedFile(CollectionCache cache, Utf8GamePath p, FullPath fp)
=> new(cache, p, fp, Mods.Mod.ForcedFiles, 3, false);
public void Apply()
{
switch (Type)
{
case 0:
Cache.RemoveModSync(Mod, AddMetaChanges);
break;
case 1:
Cache.AddModSync(Mod, AddMetaChanges);
break;
case 2:
Cache.ReloadModSync(Mod, AddMetaChanges);
break;
case 3:
Cache.ForceFileSync(Path, FullPath);
break;
}
}
}
}

View file

@ -0,0 +1,411 @@
using Dalamud.Plugin.Services;
using OtterGui.Classes;
using OtterGui.Services;
using Penumbra.Api;
using Penumbra.Api.Enums;
using Penumbra.Collections.Manager;
using Penumbra.Communication;
using Penumbra.Interop.Hooks.ResourceLoading;
using Penumbra.Meta;
using Penumbra.Mods;
using Penumbra.Mods.Groups;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Manager.OptionEditor;
using Penumbra.Mods.Settings;
using Penumbra.Mods.SubMods;
using Penumbra.Services;
using Penumbra.String.Classes;
namespace Penumbra.Collections.Cache;
public class CollectionCacheManager : IDisposable, IService
{
private readonly FrameworkManager _framework;
private readonly CommunicatorService _communicator;
private readonly TempModManager _tempMods;
private readonly ModStorage _modStorage;
private readonly CollectionStorage _storage;
private readonly ActiveCollections _active;
internal readonly Configuration Config;
internal readonly ResolvedFileChanged ResolvedFileChanged;
internal readonly MetaFileManager MetaFileManager;
internal readonly ResourceLoader ResourceLoader;
private readonly ConcurrentQueue<CollectionCache.ChangeData> _changeQueue = new();
private int _count;
public int Count
=> _count;
public IEnumerable<ModCollection> Active
=> _storage.Where(c => c.HasCache);
public CollectionCacheManager(FrameworkManager framework, CommunicatorService communicator, TempModManager tempMods, ModStorage modStorage,
MetaFileManager metaFileManager, ActiveCollections active, CollectionStorage storage, ResourceLoader resourceLoader,
Configuration config)
{
_framework = framework;
_communicator = communicator;
_tempMods = tempMods;
_modStorage = modStorage;
MetaFileManager = metaFileManager;
_active = active;
_storage = storage;
ResourceLoader = resourceLoader;
Config = config;
ResolvedFileChanged = _communicator.ResolvedFileChanged;
if (!_active.Individuals.IsLoaded)
_active.Individuals.Loaded += CreateNecessaryCaches;
_framework.Framework.Update += OnFramework;
_communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.CollectionCacheManager);
_communicator.ModPathChanged.Subscribe(OnModChangeAddition, ModPathChanged.Priority.CollectionCacheManagerAddition);
_communicator.ModPathChanged.Subscribe(OnModChangeRemoval, ModPathChanged.Priority.CollectionCacheManagerRemoval);
_communicator.TemporaryGlobalModChange.Subscribe(OnGlobalModChange, TemporaryGlobalModChange.Priority.CollectionCacheManager);
_communicator.ModOptionChanged.Subscribe(OnModOptionChange, ModOptionChanged.Priority.CollectionCacheManager);
_communicator.ModSettingChanged.Subscribe(OnModSettingChange, ModSettingChanged.Priority.CollectionCacheManager);
_communicator.CollectionInheritanceChanged.Subscribe(OnCollectionInheritanceChange,
CollectionInheritanceChanged.Priority.CollectionCacheManager);
_communicator.ModDiscoveryStarted.Subscribe(OnModDiscoveryStarted, ModDiscoveryStarted.Priority.CollectionCacheManager);
_communicator.ModDiscoveryFinished.Subscribe(OnModDiscoveryFinished, ModDiscoveryFinished.Priority.CollectionCacheManager);
if (!MetaFileManager.CharacterUtility.Ready)
MetaFileManager.CharacterUtility.LoadingFinished.Subscribe(IncrementCounters, CharacterUtilityFinished.Priority.CollectionCacheManager);
}
public void Dispose()
{
_communicator.CollectionChange.Unsubscribe(OnCollectionChange);
_communicator.ModPathChanged.Unsubscribe(OnModChangeAddition);
_communicator.ModPathChanged.Unsubscribe(OnModChangeRemoval);
_communicator.TemporaryGlobalModChange.Unsubscribe(OnGlobalModChange);
_communicator.ModOptionChanged.Unsubscribe(OnModOptionChange);
_communicator.ModSettingChanged.Unsubscribe(OnModSettingChange);
_communicator.CollectionInheritanceChanged.Unsubscribe(OnCollectionInheritanceChange);
MetaFileManager.CharacterUtility.LoadingFinished.Unsubscribe(IncrementCounters);
foreach (var collection in _storage)
{
collection._cache?.Dispose();
collection._cache = null;
}
}
public void AddChange(CollectionCache.ChangeData data)
{
if (data.Cache.Calculating == -1)
{
if (_framework.Framework.IsInFrameworkUpdateThread)
data.Apply();
else
_changeQueue.Enqueue(data);
}
else if (data.Cache.Calculating == Environment.CurrentManagedThreadId)
{
data.Apply();
}
else
{
_changeQueue.Enqueue(data);
}
}
/// <summary> Only creates a new cache, does not update an existing one. </summary>
public bool CreateCache(ModCollection collection)
{
if (collection.Identity.Index == ModCollection.Empty.Identity.Index)
return false;
if (collection._cache != null)
return false;
collection._cache = new CollectionCache(this, collection);
if (collection.Identity.Index > 0)
Interlocked.Increment(ref _count);
Penumbra.Log.Verbose($"Created new cache for collection {collection.Identity.AnonymizedName}.");
return true;
}
/// <summary>
/// Update the effective file list for the given cache.
/// Does not create caches.
/// </summary>
public void CalculateEffectiveFileList(ModCollection collection)
=> _framework.RegisterImportant(nameof(CalculateEffectiveFileList) + collection.Identity.Identifier,
() => CalculateEffectiveFileListInternal(collection));
private void CalculateEffectiveFileListInternal(ModCollection collection)
{
// Skip the empty collection.
if (collection.Identity.Index == 0)
return;
Penumbra.Log.Debug($"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.Identity.AnonymizedName}");
if (!collection.HasCache)
{
Penumbra.Log.Error(
$"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.Identity.AnonymizedName} failed, no cache exists.");
}
else if (collection._cache!.Calculating != -1)
{
Penumbra.Log.Error(
$"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.Identity.AnonymizedName} failed, already in calculation on [{collection._cache!.Calculating}].");
}
else
{
FullRecalculation(collection);
Penumbra.Log.Debug(
$"[{Environment.CurrentManagedThreadId}] Recalculation of effective file list for {collection.Identity.AnonymizedName} finished.");
}
}
private void FullRecalculation(ModCollection collection)
{
var cache = collection._cache;
if (cache is not { Calculating: -1 })
return;
cache.Calculating = Environment.CurrentManagedThreadId;
try
{
ResolvedFileChanged.Invoke(collection, ResolvedFileChanged.Type.FullRecomputeStart, Utf8GamePath.Empty, FullPath.Empty,
FullPath.Empty, null);
cache.ResolvedFiles.Clear();
cache.Meta.Reset();
cache.ConflictDict.Clear();
// Add all forced redirects.
foreach (var tempMod in _tempMods.ModsForAllCollections
.Concat(_tempMods.Mods.TryGetValue(collection, out var list)
? list
: Array.Empty<TemporaryMod>()))
cache.AddModSync(tempMod, false);
foreach (var mod in _modStorage)
cache.AddModSync(mod, false);
collection.Counters.IncrementChange();
MetaFileManager.ApplyDefaultFiles(collection);
ResolvedFileChanged.Invoke(collection, ResolvedFileChanged.Type.FullRecomputeFinished, Utf8GamePath.Empty, FullPath.Empty,
FullPath.Empty,
null);
}
finally
{
cache.Calculating = -1;
}
}
private void OnCollectionChange(CollectionType type, ModCollection? old, ModCollection? newCollection, string displayName)
{
if (type is CollectionType.Temporary)
{
if (newCollection != null && CreateCache(newCollection))
CalculateEffectiveFileList(newCollection);
if (old != null)
ClearCache(old);
}
else
{
RemoveCache(old);
if (type is not CollectionType.Inactive && newCollection != null && newCollection.Identity.Index != 0 && CreateCache(newCollection))
CalculateEffectiveFileList(newCollection);
if (type is CollectionType.Default)
if (newCollection != null)
MetaFileManager.ApplyDefaultFiles(newCollection);
else
MetaFileManager.CharacterUtility.ResetAll();
}
}
private void OnModChangeRemoval(ModPathChangeType type, Mod mod, DirectoryInfo? oldModPath, DirectoryInfo? newModPath)
{
switch (type)
{
case ModPathChangeType.Deleted:
case ModPathChangeType.StartingReload:
foreach (var collection in _storage.Where(c => c.HasCache && c.GetActualSettings(mod.Index).Settings?.Enabled == true))
collection._cache!.RemoveMod(mod, true);
break;
case ModPathChangeType.Moved:
foreach (var collection in _storage.Where(c => c.HasCache && c.GetActualSettings(mod.Index).Settings?.Enabled == true))
collection._cache!.ReloadMod(mod, true);
break;
}
}
private void OnModChangeAddition(ModPathChangeType type, Mod mod, DirectoryInfo? oldModPath, DirectoryInfo? newModPath)
{
if (type is not (ModPathChangeType.Added or ModPathChangeType.Reloaded))
return;
foreach (var collection in _storage.Where(c => c.HasCache && c.GetActualSettings(mod.Index).Settings?.Enabled == true))
collection._cache!.AddMod(mod, true);
}
/// <summary> Apply a mod change to all collections with a cache. </summary>
private void OnGlobalModChange(TemporaryMod mod, bool created, bool removed)
=> TempModManager.OnGlobalModChange(_storage.Where(c => c.HasCache), mod, created, removed);
/// <summary> Remove a cache from a collection if it is active. </summary>
private void RemoveCache(ModCollection? collection)
{
if (collection != null
&& collection.Identity.Index > ModCollection.Empty.Identity.Index
&& collection.Identity.Index != _active.Default.Identity.Index
&& collection.Identity.Index != _active.Interface.Identity.Index
&& collection.Identity.Index != _active.Current.Identity.Index
&& _active.SpecialAssignments.All(c => c.Value.Identity.Index != collection.Identity.Index)
&& _active.Individuals.All(c => c.Collection.Identity.Index != collection.Identity.Index))
ClearCache(collection);
}
/// <summary> Prepare Changes by removing mods from caches with collections or add or reload mods. </summary>
private void OnModOptionChange(ModOptionChangeType type, Mod mod, IModGroup? group, IModOption? option, IModDataContainer? container,
int movedToIdx)
{
if (type is ModOptionChangeType.PrepareChange)
{
foreach (var collection in _storage.Where(collection => collection.HasCache && collection.GetActualSettings(mod.Index).Settings is { Enabled: true }))
collection._cache!.RemoveMod(mod, false);
return;
}
type.HandlingInfo(out _, out var recomputeList, out var justAdd);
if (!recomputeList)
return;
foreach (var collection in _storage.Where(collection => collection.HasCache && collection.GetActualSettings(mod.Index).Settings is { Enabled: true }))
{
if (justAdd)
collection._cache!.AddMod(mod, true);
else
collection._cache!.ReloadMod(mod, true);
}
}
/// <summary> Increment the counter to ensure new files are loaded after applying meta changes. </summary>
private void IncrementCounters()
{
foreach (var collection in _storage.Where(c => c.HasCache))
collection.Counters.IncrementChange();
MetaFileManager.CharacterUtility.LoadingFinished.Unsubscribe(IncrementCounters);
}
private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, Setting oldValue, int groupIdx, bool _)
{
if (!collection.HasCache)
return;
var cache = collection._cache!;
switch (type)
{
case ModSettingChange.Inheritance:
cache.ReloadMod(mod!, true);
break;
case ModSettingChange.EnableState:
if (oldValue == Setting.False)
cache.AddMod(mod!, true);
else if (oldValue == Setting.True)
cache.RemoveMod(mod!, true);
else if (collection.GetActualSettings(mod!.Index).Settings?.Enabled == true)
cache.ReloadMod(mod!, true);
else
cache.RemoveMod(mod!, true);
break;
case ModSettingChange.Priority:
if (cache.Conflicts(mod!).Count > 0)
cache.ReloadMod(mod!, true);
break;
case ModSettingChange.Setting:
if (collection.GetActualSettings(mod!.Index).Settings?.Enabled == true)
cache.ReloadMod(mod, true);
break;
case ModSettingChange.TemporarySetting:
cache.ReloadMod(mod!, true);
break;
case ModSettingChange.MultiInheritance:
case ModSettingChange.MultiEnableState:
FullRecalculation(collection);
break;
case ModSettingChange.TemporaryMod:
case ModSettingChange.Edited:
// handled otherwise
break;
}
}
/// <summary>
/// Inheritance changes are too big to check for relevance,
/// just recompute everything.
/// </summary>
private void OnCollectionInheritanceChange(ModCollection collection, bool _)
=> FullRecalculation(collection);
/// <summary> Clear the current cache of a collection. </summary>
private void ClearCache(ModCollection collection)
{
if (!collection.HasCache)
return;
collection._cache!.Dispose();
collection._cache = null;
if (collection.Identity.Index > 0)
Interlocked.Decrement(ref _count);
Penumbra.Log.Verbose($"Cleared cache of collection {collection.Identity.AnonymizedName}.");
}
/// <summary>
/// Cache handling. Usually recreate caches on the next framework tick,
/// but at launch create all of them at once.
/// </summary>
public void CreateNecessaryCaches()
{
ModCollection[] caches;
// Lock to make sure no race conditions during CreateCache happen.
lock (this)
{
caches = _active.SpecialAssignments.Select(p => p.Value)
.Concat(_active.Individuals.Select(p => p.Collection))
.Prepend(_active.Current)
.Prepend(_active.Default)
.Prepend(_active.Interface)
.Where(CreateCache).ToArray();
}
Parallel.ForEach(caches, CalculateEffectiveFileListInternal);
}
private void OnModDiscoveryStarted()
{
foreach (var collection in Active)
{
collection._cache!.ResolvedFiles.Clear();
collection._cache!.Meta.Reset();
collection._cache!.ConflictDict.Clear();
}
}
private void OnModDiscoveryFinished()
=> Parallel.ForEach(Active, CalculateEffectiveFileListInternal);
/// <summary>
/// Update forced files only on framework.
/// </summary>
private void OnFramework(IFramework _)
{
while (_changeQueue.TryDequeue(out var changeData))
changeData.Apply();
}
}

View file

@ -0,0 +1,62 @@
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Editor;
using Penumbra.String.Classes;
namespace Penumbra.Collections.Cache;
/// <summary>
/// Contains associations between a mod and the paths and meta manipulations affected by that mod.
/// </summary>
public class CollectionModData
{
private readonly Dictionary<IMod, (HashSet<Utf8GamePath>, HashSet<IMetaIdentifier>)> _data = new();
public IEnumerable<(IMod, IReadOnlySet<Utf8GamePath>, IReadOnlySet<IMetaIdentifier>)> Data
=> _data.Select(kvp => (kvp.Key, (IReadOnlySet<Utf8GamePath>)kvp.Value.Item1, (IReadOnlySet<IMetaIdentifier>)kvp.Value.Item2));
public (IReadOnlyCollection<Utf8GamePath> Paths, IReadOnlyCollection<IMetaIdentifier> Manipulations) RemoveMod(IMod mod)
{
if (_data.Remove(mod, out var data))
return data;
return ([], []);
}
public void AddPath(IMod mod, Utf8GamePath path)
{
if (_data.TryGetValue(mod, out var data))
{
data.Item1.Add(path);
}
else
{
data = ([path], []);
_data.Add(mod, data);
}
}
public void AddManip(IMod mod, IMetaIdentifier manipulation)
{
if (_data.TryGetValue(mod, out var data))
{
data.Item2.Add(manipulation);
}
else
{
data = ([], [manipulation]);
_data.Add(mod, data);
}
}
public void RemovePath(IMod mod, Utf8GamePath path)
{
if (_data.TryGetValue(mod, out var data) && data.Item1.Remove(path) && data.Item1.Count == 0 && data.Item2.Count == 0)
_data.Remove(mod);
}
public void RemoveManip(IMod mod, IMetaIdentifier manip)
{
if (_data.TryGetValue(mod, out var data) && data.Item2.Remove(manip) && data.Item1.Count == 0 && data.Item2.Count == 0)
_data.Remove(mod);
}
}

View file

@ -0,0 +1,49 @@
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Api.Enums;
using Penumbra.Interop.Hooks.ResourceLoading;
using Penumbra.Interop.SafeHandles;
using Penumbra.String.Classes;
namespace Penumbra.Collections.Cache;
/// <summary> A cache for resources owned by a collection. </summary>
public sealed class CustomResourceCache(ResourceLoader loader)
: ConcurrentDictionary<Utf8GamePath, SafeResourceHandle>, IDisposable
{
/// <summary> Invalidate an existing resource by clearing it from the cache and disposing it. </summary>
public void Invalidate(Utf8GamePath path)
{
if (TryRemove(path, out var handle))
handle.Dispose();
}
public void Dispose()
{
foreach (var handle in Values)
handle.Dispose();
Clear();
}
/// <summary> Get the requested resource either from the cached resource, or load a new one if it does not exist. </summary>
public SafeResourceHandle Get(ResourceCategory category, ResourceType type, Utf8GamePath path, ResolveData data)
{
if (TryGetClonedValue(path, out var handle))
return handle;
handle = loader.LoadResolvedSafeResource(category, type, path.Path, data);
var clone = handle.Clone();
if (!TryAdd(path, clone))
clone.Dispose();
return handle;
}
/// <summary> Get a cloned cached resource if it exists. </summary>
private bool TryGetClonedValue(Utf8GamePath path, [NotNullWhen(true)] out SafeResourceHandle? handle)
{
if (!TryGetValue(path, out handle))
return false;
handle = handle.Clone();
return true;
}
}

View file

@ -0,0 +1,54 @@
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Meta;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Collections.Cache;
public sealed class EqdpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<EqdpIdentifier, EqdpEntry>(manager, collection)
{
private readonly Dictionary<(PrimaryId Id, GenderRace GenderRace, bool Accessory), (EqdpEntry Entry, EqdpEntry InverseMask)> _fullEntries =
[];
public EqdpEntry ApplyFullEntry(PrimaryId id, GenderRace genderRace, bool accessory, EqdpEntry originalEntry)
=> _fullEntries.TryGetValue((id, genderRace, accessory), out var pair)
? (originalEntry & pair.InverseMask) | pair.Entry
: originalEntry;
public void Reset()
{
Clear();
_fullEntries.Clear();
}
protected override void ApplyModInternal(EqdpIdentifier identifier, EqdpEntry entry)
{
var tuple = (identifier.SetId, identifier.GenderRace, identifier.Slot.IsAccessory());
var mask = Eqdp.Mask(identifier.Slot);
var inverseMask = ~mask;
if (_fullEntries.TryGetValue(tuple, out var pair))
pair = ((pair.Entry & inverseMask) | (entry & mask), pair.InverseMask & inverseMask);
else
pair = (entry & mask, inverseMask);
_fullEntries[tuple] = pair;
}
protected override void RevertModInternal(EqdpIdentifier identifier)
{
var tuple = (identifier.SetId, identifier.GenderRace, identifier.Slot.IsAccessory());
if (!_fullEntries.Remove(tuple, out var pair))
return;
var mask = Eqdp.Mask(identifier.Slot);
var newMask = pair.InverseMask | mask;
if (newMask is not EqdpEntry.FullMask)
_fullEntries[tuple] = (pair.Entry & ~mask, newMask);
}
protected override void Dispose(bool _)
{
Clear();
_fullEntries.Clear();
}
}

View file

@ -0,0 +1,66 @@
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Meta;
using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Collections.Cache;
public sealed class EqpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<EqpIdentifier, EqpEntry>(manager, collection)
{
public unsafe EqpEntry GetValues(CharacterArmor* armor)
{
var bodyEntry = GetSingleValue(armor[1].Set, EquipSlot.Body);
var headEntry = bodyEntry.HasFlag(EqpEntry.BodyShowHead)
? GetSingleValue(armor[0].Set, EquipSlot.Head)
: GetSingleValue(armor[1].Set, EquipSlot.Head);
var handEntry = bodyEntry.HasFlag(EqpEntry.BodyShowHand)
? GetSingleValue(armor[2].Set, EquipSlot.Hands)
: GetSingleValue(armor[1].Set, EquipSlot.Hands);
var (legsEntry, legsId) = bodyEntry.HasFlag(EqpEntry.BodyShowLeg)
? (GetSingleValue(armor[3].Set, EquipSlot.Legs), 3)
: (GetSingleValue(armor[1].Set, EquipSlot.Legs), 1);
var footEntry = legsEntry.HasFlag(EqpEntry.LegsShowFoot)
? GetSingleValue(armor[4].Set, EquipSlot.Feet)
: GetSingleValue(armor[legsId].Set, EquipSlot.Feet);
var combined = bodyEntry | headEntry | handEntry | legsEntry | footEntry;
return PostProcessFeet(PostProcessHands(combined));
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private EqpEntry GetSingleValue(PrimaryId id, EquipSlot slot)
=> TryGetValue(new EqpIdentifier(id, slot), out var pair) ? pair.Entry : ExpandedEqpFile.GetDefault(Manager, id) & Eqp.Mask(slot);
public void Reset()
=> Clear();
protected override void Dispose(bool _)
=> Clear();
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static EqpEntry PostProcessHands(EqpEntry entry)
{
if (!entry.HasFlag(EqpEntry.HandsHideForearm))
return entry;
var testFlag = entry.HasFlag(EqpEntry.HandsHideElbow)
? entry.HasFlag(EqpEntry.BodyHideGlovesL)
: entry.HasFlag(EqpEntry.BodyHideGlovesM);
return testFlag
? (entry | EqpEntry.BodyHideGloveCuffs) & ~EqpEntry.BodyHideGlovesS
: entry & ~(EqpEntry.BodyHideGloveCuffs | EqpEntry.BodyHideGlovesS);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static EqpEntry PostProcessFeet(EqpEntry entry)
{
if (!entry.HasFlag(EqpEntry.FeetHideCalf))
return entry;
if (entry.HasFlag(EqpEntry.FeetHideKnee) || !entry.HasFlag(EqpEntry._20))
return entry & ~(EqpEntry.LegsHideBootsS | EqpEntry.LegsHideBootsM);
return (entry | EqpEntry.LegsHideBootsM) & ~EqpEntry.LegsHideBootsS;
}
}

View file

@ -0,0 +1,19 @@
using Penumbra.Meta;
using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Collections.Cache;
public sealed class EstCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<EstIdentifier, EstEntry>(manager, collection)
{
public EstEntry GetEstEntry(EstIdentifier identifier)
=> TryGetValue(identifier, out var entry)
? entry.Entry
: EstFile.GetDefault(Manager, identifier);
public void Reset()
=> Clear();
protected override void Dispose(bool _)
=> Clear();
}

View file

@ -0,0 +1,122 @@
using OtterGui.Classes;
using OtterGui.Services;
using Penumbra.GameData.Structs;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods.Editor;
namespace Penumbra.Collections.Cache;
public class GlobalEqpCache : ReadWriteDictionary<GlobalEqpManipulation, IMod>, IService
{
private readonly HashSet<PrimaryId> _doNotHideEarrings = [];
private readonly HashSet<PrimaryId> _doNotHideNecklace = [];
private readonly HashSet<PrimaryId> _doNotHideBracelets = [];
private readonly HashSet<PrimaryId> _doNotHideRingL = [];
private readonly HashSet<PrimaryId> _doNotHideRingR = [];
private bool _doNotHideVieraHats;
private bool _doNotHideHrothgarHats;
private bool _hideAuRaHorns;
private bool _hideVieraEars;
private bool _hideMiqoteEars;
public new void Clear()
{
base.Clear();
_doNotHideEarrings.Clear();
_doNotHideNecklace.Clear();
_doNotHideBracelets.Clear();
_doNotHideRingL.Clear();
_doNotHideRingR.Clear();
_doNotHideHrothgarHats = false;
_doNotHideVieraHats = false;
_hideAuRaHorns = false;
_hideVieraEars = false;
_hideMiqoteEars = false;
}
public unsafe EqpEntry Apply(EqpEntry original, CharacterArmor* armor)
{
if (Count == 0)
return original;
if (_doNotHideVieraHats)
original |= EqpEntry.HeadShowVieraHat;
if (_doNotHideHrothgarHats)
original |= EqpEntry.HeadShowHrothgarHat;
if (_hideAuRaHorns)
original &= ~EqpEntry.HeadShowEarAuRa;
if (_hideVieraEars)
original &= ~EqpEntry.HeadShowEarViera;
if (_hideMiqoteEars)
original &= ~EqpEntry.HeadShowEarMiqote;
if (_doNotHideEarrings.Contains(armor[5].Set))
original |= EqpEntry.HeadShowEarringsHyurRoe
| EqpEntry.HeadShowEarringsLalaElezen
| EqpEntry.HeadShowEarringsMiqoHrothViera
| EqpEntry.HeadShowEarringsAura;
if (_doNotHideNecklace.Contains(armor[6].Set))
original |= EqpEntry.BodyShowNecklace | EqpEntry.HeadShowNecklace;
if (_doNotHideBracelets.Contains(armor[7].Set))
original |= EqpEntry.BodyShowBracelet | EqpEntry.HandShowBracelet;
if (_doNotHideRingR.Contains(armor[8].Set))
original |= EqpEntry.HandShowRingR;
if (_doNotHideRingL.Contains(armor[9].Set))
original |= EqpEntry.HandShowRingL;
return original;
}
public bool ApplyMod(IMod mod, GlobalEqpManipulation manipulation)
{
if (Remove(manipulation, out var oldMod) && oldMod == mod)
return false;
this[manipulation] = mod;
_ = manipulation.Type switch
{
GlobalEqpType.DoNotHideEarrings => _doNotHideEarrings.Add(manipulation.Condition),
GlobalEqpType.DoNotHideNecklace => _doNotHideNecklace.Add(manipulation.Condition),
GlobalEqpType.DoNotHideBracelets => _doNotHideBracelets.Add(manipulation.Condition),
GlobalEqpType.DoNotHideRingR => _doNotHideRingR.Add(manipulation.Condition),
GlobalEqpType.DoNotHideRingL => _doNotHideRingL.Add(manipulation.Condition),
GlobalEqpType.DoNotHideHrothgarHats => !_doNotHideHrothgarHats && (_doNotHideHrothgarHats = true),
GlobalEqpType.DoNotHideVieraHats => !_doNotHideVieraHats && (_doNotHideVieraHats = true),
GlobalEqpType.HideHorns => !_hideAuRaHorns && (_hideAuRaHorns = true),
GlobalEqpType.HideMiqoteEars => !_hideMiqoteEars && (_hideMiqoteEars = true),
GlobalEqpType.HideVieraEars => !_hideVieraEars && (_hideVieraEars = true),
_ => false,
};
return true;
}
public bool RevertMod(GlobalEqpManipulation manipulation, [NotNullWhen(true)] out IMod? mod)
{
if (!Remove(manipulation, out mod))
return false;
_ = manipulation.Type switch
{
GlobalEqpType.DoNotHideEarrings => _doNotHideEarrings.Remove(manipulation.Condition),
GlobalEqpType.DoNotHideNecklace => _doNotHideNecklace.Remove(manipulation.Condition),
GlobalEqpType.DoNotHideBracelets => _doNotHideBracelets.Remove(manipulation.Condition),
GlobalEqpType.DoNotHideRingR => _doNotHideRingR.Remove(manipulation.Condition),
GlobalEqpType.DoNotHideRingL => _doNotHideRingL.Remove(manipulation.Condition),
GlobalEqpType.DoNotHideHrothgarHats => _doNotHideHrothgarHats && !(_doNotHideHrothgarHats = false),
GlobalEqpType.DoNotHideVieraHats => _doNotHideVieraHats && !(_doNotHideVieraHats = false),
GlobalEqpType.HideHorns => _hideAuRaHorns && (_hideAuRaHorns = false),
GlobalEqpType.HideMiqoteEars => _hideMiqoteEars && (_hideMiqoteEars = false),
GlobalEqpType.HideVieraEars => _hideVieraEars && (_hideVieraEars = false),
_ => false,
};
return true;
}
}

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