231 Commits
v1.0 ... v2.0.3

Author SHA1 Message Date
Pao
74bb34bbc8 Merge pull request #37 from ZioPao/dev
v2.0.3
2024-05-05 00:50:22 +02:00
ZioPao
1b235ebaa4 bump to mod.info 2024-05-05 00:49:05 +02:00
Pao
44d486dfeb Merge pull request #36 from ZioPao/main
backmerge to dev since I'm an idiot
2024-05-05 00:48:40 +02:00
ZioPao
5cc982188a Fixed prosthesis not working correctly 2024-05-05 00:47:50 +02:00
ZioPao
aadbe02df4 Fixed sync with server after setup to prevent issues with medical check 2024-05-05 00:14:37 +02:00
Pao
20bed84910 Merge pull request #33 from ZioPao/dev
v2.0.2
2024-05-04 18:00:40 +02:00
ZioPao
fc3113f243 Bump to mod info and description 2024-05-04 17:59:53 +02:00
ZioPao
be368738ba Fix to cicatrization visuals with traits 2024-05-04 17:11:38 +02:00
ZioPao
4209f690a8 Added IT translation 2024-05-04 16:45:57 +02:00
ZioPao
a3a2614124 Fixed traits 2024-05-04 16:27:46 +02:00
ZioPao
50f6db9344 Added check to prevent incompatibilities with other mods 2024-05-04 15:35:25 +02:00
Pao
17d554d269 Merge pull request #28 from ZioPao/main
back merge from main to dev
2024-05-03 14:25:25 +02:00
ZioPao
1bef713de5 Merge branch 'main' of https://github.com/ZioPao/The-Only-Cure 2024-05-03 14:20:59 +02:00
ZioPao
beee6f409c bump to mod.info 2024-05-03 14:20:57 +02:00
ZioPao
540f510eb0 Fixed wearing items bug 2024-05-03 14:20:46 +02:00
Pao
92334b2f54 Update README.md 2024-05-02 20:41:18 +02:00
ZioPao
515f51d84a Merge branch 'main' of https://github.com/ZioPao/The-Only-Cure 2024-05-02 18:09:34 +02:00
ZioPao
51dbf89ad6 updated steam_desc 2024-05-02 18:09:33 +02:00
Pao
631bfea3da Update README.md 2024-05-02 18:08:02 +02:00
Pao
9d3298ea25 Update README.md 2024-05-02 17:09:27 +02:00
ZioPao
d83fd91d3f fixed mod.info 2024-05-02 14:17:47 +02:00
ZioPao
89ff76ac8a fixed poster and logos 2024-05-02 13:40:08 +02:00
ZioPao
9a22e816f0 Missed isIgnoredPartInfected for HealthFullBody 2024-05-02 12:51:46 +02:00
ZioPao
5dc328ebc2 Fixed Cheats and TOC interactability 2024-05-02 12:46:47 +02:00
ZioPao
b1adefe8f3 somes tuff to fix before relase 2024-05-02 01:24:18 +02:00
ZioPao
ff8032f26a Fixed tourniquet ground model 2024-05-02 00:34:45 +02:00
ZioPao
fa1d86ae7e Cicatrization visuals reimplemented 2024-05-01 23:21:15 +02:00
ZioPao
c621a56f67 Disabled Zombies stuff for now 2024-05-01 23:09:44 +02:00
ZioPao
5b4fb85db6 added steam desc 2024-04-30 19:02:34 +02:00
ZioPao
0113c8272e Merge branch 'main' of https://github.com/ZioPao/The-Only-Cure 2024-04-30 19:01:39 +02:00
ZioPao
2a2d311ac1 updated workshop.txt and logos 2024-04-30 19:01:37 +02:00
Pao
a7de7a7315 Update README.md 2024-04-30 18:50:34 +02:00
ZioPao
551125bb50 restored poster 2024-04-30 18:28:02 +02:00
ZioPao
abc1da7095 Merge branch 'main' of https://github.com/ZioPao/The-Only-Cure 2024-04-30 18:24:40 +02:00
ZioPao
37c9415c2a Changed mod id to prevent issues 2024-04-30 18:24:38 +02:00
Pao
42cf719de9 Update README.md 2024-04-30 18:23:19 +02:00
ZioPao
7b07b7954a cleaning up crap 2024-04-30 18:00:34 +02:00
ZioPao
074950ebf6 Merge branch 'main' of https://github.com/ZioPao/The-Only-Cure 2024-04-30 17:38:48 +02:00
ZioPao
7e505285c6 updated poster 2024-04-30 17:38:46 +02:00
Pao
d341b4cfbd Update README.md 2024-04-30 17:38:25 +02:00
ZioPao
551589e461 new icon 2024-04-30 17:18:04 +02:00
ZioPao
5144491e81 Cleaning up 2024-04-30 17:14:40 +02:00
ZioPao
bd8e4d6c86 Implemented Tourniquet functionality for amputations 2024-04-30 17:09:18 +02:00
ZioPao
c4529d0890 Fixed items floating 2024-04-22 18:09:04 +02:00
ZioPao
005d9e863e Added wrist watches and stuff drop 2024-04-21 03:03:32 +02:00
ZioPao
5c03cea8c4 Equip saw before cutting 2024-04-21 02:20:05 +02:00
ZioPao
d73b1a3f43 Too much stuff to fix. 2024-03-29 18:46:44 +01:00
ZioPao
883a84412b Renamed some stuff, working tourniquet 2024-03-29 02:15:42 +01:00
ZioPao
3a3d58e8a0 Fixed Interact Key bug 2024-03-29 01:09:13 +01:00
ZioPao
1e72bac223 Cleaned admin menu 2024-03-28 23:04:26 +01:00
ZioPao
d771bed2db Changed recipe time, better handling when no hands available 2024-03-28 22:39:38 +01:00
ZioPao
f64a76a128 Added username on TOC option 2024-03-28 19:14:39 +01:00
ZioPao
b1f12e55a1 Local reset for admins 2024-03-28 19:12:56 +01:00
ZioPao
a8956df057 Modified readme, added version to main class 2024-03-28 18:36:07 +01:00
ZioPao
eb3dac0821 ops 2024-03-28 18:28:15 +01:00
ZioPao
f63f0420e2 temp readme 2024-03-28 18:27:48 +01:00
ZioPao
4dd5483f35 Bigger logo 2024-03-28 18:14:14 +01:00
ZioPao
f72fe35e03 Added title 2024-03-28 18:11:28 +01:00
ZioPao
e35b91f142 New posters 2024-03-28 11:22:48 +01:00
ZioPao
43b366b467 cleaning compat 2024-03-28 09:55:24 +01:00
ZioPao
b6fb136098 Compat for FH 2024-03-24 23:45:35 +01:00
ZioPao
639841cf20 Add explicit support for brutal handwork 2024-03-24 22:30:04 +01:00
ZioPao
93a00d1b48 Added German translation 2024-03-23 02:42:59 +01:00
ZioPao
00b1b78cb3 Fixed update when Health Panel is open for remote pl 2024-03-22 15:52:34 +01:00
ZioPao
feab41ac5c Fixed UV for female right forearm amp 2024-03-21 23:34:20 +01:00
ZioPao
35ef646402 Fixed various problems with prosts 2024-03-21 22:56:10 +01:00
ZioPao
cc3ee9495c ObjectClickhandler with limitations 2024-03-21 20:07:34 +01:00
ZioPao
1c990f3b9b Caching hand feasibility, disabling interactions if there are no hands 2024-03-21 19:37:30 +01:00
ZioPao
0e911ec860 Visible prost in HealthPanel 2024-03-21 12:26:48 +01:00
ZioPao
ec44e4a29e Reset model on current frame to force re-init 2024-03-21 11:11:58 +01:00
ZioPao
7a7e90dd7d Fixes 2024-03-21 03:37:50 +01:00
ZioPao
3fb22a9b1e Fixed equipping prosthesis on upper arms 2024-03-21 03:02:52 +01:00
ZioPao
5b524e5a4f added a todo 2024-03-20 11:53:53 +01:00
ZioPao
e055b49c10 Fixed issue with items 2024-03-20 10:47:28 +01:00
ZioPao
dd91ca3c7a Rewrote toc compat 2024-03-20 10:31:11 +01:00
ZioPao
41b6bfd456 deleted old media 2024-03-20 00:45:01 +01:00
ZioPao
6e9e522731 BodyLocations should be FINALLY fine 2024-03-20 00:27:00 +01:00
ZioPao
509f03f2ef Removed useless code 2024-03-19 22:35:54 +01:00
ZioPao
72f225f154 I'm an idiot and I don't need workarounds to hide items in the inventory :))) 2024-03-19 22:35:42 +01:00
ZioPao
40fe46c1c6 Big todos and double amputations for zombies 2024-03-19 21:05:44 +01:00
ZioPao
6c5276aff2 Cleaning 2024-03-19 12:25:19 +01:00
ZioPao
95f1aa304e Working zombies amputation POC 2024-03-19 11:36:19 +01:00
ZioPao
d9c315401c Cleaning up checks for stitches and bandages 2024-03-18 18:49:05 +01:00
ZioPao
afaceac86e Cleaning stuff 2024-03-18 18:35:43 +01:00
ZioPao
5180a2cbfb Switched folders, zombies amputations working 2024-03-18 00:55:30 +01:00
ZioPao
f38d68a35a Added distributions 2024-03-17 18:14:18 +01:00
ZioPao
3be619c329 Removed stuff 2024-03-17 16:54:58 +01:00
ZioPao
b9ed37f3ba Fixed error in UpdateAmputations during init in MP 2024-01-12 14:03:22 +01:00
ZioPao
15b1d070bf Another fix to init and other clients handling 2024-01-11 01:36:16 +01:00
ZioPao
1028cef704 Init for SP and MP working, may rework it though 2024-01-11 01:27:19 +01:00
ZioPao
f984a834fc Fixed SP, added some tests 2024-01-10 23:43:36 +01:00
ZioPao
02c992b12a Merge branch 'master' of https://github.com/ZioPao/The-Only-Cure 2024-01-10 22:15:07 +01:00
ZioPao
482fffbdd0 Added CauteirzeAction and sounds 2024-01-10 22:15:06 +01:00
Pao
95bbedd698 Update README.md 2024-01-10 03:44:39 +01:00
ZioPao
81f9bbf6ea Added prost notification in health panel 2024-01-10 01:38:29 +01:00
ZioPao
74adac6f9d Added missing models 2024-01-10 01:30:54 +01:00
ZioPao
c425203618 Reworked initialization logic for local client 2024-01-10 01:07:12 +01:00
ZioPao
d3d0db3941 Separating some stuff from various files 2024-01-09 21:20:52 +01:00
ZioPao
a41f976949 Readded tourniquet 2024-01-09 14:42:56 +01:00
ZioPao
aeb1bca94d Fixing interactions 2024-01-09 00:04:57 +01:00
ZioPao
06e41790ca More cleaning, fixing up interactions 2024-01-08 22:40:15 +01:00
ZioPao
d3722c1171 Simplifield health panel logic 2024-01-08 16:09:10 +01:00
ZioPao
c35454807d Removed old lua files 2024-01-08 16:01:28 +01:00
ZioPao
cf27a24380 Better debug print 2024-01-08 12:49:42 +01:00
ZioPao
1273dc7c7a Missed some stuff 2024-01-08 11:43:07 +01:00
ZioPao
1cb60dd0e8 Missed some stuff after previous refactoring 2024-01-08 10:59:51 +01:00
ZioPao
312294edb8 Rethinking structure a bit 2024-01-08 10:57:01 +01:00
ZioPao
6b1205e160 Fixed some last stuff in the PlayerHandler and Healthpanel 2024-01-07 22:44:58 +01:00
ZioPao
2cbf5aea8b Fixes to ReceiveData 2024-01-07 21:52:40 +01:00
ZioPao
4cf97a2e79 Reworking initialization and caching 2024-01-07 21:21:05 +01:00
ZioPao
1098333247 Updated gitignore 2024-01-07 18:25:14 +01:00
ZioPao
49832092b4 Added sandbox var for wound dirtyness 2024-01-02 15:04:43 -05:00
ZioPao
f2007d56a9 Cleaning cleans wounds and visual too 2023-12-30 00:09:54 -05:00
ZioPao
4e4b771e41 Handling WoundCleaning 2023-12-29 23:24:49 -05:00
ZioPao
2bf1631809 Fixing up some mistakes 2023-12-24 22:44:19 -05:00
ZioPao
ba119de89b Cauterized status in health panel 2023-12-24 21:44:45 -05:00
ZioPao
33140938c9 Some fixes to cicatrization handling 2023-12-24 02:15:19 -05:00
ZioPao
2c5c3885d1 removed test thingy 2023-12-24 01:16:28 -05:00
ZioPao
96ddd3e030 Cicatrization shown in health panel 2023-12-24 01:14:11 -05:00
ZioPao
809d5492e8 typo 2023-12-23 00:57:07 -05:00
ZioPao
77cf50066a Added ground model for Prosthetic Arm 2023-12-23 00:54:31 -05:00
ZioPao
2606f664bc Fixes to extra equip and recipes 2023-12-22 23:04:36 -05:00
ZioPao
97b51e1bcf Merge branch 'master' of https://github.com/ZioPao/The-Only-Cure 2023-12-18 22:59:42 +01:00
ZioPao
ecee8b06e7 Base stuff for recipes 2023-12-18 22:59:40 +01:00
Pao
d6ba1e9068 Create LICENSE 2023-11-29 01:20:18 +01:00
ZioPao
f352b40232 Changed amputation sound 2023-11-21 02:37:03 +01:00
ZioPao
74508a9ed1 Readded amputation sound 2023-11-21 02:25:49 +01:00
ZioPao
0d3b82aee3 Reworked GetAmputationTexturesIndex 2023-11-21 02:10:09 +01:00
ZioPao
8a3b0bb190 Added suyrgeonFactor (and ugly venom art) 2023-11-21 02:04:30 +01:00
ZioPao
55a4f9fdb4 Added test and workspace 2023-11-19 05:18:52 +01:00
ZioPao
9a3a57a819 fix for TestperfFramework 2023-11-18 16:02:47 +01:00
ZioPao
53eacd4717 Trying to work out TryRandomBleed 2023-11-18 05:23:04 +01:00
ZioPao
877e5a711e Some fixes to ModDataHandler, preventing desync with the table from ModData 2023-11-17 20:05:41 +01:00
ZioPao
bd8dae128c Implemented TestPerfFramework 2023-11-17 18:36:44 +01:00
ZioPao
0d85638464 Added toggle of cicatrization update at startup 2023-11-17 14:50:30 +01:00
ZioPao
38141bc5db Figured out placing textures + male textures 2023-11-17 13:17:19 +01:00
ZioPao
ebd128c710 textures still not aligned, figure it out pls 2023-11-17 02:38:44 +01:00
ZioPao
18dc61f552 Reworked textures for HealthPanel 2023-11-17 02:29:33 +01:00
ZioPao
3418378139 Reorganized Tests 2023-11-17 01:36:24 +01:00
ZioPao
03093f77a7 better random bleeding 2023-11-16 20:15:07 +01:00
ZioPao
1b248d744e Little fixes 2023-11-16 20:04:32 +01:00
ZioPao
fe6d88fa43 Fixing prosthesis equipping and unequipping 2023-11-16 19:45:11 +01:00
ZioPao
43cf52cd02 Added another static table, cicatrization fixes, random bleeding when prost is equipped but isCicatrized is false 2023-11-16 18:58:43 +01:00
ZioPao
18cae1de98 Fixed CutLimbHandler once again 2023-11-16 18:19:47 +01:00
ZioPao
e7119cf032 Added adjacent parts to put the damage 2023-11-16 15:30:34 +01:00
ZioPao
34570cd013 cicatrization process 2023-11-16 12:50:07 +01:00
ZioPao
c09c3ac646 little fix 2023-11-16 02:44:57 +01:00
ZioPao
16e83955f3 Added exceptions for the adjusted time 2023-11-16 02:41:27 +01:00
ZioPao
cda3a61d2a Fixes cause I'm dum 2023-11-16 01:46:41 +01:00
ZioPao
9a4d07900c Fixing healing mechanisms 2023-11-16 01:42:56 +01:00
ZioPao
26f7867320 Added NormalArm 2023-11-16 01:02:23 +01:00
ZioPao
f7bd45420b Fixes 2023-11-15 19:43:59 +01:00
ZioPao
c4f6890dd0 Implemented stitching + bandages after amputations 2023-11-15 19:23:23 +01:00
ZioPao
f7c9047457 Bandage handling and bug fixes 2023-11-15 18:28:37 +01:00
ZioPao
c9b41838be Cleaning, bandages handling 2023-11-15 11:07:15 +01:00
ZioPao
46bc6030fe Added indexed limbs_str 2023-11-15 03:04:30 +01:00
ZioPao
6adb694077 equipping weapons working with prosts 2023-11-15 02:52:02 +01:00
ZioPao
0fdc0de27d Fixed some types stuf 2023-11-15 01:56:34 +01:00
ZioPao
bd9be10e02 Fixes 2023-11-15 01:47:30 +01:00
ZioPao
441fe67890 Added basic stuff to handle zombie amputations 2023-11-15 01:33:58 +01:00
ZioPao
a7d49c7253 fixes after changing some data structure 2023-11-14 23:30:28 +01:00
ZioPao
65795cbe79 cleaned naming for StaticData 2023-11-14 10:20:58 +01:00
ZioPao
81be27561d Adding stuff to accomodate prostheses 2023-11-14 01:59:29 +01:00
ZioPao
150772e252 pushing before fucking everything up 2023-11-14 01:46:12 +01:00
ZioPao
33299b0779 Fixes, placeholders, and getters\setters 2023-11-14 01:17:46 +01:00
ZioPao
46175908b1 Cleaning 2023-11-13 23:48:59 +01:00
ZioPao
56da8583b0 Fixes to CutLimbHandler 2023-11-13 23:46:35 +01:00
ZioPao
4fb19ae5a6 Working MP :) 2023-11-13 21:12:36 +01:00
ZioPao
627e480d07 Fixed ModDataHandler initialization process 2023-11-13 19:37:39 +01:00
ZioPao
6f17b428bc Moved event to PlayerHandler 2023-11-13 18:23:39 +01:00
ZioPao
3bc4ce471f Fixes 2023-11-13 17:30:14 +01:00
ZioPao
3d6144cfcd Bob sheninagans and relay 2023-11-13 16:37:53 +01:00
ZioPao
8fde83c049 Fix for the Bob stuff 2023-11-13 11:06:43 +01:00
ZioPao
328d3c4d3a aligned some crap in the comments 2023-11-13 03:41:14 +01:00
ZioPao
e374601402 Reworked caching 2023-11-13 03:35:07 +01:00
ZioPao
deb6dcc056 Cleaning 2023-11-13 03:15:37 +01:00
ZioPao
980545a07e POC for health panels in MP 2023-11-13 03:04:12 +01:00
ZioPao
30f343a7de added some debug commands 2023-11-12 22:45:35 +01:00
ZioPao
d1aef1f67d Cleaning crap 2023-11-12 22:27:18 +01:00
ZioPao
4887699892 Reworked some stuff for MP 2023-11-12 22:20:46 +01:00
ZioPao
f973b774a2 removed some useless warnings 2023-11-12 06:00:10 +01:00
ZioPao
52a4798377 Added debug print func 2023-11-12 05:58:45 +01:00
ZioPao
3d963a2ad6 fixes 2023-11-12 05:53:26 +01:00
ZioPao
3443198edb Trying to make stuff work in MP 2023-11-12 05:35:54 +01:00
ZioPao
a691a729cf Basic stuff to handle MP envs 2023-11-12 04:48:02 +01:00
ZioPao
5e89a05b26 Renamed another file 2023-11-11 17:59:30 +01:00
ZioPao
74c7b99bc5 Fixed some mistakes 2023-11-11 17:59:18 +01:00
ZioPao
35ead001b0 Reorganizing 2023-11-11 17:53:04 +01:00
ZioPao
a754ae70c4 forgot to re-enable stuff 2023-11-11 04:15:46 +01:00
ZioPao
78942d2e78 Added some descriptions to the oven operation 2023-11-11 04:15:25 +01:00
ZioPao
e8269899b0 Added placeholder for Oven operations 2023-11-11 03:22:05 +01:00
ZioPao
d82fce6f2f some comments 2023-11-11 03:07:27 +01:00
ZioPao
2cf794552f Cleaning 2023-11-11 03:04:48 +01:00
ZioPao
9459360948 Re-added context menus for saws 2023-11-11 02:58:51 +01:00
ZioPao
e8d4a37fdc More basic stuff for prosthesis 2023-11-10 19:00:21 +01:00
ZioPao
ca42c383d1 Debug and basic stuff for prosthesis 2023-11-10 18:01:03 +01:00
ZioPao
7813d242e8 Better handling of items in containers 2023-11-10 12:37:38 +01:00
ZioPao
7424f45ae3 Fixes and handling of perks 2023-11-10 11:33:27 +01:00
ZioPao
7557a13544 Time action handling 2023-11-10 01:11:00 +01:00
ZioPao
c8cd318f2d Bug fixes and better handling of ISHealthPanel 2023-11-09 12:25:29 +01:00
ZioPao
99ce9bacda Perks handling 2023-11-09 11:15:04 +01:00
ZioPao
9095d11f41 Fixed Tests 2023-11-09 10:35:02 +01:00
ZioPao
60a7f1b996 Moved some functions to ItemsHandler 2023-11-09 10:09:46 +01:00
ZioPao
066f4f8049 Mostly working base 2023-11-07 19:01:31 +01:00
ZioPao
74b625b287 Readding amputation models 2023-11-07 18:33:06 +01:00
ZioPao
6c84ab14b7 Fixed Tests 2023-11-07 17:36:04 +01:00
ZioPao
301603ed68 Reimplemented item handling 2023-11-07 17:21:03 +01:00
ZioPao
8faa5b9cc5 Reworked structure a bit 2023-11-07 16:46:37 +01:00
ZioPao
42bc95786b cleaning 2023-11-07 01:39:06 +01:00
ZioPao
01a6d34c97 More tests and cleaning UI 2023-11-07 01:37:00 +01:00
ZioPao
4f6910c806 Bunch of stuff for the UI 2023-11-07 01:19:19 +01:00
ZioPao
835c57faaa Base for working UI 2023-11-07 00:39:40 +01:00
ZioPao
81fc637047 Added handling for infection in other zones 2023-11-06 19:50:10 +01:00
ZioPao
a002bbbdb0 More bug fixes 2023-11-06 19:35:13 +01:00
ZioPao
ae51634be4 Fixing bugs 2023-11-06 19:22:56 +01:00
ZioPao
4254d123bf Cleaning 2023-11-06 18:27:08 +01:00
ZioPao
8ec28be2b6 POC for new UI 2023-11-06 18:23:34 +01:00
ZioPao
d0dae7dfe2 Fixed context menu in the health panel 2023-11-06 15:48:06 +01:00
ZioPao
28ba7d73ab Refactoring and working drag n drop 2023-11-06 14:44:35 +01:00
ZioPao
0ef3f7284f Redoing user experience 2023-11-06 14:28:03 +01:00
ZioPao
4035423590 refactoring 2023-11-06 13:12:42 +01:00
ZioPao
16c510c79f Making it a bit neater 2023-11-06 12:57:33 +01:00
ZioPao
f39bc5c36e Redone staticdata 2023-11-06 11:28:47 +01:00
ZioPao
fcc30e3314 Merge branch 'master' of https://github.com/ZioPao/The-Only-Cure 2023-11-06 11:01:49 +01:00
ZioPao
807d368df4 Initialization remade 2023-11-06 11:01:47 +01:00
Pao
9fe58dee87 ModStructure 2023-11-06 09:39:30 +01:00
ZioPao
d580bad0eb Readded perks 2023-11-06 04:29:50 +01:00
ZioPao
875e0fdceb reworking 2023-11-06 04:16:43 +01:00
ZioPao
d79dfcc509 Cleaning 2 2023-11-06 02:10:07 +01:00
ZioPao
57862163f1 Merge branch 'master' of https://github.com/ZioPao/The-Only-Cure 2023-11-06 02:08:38 +01:00
ZioPao
de63163e77 Cleaning crap 2023-11-06 02:08:36 +01:00
362 changed files with 6958 additions and 6219 deletions

View File

@@ -1,33 +0,0 @@
name: Build Mod
on:
workflow_dispatch:
inputs:
mod_name:
description: 'Mod name'
default: 'TheOnlyCure'
required: true
version:
description: 'Version'
default: '1.0'
required: true
jobs:
main_job:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: mkdir -p root/${{ inputs.mod_name }}/Contents/mods/${{ inputs.mod_name }}
- run: cp workshop.txt root/${{ inputs.mod_name }}/workshop.txt
- run: cp preview.png root/${{ inputs.mod_name}}/preview.png
- run: cp mod.info root/${{ inputs.mod_name }}/Contents/mods/${{ inputs.mod_name }}/mod.info
- run: cp icon.png root/${{ inputs.mod_name }}/Contents/mods/${{ inputs.mod_name }}/icon.png
- run: cp generic.png root/${{ inputs.mod_name }}/Contents/mods/${{ inputs.mod_name }}/generic.png
- run: cp -r media root/${{ inputs.mod_name }}/Contents/mods/${{ inputs.mod_name }}/media
- uses: actions/upload-artifact@v3
with:
name: ${{ inputs.mod_name }}-${{ inputs.version }}
path: |
root
root/${{ inputs.mod_name }}
root/${{ inputs.mod_name }}/Contents/
root/${{ inputs.mod_name }}/Contents/mods
root/${{ inputs.mod_name }}/Contents/mods/${{ inputs.mod_name }}/*

1
.gitignore vendored
View File

@@ -0,0 +1 @@
.vscode

3
.idea/.gitignore generated vendored
View File

@@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="zdoc-lua" level="project" />
<orderEntry type="library" name="zomboid-sources" level="project" />
</component>
</module>

7
.idea/discord.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT" />
<option name="description" value="" />
</component>
</project>

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="zdoc-lua">
<CLASSES>
<root url="jar://$USER_HOME$/IdeaProjects/decompilation-zomboid/lib/zdoc-lua.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,11 +0,0 @@
<component name="libraryTable">
<library name="zomboid-sources">
<CLASSES>
<root url="jar://$USER_HOME$/IdeaProjects/decompilation-zomboid/lib/zomboid.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/IdeaProjects/decompilation-zomboid/lib/zomboid-sources.jar!/" />
</SOURCES>
</library>
</component>

6
.idea/misc.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_19" project-jdk-name="Python 3.11" project-jdk-type="Python SDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/The-Only-Cure-But-Better.iml" filepath="$PROJECT_DIR$/.idea/The-Only-Cure-But-Better.iml" />
</modules>
</component>
</project>

View File

@@ -1,17 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run Zomboid" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="false" />
<option name="SCRIPT_PATH" value="E:/Steam/steamapps/common/ProjectZomboid/ProjectZomboid64 - nosteam-debug.bat" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs />
<method v="2" />
</configuration>
</component>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

65
.vscode/settings.json vendored
View File

@@ -1,64 +1,11 @@
{
"todo-tree.tree.scanMode": "workspace",
"zomboid_user_folder": "C:/Users/picch/Zomboid",
"zomboid_folder": "E:\\Steam\\steamapps\\common\\ProjectZomboid",
"zomboid_server_folder": "E:\\Steam\\steamapps\\common\\Project Zomboid Dedicated Server",
"Lua.diagnostics.globals": [
"ZombRand",
"getPlayer",
"BodyPartType",
"ProceduralDistributions",
"Events",
"getText",
"Perks",
"getPlayerByOnlineID",
"getTexture",
"ISTimedActionQueue",
"sendClientCommand",
"ISBaseTimedAction",
"instanceof",
"getPlayerInventory",
"sendServerCommand",
"TraitFactory",
"ISWorldObjectContextMenu",
"getCell",
"getSpecificPlayer",
"_",
"NewUI",
"getTextManager",
"isClient",
"ISHealthPanel",
"ModOptions",
"ISNewHealthPanel",
"ISButton",
"getCore",
"ProfessionFactory",
"BaseGameCharacterDetails",
"MoodleType",
"ISEquipWeaponAction",
"triggerEvent",
"forceDropHeavyItems",
"getPlayerHotbar",
"isServer",
"BodyLocations",
"ISUnequipAction",
"ISInventoryPaneContextMenu",
"ISDropItemAction",
"BloodBodyPartType",
"ISInventoryPane",
"ModData",
"isDebugEnabled",
"getActivatedMods",
"ISHotbar",
"isForceDropHeavyItem",
"isFHModKeyDown",
"getPlayerData",
"FHSwapHandsAction",
"getClassFieldVal",
"SandboxVars",
"getClassField",
"ISWearClothing",
"SyncXp",
"ISClothingExtraAction",
"SwapItConfig",
"getTimestamp",
"addSound"
]
"zombie",
"_"
],
}

40
.vscode/tasks.json vendored
View File

@@ -3,29 +3,59 @@
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Create Workshop folder",
"type": "shell",
"options": {"statusbar": {"label": "$(combine) Assemble Mod"}},
"command": "python ${config:zomboid_user_folder}/PaosCrap/make_workshop_pack.py picch ${workspaceFolderBasename}",
},
{
"label": "Run Zomboid Debug No Steam",
"type": "shell",
"command": "\"E:\\Steam\\steamapps\\common\\ProjectZomboid\\ProjectZomboid64 - nosteam-debug.bat\"",
"presentation": {
"group": "groupZomboid"
},
"command": "\"${config:zomboid_folder}\\ProjectZomboid64 - nosteam-debug.bat\"",
"options": {"statusbar": {"label": "$(run) Zomboid client"}},
"problemMatcher": [
"$eslint-stylish"
]
},
{
"label": "Run Zomboid Debug No Steam 2",
"type": "shell",
"command": "\"${config:zomboid_folder}\\ProjectZomboid64 - nosteam-debug.bat\"",
"options": {"statusbar": {"hide": true}},
"problemMatcher": [
"$eslint-stylish"
]
},
{
"label": "Run two instances of Zomboid Debug No Steam",
"options": {"statusbar": {"label": "$(run-all) Two Zomboid Clients"}},
"presentation": {
"reveal": "always",
"panel": "new"
},
"dependsOn": [
"Run Zomboid Debug No Steam",
"Run Zomboid Debug No Steam"
],
"Run Zomboid Debug No Steam", "Run Zomboid Debug No Steam 2"],
"problemMatcher": []
},
{
"label": "Run Zomboid Test Server",
"options": {"statusbar": {"label": "$(run) Zomboid Server (TOC)"}},
"type": "shell",
"command": "\"E:\\Steam\\steamapps\\common\\Project Zomboid Dedicated Server\\StartServer64_nosteam.bat\"",
"command":"\"${config:zomboid_server_folder}\\StartServer64_nosteam_custom.bat\" TOC",
"problemMatcher": [
"$eslint-stylish"
]
},
{
"label": "Run Zomboid Test Server 2",
"options": {"statusbar": {"label": "$(run) Zomboid Server (TOC+FH+BH)"}},
"type": "shell",
"command":"\"${config:zomboid_server_folder}\\StartServer64_nosteam_custom.bat\" TOC_FH_BH",
"problemMatcher": [
"$eslint-stylish"
]

674
LICENSE Normal file
View File

@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@@ -1,4 +1,63 @@
# Dev Version
Version: 1.0
Workshop ID: 2915572347
Mod ID: Amputation2
<p align='center'>
<img src="/dev_stuff/logos/title.png" width=50% height=50%>
</p>
<p align='center'>
<a href='https://steamcommunity.com/sharedfiles/filedetails/?id=3236152598'>
<img src='https://img.shields.io/badge/Steam-000000?style=for-the-badge&logo=steam&logoColor=white' />
</a>
</p>
You're bitten. You have two choices.
Wait until you succumb to the virus or take matters into your hands. Cut off that infected part and live to die another day.
This version of **The Only Cure** has been rebuilt from scratch to support future additions and to feel as close as possible as a vanilla mechanic.
**The older version will be delisted shortly and it will not be supported anymore.**
Supports **Single Player** and **Multiplayer**!
# Setup
Use it with the following mods for the intended experience:
- [Fancy Handwork](https://steamcommunity.com/sharedfiles/filedetails/?id=2904920097)
- [Brutal Handwork](https://steamcommunity.com/sharedfiles/filedetails/?id=2934621024)
# Quick guide
## Amputation
Get a _Saw_ or a _Garden Saw_, right click on it, and choose which limb to amputate. You can also drag n' drop your Saw item directly into the afflicted area to start cutting it off.
If you have some _bandages_ and\or _stitches_ in your inventory you will automatically use them, multiplying the chances of your survival.
If you have a _tourniquet_, place it on the correct side to dampen the amount of damage you will take after you're done amputating the limb.
Keep in mind that if you amputate your **upper arm**, you won't be able to equip any prosthesis.
After you've amputated a limb, you will gain skill points for the amputated side, making timed actions faster in due time.
## Cicatrization
You can check the cicatrization status of an amputated limb from your **Health Panel**.
From time to time, you should clean your wounds with a bandage to help the cicatrization process.
If your limb isn't completely cicatrized, you can still equip prosthetic limbs, but that can trigger random bleedings from that area.
## Prosthesis
If you're missing a hand, you won't be able to do a lot of things, such as equipping two-handed weapons. With prosthetics limbs, you can fix that.
There are two prosthesis type that can be crafted\found.
- Hook Prosthesis
- Arm Prosthesis
The main difference between the twos is that your actions will take longer with the Hook Prosthesis.
When you equip a prosthetic limb, you will slowly gain familiarity with it, making actions more speedy in due time.
## Admin tools
If something strange happened, an admin can reset TOC mechanics on any player by right clicking on them and select _"Reset Amputations"_. They could also do the opposite by clicking on _"Force Amputation"_ for each amputable limb.
# Credits
| | |
| ------------- | ------------- |
| Pao | Developer |
| Mr. Bounty | Original developer |
| Chuckleberry Finn | Logo and Icon |
| dhert | Compatibility API |
| The Zomboid community as a whole | A lot of little things |

View File

@@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}

View File

@@ -1,19 +0,0 @@
1) Can cut every limb in SP
2) Does cutting a limb cure infection
3) If I get bit in the hand, will cutting the forearm cure the infection?
4) Both forearm bit, cut everyone, infection cured?
5) Can I operate myself?
6) Can I equip a prost?
7) Can i unequip a prost?
MP TEST
1) Everything up there but in mp env
--------------------------
- traits are kinda broken, cant reset them

View File

@@ -0,0 +1,124 @@
<mxfile host="app.diagrams.net" modified="2023-11-06T08:39:21.951Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0" version="22.0.8" etag="ddG-F5xvL5OceRP-7Ajf" type="github">
<diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
<mxGraphModel dx="1223" dy="871" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-0" />
<mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0" />
<mxCell id="zkfFHV4jXpPFQw0GAbJ--0" value="Person" style="swimlane;fontStyle=2;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeLast=0;collapsible=1;marginBottom=0;rounded=0;shadow=0;strokeWidth=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="220" y="120" width="160" height="138" as="geometry">
<mxRectangle x="230" y="140" width="160" height="26" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--1" value="Name" style="text;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="zkfFHV4jXpPFQw0GAbJ--0" vertex="1">
<mxGeometry y="26" width="160" height="26" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--2" value="Phone Number" style="text;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rounded=0;shadow=0;html=0;" parent="zkfFHV4jXpPFQw0GAbJ--0" vertex="1">
<mxGeometry y="52" width="160" height="26" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--3" value="Email Address" style="text;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rounded=0;shadow=0;html=0;" parent="zkfFHV4jXpPFQw0GAbJ--0" vertex="1">
<mxGeometry y="78" width="160" height="26" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--4" value="" style="line;html=1;strokeWidth=1;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" parent="zkfFHV4jXpPFQw0GAbJ--0" vertex="1">
<mxGeometry y="104" width="160" height="8" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--5" value="Purchase Parking Pass" style="text;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="zkfFHV4jXpPFQw0GAbJ--0" vertex="1">
<mxGeometry y="112" width="160" height="26" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--6" value="Student" style="swimlane;fontStyle=0;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeLast=0;collapsible=1;marginBottom=0;rounded=0;shadow=0;strokeWidth=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="120" y="360" width="160" height="138" as="geometry">
<mxRectangle x="130" y="380" width="160" height="26" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--7" value="Student Number" style="text;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="zkfFHV4jXpPFQw0GAbJ--6" vertex="1">
<mxGeometry y="26" width="160" height="26" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--8" value="Average Mark" style="text;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rounded=0;shadow=0;html=0;" parent="zkfFHV4jXpPFQw0GAbJ--6" vertex="1">
<mxGeometry y="52" width="160" height="26" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--9" value="" style="line;html=1;strokeWidth=1;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" parent="zkfFHV4jXpPFQw0GAbJ--6" vertex="1">
<mxGeometry y="78" width="160" height="8" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--10" value="Is Eligible To Enroll" style="text;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontStyle=4" parent="zkfFHV4jXpPFQw0GAbJ--6" vertex="1">
<mxGeometry y="86" width="160" height="26" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--11" value="Get Seminars Taken" style="text;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="zkfFHV4jXpPFQw0GAbJ--6" vertex="1">
<mxGeometry y="112" width="160" height="26" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--12" value="" style="endArrow=block;endSize=10;endFill=0;shadow=0;strokeWidth=1;rounded=0;edgeStyle=elbowEdgeStyle;elbow=vertical;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="zkfFHV4jXpPFQw0GAbJ--6" target="zkfFHV4jXpPFQw0GAbJ--0" edge="1">
<mxGeometry width="160" relative="1" as="geometry">
<mxPoint x="200" y="203" as="sourcePoint" />
<mxPoint x="200" y="203" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--13" value="Professor" style="swimlane;fontStyle=0;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeLast=0;collapsible=1;marginBottom=0;rounded=0;shadow=0;strokeWidth=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="330" y="360" width="160" height="70" as="geometry">
<mxRectangle x="340" y="380" width="170" height="26" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--14" value="Salary" style="text;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="zkfFHV4jXpPFQw0GAbJ--13" vertex="1">
<mxGeometry y="26" width="160" height="26" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--15" value="" style="line;html=1;strokeWidth=1;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" parent="zkfFHV4jXpPFQw0GAbJ--13" vertex="1">
<mxGeometry y="52" width="160" height="8" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--16" value="" style="endArrow=block;endSize=10;endFill=0;shadow=0;strokeWidth=1;rounded=0;edgeStyle=elbowEdgeStyle;elbow=vertical;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="zkfFHV4jXpPFQw0GAbJ--13" target="zkfFHV4jXpPFQw0GAbJ--0" edge="1">
<mxGeometry width="160" relative="1" as="geometry">
<mxPoint x="210" y="373" as="sourcePoint" />
<mxPoint x="310" y="271" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--17" value="Address" style="swimlane;fontStyle=0;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeLast=0;collapsible=1;marginBottom=0;rounded=0;shadow=0;strokeWidth=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="508" y="120" width="160" height="216" as="geometry">
<mxRectangle x="550" y="140" width="160" height="26" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--18" value="Street" style="text;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="zkfFHV4jXpPFQw0GAbJ--17" vertex="1">
<mxGeometry y="26" width="160" height="26" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--19" value="City" style="text;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rounded=0;shadow=0;html=0;" parent="zkfFHV4jXpPFQw0GAbJ--17" vertex="1">
<mxGeometry y="52" width="160" height="26" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--20" value="State" style="text;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rounded=0;shadow=0;html=0;" parent="zkfFHV4jXpPFQw0GAbJ--17" vertex="1">
<mxGeometry y="78" width="160" height="26" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--21" value="Postal Code" style="text;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rounded=0;shadow=0;html=0;" parent="zkfFHV4jXpPFQw0GAbJ--17" vertex="1">
<mxGeometry y="104" width="160" height="26" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--22" value="Country" style="text;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rounded=0;shadow=0;html=0;" parent="zkfFHV4jXpPFQw0GAbJ--17" vertex="1">
<mxGeometry y="130" width="160" height="26" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--23" value="" style="line;html=1;strokeWidth=1;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" parent="zkfFHV4jXpPFQw0GAbJ--17" vertex="1">
<mxGeometry y="156" width="160" height="8" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--24" value="Validate" style="text;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="zkfFHV4jXpPFQw0GAbJ--17" vertex="1">
<mxGeometry y="164" width="160" height="26" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--25" value="Output As Label" style="text;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;" parent="zkfFHV4jXpPFQw0GAbJ--17" vertex="1">
<mxGeometry y="190" width="160" height="26" as="geometry" />
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--26" value="" style="endArrow=open;shadow=0;strokeWidth=1;rounded=0;endFill=1;edgeStyle=elbowEdgeStyle;elbow=vertical;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="zkfFHV4jXpPFQw0GAbJ--0" target="zkfFHV4jXpPFQw0GAbJ--17" edge="1">
<mxGeometry x="0.5" y="41" relative="1" as="geometry">
<mxPoint x="380" y="192" as="sourcePoint" />
<mxPoint x="540" y="192" as="targetPoint" />
<mxPoint x="-40" y="32" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--27" value="0..1" style="resizable=0;align=left;verticalAlign=bottom;labelBackgroundColor=none;fontSize=12;" parent="zkfFHV4jXpPFQw0GAbJ--26" connectable="0" vertex="1">
<mxGeometry x="-1" relative="1" as="geometry">
<mxPoint y="4" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--28" value="1" style="resizable=0;align=right;verticalAlign=bottom;labelBackgroundColor=none;fontSize=12;" parent="zkfFHV4jXpPFQw0GAbJ--26" connectable="0" vertex="1">
<mxGeometry x="1" relative="1" as="geometry">
<mxPoint x="-7" y="4" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="zkfFHV4jXpPFQw0GAbJ--29" value="lives at" style="text;html=1;resizable=0;points=[];;align=center;verticalAlign=middle;labelBackgroundColor=none;rounded=0;shadow=0;strokeWidth=1;fontSize=12;" parent="zkfFHV4jXpPFQw0GAbJ--26" vertex="1" connectable="0">
<mxGeometry x="0.5" y="49" relative="1" as="geometry">
<mxPoint x="-38" y="40" as="offset" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 KiB

BIN
dev_stuff/logos/test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

BIN
dev_stuff/logos/title.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

View File

@@ -0,0 +1,54 @@
https://steamcommunity.com/sharedfiles/filedetails/?id=3236152598
[img]https://i.imgur.com/PctsskC.png[/img]
[h1]Supports B41+. Compatible with SP and MP[/h1]
[h2]Made by [b]Mr. Bounty[/b] and maintained by [b]Pao[/b][/h2]
[img]https://i.imgur.com/RNxXaZ8.gif[/img]
[img]https://i.imgur.com/C6FcUgm.png[/img]
[h1] An update will come "soon". The work in progress version is available on GitHub [url=https://github.com/ZioPao/The-Only-Cure]here[/url].[/h1]
[img]https://i.imgur.com/koMbiql.png[/img]
[list]
[*]Amputate different parts of your arm to prevent an infection
[*]Cicatrization process and surgery after amputation
[*]Prosthesis for cut limbs up to the forearm
[*]Debug cheats (Multiplayer compatible)
[/list]
[img]https://i.imgur.com/BOj0YGn.png[/img]
You can find a concise tutorial about the mod [url=https://github.com/ZioPao/The-Only-Cure/wiki]here[/url]. The wiki will be updated whenever we add some new feature that needs to be explained.
[img]https://i.imgur.com/RGF3ZTF.png[/img]
[h2]Required mods[/h2]
[list]
[*][url=https://steamcommunity.com/workshop/filedetails/?id=2760035814]Simple UI library[/url]
[/list]
[h2]Recommended mods[/h2]
[list]
[*][url=https://steamcommunity.com/sharedfiles/filedetails/?id=2904920097]Fancy Handwork[/url]
[*][url=https://steamcommunity.com/sharedfiles/filedetails/?id=2934621024]Brutal Handwork[/url]
[/list]
[h2]Compatible mods[/h2]
[list]
[*][url=https://steamcommunity.com/sharedfiles/filedetails/?id=2366717227]Swap It[/url]
[/list]
[h2]Incompatible mods[/h2]
[list]
[*][url=https://steamcommunity.com/workshop/filedetails/?id=2629286881]Military Ponchos[/url] (Amputation sometimes won't show)
[*][url=https://steamcommunity.com/sharedfiles/filedetails/?id=2915572347]zRe Bandaged Status[/url] (TOC button in the health panel menu won't shown)
[/list]
[hr]
Workshop ID: 2703664356
Mod ID: Amputation

View File

@@ -5,6 +5,11 @@ import openpyxl
import os
#### ITEMS FORMAT SHOULD BE
# Prost_Something_HookArm_L
os.chdir(os.getcwd() + "\\dev_stuff\\python_helpers\\")

View File

@@ -97,3 +97,102 @@ item LeatherBase_MetalHand
CanHaveHoles = false,
WorldStaticModel = TOC.MetalHook,
}
item WoodenBase_WoodenHook
{
Weight = 0.90,
Type = Normal,
DisplayCategory = Prosthesis,
DisplayName = Prosthesis - Wooden Base and Wooden Hook,
Icon = metalLeg,
Tooltip = TempTooltip,
CanHaveHoles = false,
WorldStaticModel = TOC.MetalHook,
}
item WoodenBase_MetalHook
{
Weight = 1.20,
Type = Normal,
DisplayCategory = Prosthesis,
DisplayName = Prosthesis - Wooden Base and Metal Hook,
Icon = metalLeg,
Tooltip = TempTooltip,
CanHaveHoles = false,
WorldStaticModel = TOC.MetalHook,
}
item WoodenBase_MetalHand
{
Weight = 1.40,
Type = Normal,
DisplayCategory = Prosthesis,
DisplayName = Prosthesis - Wooden Base and Metal Hand,
Icon = metalLeg,
Tooltip = TempTooltip,
CanHaveHoles = false,
WorldStaticModel = TOC.MetalHook,
}
item MetalBase_WoodenHook
{
Weight = 1.40,
Type = Normal,
DisplayCategory = Prosthesis,
DisplayName = Prosthesis - Metal Base and Wooden Hook,
Icon = metalLeg,
Tooltip = TempTooltip,
CanHaveHoles = false,
WorldStaticModel = TOC.MetalHook,
}
item MetalBase_MetalHook
{
Weight = 1.70,
Type = Normal,
DisplayCategory = Prosthesis,
DisplayName = Prosthesis - Metal Base and Metal Hook,
Icon = metalLeg,
Tooltip = TempTooltip,
CanHaveHoles = false,
WorldStaticModel = TOC.MetalHook,
}
item MetalBase_MetalHand
{
Weight = 1.90,
Type = Normal,
DisplayCategory = Prosthesis,
DisplayName = Prosthesis - Metal Base and Metal Hand,
Icon = metalLeg,
Tooltip = TempTooltip,
CanHaveHoles = false,
WorldStaticModel = TOC.MetalHook,
}
item LeatherBase_WoodenHook
{
Weight = 1.20,
Type = Normal,
DisplayCategory = Prosthesis,
DisplayName = Prosthesis - Leather Base and Wooden Hook,
Icon = metalLeg,
Tooltip = TempTooltip,
CanHaveHoles = false,
WorldStaticModel = TOC.MetalHook,
}
item LeatherBase_MetalHook
{
Weight = 1.50,
Type = Normal,
DisplayCategory = Prosthesis,
DisplayName = Prosthesis - Leather Base and Metal Hook,
Icon = metalLeg,
Tooltip = TempTooltip,
CanHaveHoles = false,
WorldStaticModel = TOC.MetalHook,
}
item LeatherBase_MetalHand
{
Weight = 1.70,
Type = Normal,
DisplayCategory = Prosthesis,
DisplayName = Prosthesis - Leather Base and Metal Hand,
Icon = metalLeg,
Tooltip = TempTooltip,
CanHaveHoles = false,
WorldStaticModel = TOC.MetalHook,
}

84
dev_stuff/steam_desc.txt Normal file
View File

@@ -0,0 +1,84 @@
[img]https://raw.githubusercontent.com/ZioPao/The-Only-Cure/551125bb50cb65608ad89ca81ef0daccb3b02c4c/dev_stuff/logos/title.png[/img]
[h1]You're bitten. You have two choices.[/h1]
Wait until you succumb to the virus or take matters into your hands. Cut off that infected part and live to die another day.
This version of [b]The Only Cure[/b] has been rebuilt from scratch to support future additions and to feel as close as possible as a vanilla mechanic.
[b]The older version will be delisted shortly and it will not be supported anymore.[/b]
Supports [b]Single Player[/b] and [b]Multiplayer[/b]!
[h1]Setup[/h1]
Use it with the following mods for the intended experience:
[list]
[*] [url=https://steamcommunity.com/sharedfiles/filedetails/?id=2904920097]Fancy Handwork[/url]
[*] [url=https://steamcommunity.com/sharedfiles/filedetails/?id=2934621024]Brutal Handwork[/url]
[/list]
[hr][/hr]
[h1]Quick guide[/h1]
[h2]Amputation[/h2]
Get a [i]Saw[/i] or a [i]Garden Saw[/i], right click on it, and choose which limb to amputate. You can also drag n' drop your Saw item directly into the afflicted area to start cutting it off.
If you have some [i]bandages[/i] and\or [i]stitches[/i] in your inventory you will automatically use them, multiplying the chances of your survival.
If you have a [i]tourniquet[/i], place it on the correct side to dampen the amount of damage you will take after you're done amputating the limb.
Keep in mind that if you amputate your [b]upper arm[/b], you won't be able to equip any prosthesis.
After you've amputated a limb, you will gain [b]skill points[/b] for the amputated side, making timed actions faster in due time.
[h2]Cicatrization[/h2]
You can check the cicatrization status of an amputated limb from your [b]Health Panel[/b]
From time to time, you should clean your wounds with a bandage to help the cicatrization process.
If your limb isn't completely cicatrized, you can still equip prosthetic limbs, but that can trigger random bleedings from that area.
[h2]Prosthetics[/h2]
If you're missing a hand, you won't be able to do a lot of things, such as equipping two-handed weapons. With prosthetics limbs, you can fix that.
There are two prosthesis type that can be crafted\found in medical areas:
[list]
[*] Hook Prosthesis
[*] Arm Prosthesis
[/list]
The main difference between the twos is that your actions will take longer with the Hook Prosthesis.
When you equip a prosthetic limb, you will slowly gain skill points in the [b]Prosthesis Familiarity[/b] perk, making actions more speedy in due time.
[h2]Admin tools[/h2]
If something strange happened, an admin can reset TOC mechanics on any player by right clicking on them and select [i]"Reset Amputations"[/i]. They could also do the opposite by clicking on [i]"Force Amputation"[/i] for each amputable limb.
[hr][/hr]
[h1]Issues and bugs?[/h1]
Got any issues or found some pesky bugs? Report them on GitHub!
[url=https://github.com/ZioPao/The-Only-Cure][img]https://img.shields.io/badge/GitHub-100000?style=for-the-badge&logo=github&logoColor=white[/img][/url]
[h1]Credits[/h1]
[table]
[tr]
[th]Pao[/th]
[th]Developer[/th]
[/tr]
[tr]
[th]Mr. Bounty[/th]
[th]Original Developer[/th]
[/tr]
[tr]
[th]Chuckleberry Finn[/th]
[th]Logo and Icon[/th]
[/tr]
[tr]
[th]dhert[/th]
[th]Compatibility API[/th]
[/tr]
[/table]
[hr][/hr]
[h1]Want to support me?[/h1]
[url=https://ko-fi.com/M4M7IERNW][img]https://storage.ko-fi.com/cdn/kofi3.png[/img][/url]
[hr][/hr]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<animNode>
<m_Name>aim_default</m_Name>
<m_AnimName>Bob_Sit_FishingIdle</m_AnimName>
<m_Priority>4</m_Priority>
<m_deferredBoneAxis>Y</m_deferredBoneAxis>
<m_SyncTrackingEnabled>false</m_SyncTrackingEnabled>
<m_SpeedScale>IdleSpeed</m_SpeedScale>
<m_BlendTime>0.30</m_BlendTime>
<m_maxTorsoTwist>70.0</m_maxTorsoTwist>
<m_Conditions>
<m_Name>IsCrawling</m_Name>
<m_Type>BOOL</m_Type>
<m_BoolValue>true</m_BoolValue>
</m_Conditions>
<m_Transitions>
<m_Target>Idle</m_Target>
<m_AnimName>Bob_Sit_FishingIdle</m_AnimName>
<m_blendInTime>0.3</m_blendInTime>
<m_blendOutTime>0.3</m_blendOutTime>
<m_speedScale>1.2</m_speedScale>
</m_Transitions>
<m_Transitions>
<m_Target>sneakIdle</m_Target>
<m_AnimName>Bob_Sit_FishingIdle</m_AnimName>
<m_blendInTime>0.3</m_blendInTime>
<m_blendOutTime>0.3</m_blendOutTime>
<m_speedScale>1.2</m_speedScale>
</m_Transitions>
<m_Transitions>
<m_Target>sneakIdleLow</m_Target>
<m_AnimName>Bob_Sit_FishingIdle</m_AnimName>
<m_blendInTime>0.3</m_blendInTime>
<m_blendOutTime>0.3</m_blendOutTime>
<m_speedScale>1.2</m_speedScale>
</m_Transitions>
</animNode>

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<animNode>
<m_Name>NoLegs_Idle</m_Name>
<m_AnimName>Bob_Sit_FishingIdle</m_AnimName>
<m_DeferredBoneName>Translation_Data</m_DeferredBoneName>
<m_deferredBoneAxis>Y</m_deferredBoneAxis>
<m_Looped>true</m_Looped>
<m_SpeedScale>0.8</m_SpeedScale>
<m_BlendTime>0.20</m_BlendTime>
<m_Conditions>
<m_Name>IsCrawling</m_Name>
<m_Type>BOOL</m_Type>
<m_BoolValue>true</m_BoolValue>
</m_Conditions>
<m_Transitions>
<m_Target>NoLegs_IdleSneak</m_Target>
<m_blendInTime>0.1</m_blendInTime>
</m_Transitions>
<m_Transitions>
<m_Target>NoLegs_Idle</m_Target>
<m_blendInTime>0.35</m_blendInTime>
<m_blendOutTime>0.35</m_blendOutTime>
</m_Transitions>
</animNode>

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<animNode>
<m_Name>NoLegs_IdleSneak</m_Name>
<m_AnimName>Bob_ScrambleFloorIdle</m_AnimName>
<m_Looped>true</m_Looped>
<m_deferredBoneAxis>Y</m_deferredBoneAxis>
<m_SyncTrackingEnabled>false</m_SyncTrackingEnabled>
<m_EarlyTransitionOut>true</m_EarlyTransitionOut>
<m_SpeedScale>0.8</m_SpeedScale>
<m_BlendTime>0.20</m_BlendTime>
<m_Conditions>
<m_Name>IsCrawling</m_Name>
<m_Type>BOOL</m_Type>
<m_BoolValue>true</m_BoolValue>
</m_Conditions>
<m_Conditions>
<m_Name>sneaking</m_Name>
<m_Type>BOOL</m_Type>
<m_BoolValue>true</m_BoolValue>
</m_Conditions>
</animNode>

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<animNode>
<m_Name>NoLegs_Walk</m_Name>
<m_AnimName>Bob_Crawl</m_AnimName>
<m_deferredBoneAxis>Y</m_deferredBoneAxis>
<m_Looped>true</m_Looped>
<m_SpeedScale>1.5</m_SpeedScale>
<m_BlendTime>0.20</m_BlendTime>
<m_Conditions>
<m_Name>IsCrawling</m_Name>
<m_Type>BOOL</m_Type>
<m_BoolValue>true</m_BoolValue>
</m_Conditions>
<m_Events>
<m_EventName>Footstep</m_EventName>
<m_TimePc>0.15</m_TimePc>
<m_ParameterValue>walk</m_ParameterValue>
</m_Events>
<m_Events>
<m_EventName>Footstep</m_EventName>
<m_TimePc>0.6</m_TimePc>
<m_ParameterValue>walk</m_ParameterValue>
</m_Events>
</animNode>

View File

@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<animNode>
<m_Name>NoLegs_WalkSneak</m_Name>
<m_AnimName>Zombie_CrawlUnder</m_AnimName>
<m_DeferredBoneName>Translation_Data</m_DeferredBoneName>
<m_Looped>true</m_Looped>
<m_SpeedScale>1.5</m_SpeedScale>
<m_BlendTime>0.20</m_BlendTime>
<m_Conditions>
<m_Name>IsCrawling</m_Name>
<m_Type>BOOL</m_Type>
<m_BoolValue>true</m_BoolValue>
</m_Conditions>
<m_Conditions>
<m_Name>sneaking</m_Name>
<m_Type>BOOL</m_Type>
<m_BoolValue>true</m_BoolValue>
</m_Conditions>
<m_Conditions>
<m_Name>inTrees</m_Name>
<m_Type>BOOL</m_Type>
<m_BoolValue>false</m_BoolValue>
</m_Conditions>
<m_Events>
<m_EventName>Footstep</m_EventName>
<m_TimePc>0.15</m_TimePc>
<m_ParameterValue>sneak_walk</m_ParameterValue>
</m_Events>
<m_Events>
<m_EventName>Footstep</m_EventName>
<m_TimePc>0.6</m_TimePc>
<m_ParameterValue>sneak_walk</m_ParameterValue>
</m_Events>
</animNode>

View File

@@ -1,61 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<animNode>
<m_Name>NoLegs_Turn</m_Name>
<m_DeferredBoneName>Bip01</m_DeferredBoneName>
<m_deferredBoneAxis>Y</m_deferredBoneAxis>
<m_useDeferedRotation>true</m_useDeferedRotation>
<m_SpeedScale>0.80</m_SpeedScale>
<m_BlendTime>0.10</m_BlendTime>
<m_BlendOutTime>0.20</m_BlendOutTime>
<m_Conditions>
<m_Name>IsCrawling</m_Name>
<m_Type>BOOL</m_Type>
<m_BoolValue>true</m_BoolValue>
</m_Conditions>
<m_Conditions>
<m_Name>isTurning</m_Name>
<m_Type>BOOL</m_Type>
<m_BoolValue>true</m_BoolValue>
</m_Conditions>
<m_SubStateBoneWeights>
<boneName>Bip01_Pelvis</boneName>
<includeDescendants>false</includeDescendants>
</m_SubStateBoneWeights>
<m_SubStateBoneWeights>
<boneName>Bip01_Spine</boneName>
<includeDescendants>false</includeDescendants>
</m_SubStateBoneWeights>
<m_SubStateBoneWeights>
<boneName>Bip01_BackPack</boneName>
</m_SubStateBoneWeights>
<m_SubStateBoneWeights>
<boneName>Bip01_DressFront</boneName>
</m_SubStateBoneWeights>
<m_SubStateBoneWeights>
<boneName>Bip01_DressBack</boneName>
</m_SubStateBoneWeights>
<m_SubStateBoneWeights>
<boneName>Bip01_L_Thigh</boneName>
</m_SubStateBoneWeights>
<m_SubStateBoneWeights>
<boneName>Bip01_R_Thigh</boneName>
</m_SubStateBoneWeights>
<m_SubStateBoneWeights>
<boneName>Bip01</boneName>
<includeDescendants>false</includeDescendants>
</m_SubStateBoneWeights>
<m_SubStateBoneWeights>
<boneName>Translation_Data</boneName>
<includeDescendants>false</includeDescendants>
</m_SubStateBoneWeights>
<m_SubStateBoneWeights>
<boneName>Bip01_Prop1</boneName>
<weight>0.00</weight>
</m_SubStateBoneWeights>
<m_SubStateBoneWeights>
<boneName>Bip01_Prop2</boneName>
<weight>0.00</weight>
</m_SubStateBoneWeights>
</animNode>

View File

@@ -1,45 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<animNode x_extends="NoLegs_Turn.xml">
<m_Name>NoLegs_TurnIdle</m_Name>
<m_Looped>false</m_Looped>
<m_EarlyTransitionOut>true</m_EarlyTransitionOut>
<m_BlendOutTime>0.10</m_BlendOutTime>
<m_Conditions />
<m_Conditions>
<m_Name>IsCrawling</m_Name>
<m_Type>BOOL</m_Type>
<m_BoolValue>true</m_BoolValue>
</m_Conditions>
<m_Conditions>
<m_Name>isMoving</m_Name>
<m_Type>BOOL</m_Type>
<m_BoolValue>false</m_BoolValue>
</m_Conditions>
<m_Conditions>
<m_Name>Aim</m_Name>
<m_Type>BOOL</m_Type>
<m_BoolValue>false</m_BoolValue>
</m_Conditions>
<m_Transitions>
<m_Target>Idle</m_Target>
<m_blendInTime>0.1</m_blendInTime>
</m_Transitions>
<m_SubStateBoneWeights />
<m_SubStateBoneWeights>
<weight>0.25</weight>
</m_SubStateBoneWeights>
<m_SubStateBoneWeights>
<weight>0.20</weight>
</m_SubStateBoneWeights>
<m_SubStateBoneWeights />
<m_SubStateBoneWeights />
<m_SubStateBoneWeights />
<m_SubStateBoneWeights />
<m_SubStateBoneWeights />
<m_SubStateBoneWeights />
<m_SubStateBoneWeights />
<m_SubStateBoneWeights />
</animNode>

View File

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<animNode x_extends="NoLegs_TurnIdle.xml">
<m_Name>NoLegs_TurnIdle180</m_Name>
<m_Conditions />
<m_Conditions />
<m_Conditions>
<m_Name>IsCrawling</m_Name>
<m_Type>BOOL</m_Type>
<m_BoolValue>true</m_BoolValue>
</m_Conditions>
<m_Conditions>
<m_Name>isTurningAround</m_Name>
<m_Type>BOOL</m_Type>
<m_BoolValue>true</m_BoolValue>
</m_Conditions>
<m_SubStateBoneWeights />
<m_SubStateBoneWeights>
<weight>0.00</weight>
</m_SubStateBoneWeights>
<m_SubStateBoneWeights />
<m_SubStateBoneWeights />
<m_SubStateBoneWeights />
<m_SubStateBoneWeights />
<m_SubStateBoneWeights />
<m_SubStateBoneWeights />
<m_SubStateBoneWeights />
</animNode>

View File

@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<animNode x_extends="NoLegs_TurnIdle180.xml">
<m_Name>NoLegs_turnIdle180L</m_Name>
<m_AnimName>Bob_Crawl</m_AnimName>
<m_Conditions>
<m_Name>IsCrawling</m_Name>
<m_Type>BOOL</m_Type>
<m_BoolValue>true</m_BoolValue>
</m_Conditions>
<m_Conditions>
<m_Name>twist</m_Name>
<m_Type>LESS</m_Type>
<m_FloatValue>0</m_FloatValue>
</m_Conditions>
</animNode>

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<animNode x_extends="NoLegs_TurnIdle180.xml">
<m_Name>NoLegs_TurnIdle180R</m_Name>
<m_AnimName>Bob_EmoteWaveBye</m_AnimName>
<m_Conditions />
<m_Conditions />
<m_Conditions />
<m_Conditions>
<m_Name>IsCrawling</m_Name>
<m_Type>BOOL</m_Type>
<m_BoolValue>true</m_BoolValue>
</m_Conditions>
<m_Conditions>
<m_Name>twist</m_Name>
<m_Type>GTR</m_Type>
<m_FloatValue>0</m_FloatValue>
</m_Conditions>
</animNode>

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<clothingItem>
<m_MaleModel>Amputation\Amputation_Left_LowerArm_Male</m_MaleModel>
<m_FemaleModel>Amputation\Amputation_Left_LowerArm_Female</m_FemaleModel>
<m_GUID>d3816fe0-48e1-4cf5-a8e4-48c72595edb4</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomHue>false</m_AllowRandomHue>
<m_AllowRandomTint>false</m_AllowRandomTint>
<m_Masks>3</m_Masks>
<m_Masks>4</m_Masks>
<!-- HUMAN -->
<textureChoices>Amputations\Human\Forearm\skin01_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin02_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin03_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin04_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin05_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin01_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin02_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin03_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin04_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin05_hairy_b</textureChoices>
<!-- HUMAN AFTER CICATRIZATION -->
<textureChoices>Body\MaleBody01</textureChoices>
<textureChoices>Body\MaleBody02</textureChoices>
<textureChoices>Body\MaleBody03</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05</textureChoices>
<textureChoices>Body\MaleBody01a</textureChoices>
<textureChoices>Body\MaleBody02a</textureChoices>
<textureChoices>Body\MaleBody03a</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05a</textureChoices>
<!-- ZOMBIE -->
<textureChoices>Amputations\Zombie\Forearm\z_skin01_l1</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin01_l2</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin01_l3</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin02_l1</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin02_l2</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin02_l3</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin03_l1</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin03_l2</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin03_l3</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin04_l1</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin04_l2</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin04_l3</textureChoices>
</clothingItem>

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<clothingItem>
<m_MaleModel>Amputation\Amputation_Right_LowerArm_Male</m_MaleModel>
<m_FemaleModel>Amputation\Amputation_Right_LowerArm_Female</m_FemaleModel>
<m_GUID>e6f80efd-22e5-49e0-8b24-537519d42b37</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomHue>false</m_AllowRandomHue>
<m_AllowRandomTint>false</m_AllowRandomTint>
<m_Masks>5</m_Masks>
<m_Masks>6</m_Masks>
<!-- HUMAN -->
<textureChoices>Amputations\Human\Forearm\skin01_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin02_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin03_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin04_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin05_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin01_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin02_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin03_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin04_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin05_hairy_b</textureChoices>
<!-- HUMAN AFTER CICATRIZATION -->
<textureChoices>Body\MaleBody01</textureChoices>
<textureChoices>Body\MaleBody02</textureChoices>
<textureChoices>Body\MaleBody03</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05</textureChoices>
<textureChoices>Body\MaleBody01a</textureChoices>
<textureChoices>Body\MaleBody02a</textureChoices>
<textureChoices>Body\MaleBody03a</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05a</textureChoices>
<!-- ZOMBIE -->
<textureChoices>Amputations\Zombie\Forearm\z_skin01_l1</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin01_l2</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin01_l3</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin02_l1</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin02_l2</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin02_l3</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin03_l1</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin03_l2</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin03_l3</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin04_l1</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin04_l2</textureChoices>
<textureChoices>Amputations\Zombie\Forearm\z_skin04_l3</textureChoices>
</clothingItem>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<clothingItem>
<m_MaleModel>Amputation\Amputation_Left_Hand_Male</m_MaleModel>
<m_FemaleModel>Amputation\Amputation_Left_Hand_Female</m_FemaleModel>
<m_GUID>2de93af2-b7a8-4c04-84d1-28d92cce8a0f</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomHue>false</m_AllowRandomHue>
<m_AllowRandomTint>false</m_AllowRandomTint>
<m_Masks>4</m_Masks>
<m_MasksFolder>none</m_MasksFolder>
<!-- HUMAN -->
<textureChoices>Amputations\Human\Forearm\skin01_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin02_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin03_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin04_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin05_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin01_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin02_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin03_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin04_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin05_hairy_b</textureChoices>
</clothingItem>

View File

@@ -0,0 +1,23 @@
<clothingItem>
<m_MaleModel>Amputation\Amputation_Right_Hand_Male</m_MaleModel>
<m_FemaleModel>Amputation\Amputation_Right_Hand_Female</m_FemaleModel>
<m_GUID>f114e53a-b92e-4639-8d8c-2b43ab981885</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomHue>false</m_AllowRandomHue>
<m_AllowRandomTint>false</m_AllowRandomTint>
<m_Masks>6</m_Masks>
<m_MasksFolder>none</m_MasksFolder>
<!-- HUMAN -->
<textureChoices>Amputations\Human\Forearm\skin01_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin02_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin03_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin04_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin05_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin01_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin02_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin03_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin04_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Forearm\skin05_hairy_b</textureChoices>
</clothingItem>

View File

@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<clothingItem>
<m_MaleModel>Amputation\Amputation_GenericModel</m_MaleModel>
<m_FemaleModel>Amputation\Amputation_GenericModel</m_FemaleModel>
<m_GUID>506c0fc0-b50c-4667-bafa-ae22e3c2c0dc</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomHue>false</m_AllowRandomHue>
<m_AllowRandomTint>false</m_AllowRandomTint>
<m_Masks>8</m_Masks>
<m_MasksFolder>none</m_MasksFolder>
<textureChoices>Amputations\Forearm\skin01_b</textureChoices>
<textureChoices>Amputations\Forearm\skin02_b</textureChoices>
<textureChoices>Amputations\Forearm\skin03_b</textureChoices>
<textureChoices>Amputations\Forearm\skin04_b</textureChoices>
<textureChoices>Amputations\Forearm\skin05_b</textureChoices>
<textureChoices>Amputations\Forearm\skin01_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin02_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin03_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin04_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin05_hairy_b</textureChoices>
<textureChoices>Body\MaleBody01</textureChoices>
<textureChoices>Body\MaleBody02</textureChoices>
<textureChoices>Body\MaleBody03</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05</textureChoices>
<textureChoices>Body\MaleBody01a</textureChoices>
<textureChoices>Body\MaleBody02a</textureChoices>
<textureChoices>Body\MaleBody03a</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05a</textureChoices>
</clothingItem>

View File

@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<clothingItem>
<m_MaleModel>Amputation_Left_Hand_Male</m_MaleModel>
<m_FemaleModel>Amputation_Left_Hand_Female</m_FemaleModel>
<m_GUID>2de93af2-b7a8-4c04-84d1-28d92cce8a0f</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomHue>false</m_AllowRandomHue>
<m_AllowRandomTint>false</m_AllowRandomTint>
<m_Masks>4</m_Masks>
<m_MasksFolder>none</m_MasksFolder>
<textureChoices>Amputations\Forearm\skin01_b</textureChoices>
<textureChoices>Amputations\Forearm\skin02_b</textureChoices>
<textureChoices>Amputations\Forearm\skin03_b</textureChoices>
<textureChoices>Amputations\Forearm\skin04_b</textureChoices>
<textureChoices>Amputations\Forearm\skin05_b</textureChoices>
<textureChoices>Amputations\Forearm\skin01_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin02_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin03_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin04_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin05_hairy_b</textureChoices>
<textureChoices>Body\MaleBody01</textureChoices>
<textureChoices>Body\MaleBody02</textureChoices>
<textureChoices>Body\MaleBody03</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05</textureChoices>
<textureChoices>Body\MaleBody01a</textureChoices>
<textureChoices>Body\MaleBody02a</textureChoices>
<textureChoices>Body\MaleBody03a</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05a</textureChoices>
</clothingItem>

View File

@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<clothingItem>
<m_MaleModel>Amputation_Left_LowerArm_Male</m_MaleModel>
<m_FemaleModel>Amputation_Left_LowerArm_Female</m_FemaleModel>
<m_GUID>d3816fe0-48e1-4cf5-a8e4-48c72595edb4</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomHue>false</m_AllowRandomHue>
<m_AllowRandomTint>false</m_AllowRandomTint>
<m_Masks>3</m_Masks>
<m_Masks>4</m_Masks>
<m_MasksFolder>none</m_MasksFolder>
<textureChoices>Amputations\Forearm\skin01_b</textureChoices>
<textureChoices>Amputations\Forearm\skin02_b</textureChoices>
<textureChoices>Amputations\Forearm\skin03_b</textureChoices>
<textureChoices>Amputations\Forearm\skin04_b</textureChoices>
<textureChoices>Amputations\Forearm\skin05_b</textureChoices>
<textureChoices>Amputations\Forearm\skin01_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin02_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin03_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin04_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin05_hairy_b</textureChoices>
<textureChoices>Body\MaleBody01</textureChoices>
<textureChoices>Body\MaleBody02</textureChoices>
<textureChoices>Body\MaleBody03</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05</textureChoices>
<textureChoices>Body\MaleBody01a</textureChoices>
<textureChoices>Body\MaleBody02a</textureChoices>
<textureChoices>Body\MaleBody03a</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05a</textureChoices>
</clothingItem>

View File

@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<clothingItem>
<m_MaleModel>Amputation_Left_UpperArm_Male</m_MaleModel>
<m_FemaleModel>Amputation_Left_UpperArm_Female</m_FemaleModel>
<m_GUID>646cafa5-3fa1-41af-9ca0-aa57cca3b36d</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomHue>false</m_AllowRandomHue>
<m_AllowRandomTint>false</m_AllowRandomTint>
<m_Masks>3</m_Masks>
<m_Masks>4</m_Masks>
<m_MasksFolder>none</m_MasksFolder>
<textureChoices>Amputations\Upperarm\skin01_b</textureChoices>
<textureChoices>Amputations\Upperarm\skin02_b</textureChoices>
<textureChoices>Amputations\Upperarm\skin03_b</textureChoices>
<textureChoices>Amputations\Upperarm\skin04_b</textureChoices>
<textureChoices>Amputations\Upperarm\skin05_b</textureChoices>
<textureChoices>Amputations\Upperarm\skin01_hairy_b</textureChoices>
<textureChoices>Amputations\Upperarm\skin02_hairy_b</textureChoices>
<textureChoices>Amputations\Upperarm\skin03_hairy_b</textureChoices>
<textureChoices>Amputations\Upperarm\skin04_hairy_b</textureChoices>
<textureChoices>Amputations\Upperarm\skin05_hairy_b</textureChoices>
<textureChoices>Body\MaleBody01</textureChoices>
<textureChoices>Body\MaleBody02</textureChoices>
<textureChoices>Body\MaleBody03</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05</textureChoices>
<textureChoices>Body\MaleBody01a</textureChoices>
<textureChoices>Body\MaleBody02a</textureChoices>
<textureChoices>Body\MaleBody03a</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05a</textureChoices>
</clothingItem>

View File

@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<clothingItem>
<m_MaleModel>Amputation\Amputation_GenericModel</m_MaleModel>
<m_FemaleModel>Amputation\Amputation_GenericModel</m_FemaleModel>
<m_GUID>2600c2ab-bfeb-49c3-b0b5-e21c6d83d5c2</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomHue>false</m_AllowRandomHue>
<m_AllowRandomTint>false</m_AllowRandomTint>
<m_Masks>10</m_Masks>
<m_MasksFolder>none</m_MasksFolder>
<textureChoices>Amputations\Forearm\skin01_b</textureChoices>
<textureChoices>Amputations\Forearm\skin02_b</textureChoices>
<textureChoices>Amputations\Forearm\skin03_b</textureChoices>
<textureChoices>Amputations\Forearm\skin04_b</textureChoices>
<textureChoices>Amputations\Forearm\skin05_b</textureChoices>
<textureChoices>Amputations\Forearm\skin01_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin02_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin03_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin04_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin05_hairy_b</textureChoices>
<textureChoices>Body\MaleBody01</textureChoices>
<textureChoices>Body\MaleBody02</textureChoices>
<textureChoices>Body\MaleBody03</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05</textureChoices>
<textureChoices>Body\MaleBody01a</textureChoices>
<textureChoices>Body\MaleBody02a</textureChoices>
<textureChoices>Body\MaleBody03a</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05a</textureChoices>
</clothingItem>

View File

@@ -1,34 +0,0 @@
<clothingItem>
<m_MaleModel>Amputation_Right_Hand_Male</m_MaleModel>
<m_FemaleModel>Amputation_Right_Hand_Female</m_FemaleModel>
<m_GUID>f114e53a-b92e-4639-8d8c-2b43ab981885</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomHue>false</m_AllowRandomHue>
<m_AllowRandomTint>false</m_AllowRandomTint>
<m_Masks>6</m_Masks>
<m_MasksFolder>none</m_MasksFolder>
<textureChoices>Amputations\Forearm\skin01_b</textureChoices>
<textureChoices>Amputations\Forearm\skin02_b</textureChoices>
<textureChoices>Amputations\Forearm\skin03_b</textureChoices>
<textureChoices>Amputations\Forearm\skin04_b</textureChoices>
<textureChoices>Amputations\Forearm\skin05_b</textureChoices>
<textureChoices>Amputations\Forearm\skin01_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin02_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin03_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin04_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin05_hairy_b</textureChoices>
<textureChoices>Body\MaleBody01</textureChoices>
<textureChoices>Body\MaleBody02</textureChoices>
<textureChoices>Body\MaleBody03</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05</textureChoices>
<textureChoices>Body\MaleBody01a</textureChoices>
<textureChoices>Body\MaleBody02a</textureChoices>
<textureChoices>Body\MaleBody03a</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05a</textureChoices>
</clothingItem>

View File

@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<clothingItem>
<m_MaleModel>Amputation_Right_LowerArm_Male</m_MaleModel>
<m_FemaleModel>Amputation_Right_LowerArm_Female</m_FemaleModel>
<m_GUID>e6f80efd-22e5-49e0-8b24-537519d42b37</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomHue>false</m_AllowRandomHue>
<m_AllowRandomTint>false</m_AllowRandomTint>
<m_Masks>5</m_Masks>
<m_Masks>6</m_Masks>
<textureChoices>Amputations\Forearm\skin01_b</textureChoices>
<textureChoices>Amputations\Forearm\skin02_b</textureChoices>
<textureChoices>Amputations\Forearm\skin03_b</textureChoices>
<textureChoices>Amputations\Forearm\skin04_b</textureChoices>
<textureChoices>Amputations\Forearm\skin05_b</textureChoices>
<textureChoices>Amputations\Forearm\skin01_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin02_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin03_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin04_hairy_b</textureChoices>
<textureChoices>Amputations\Forearm\skin05_hairy_b</textureChoices>
<textureChoices>Body\MaleBody01</textureChoices>
<textureChoices>Body\MaleBody02</textureChoices>
<textureChoices>Body\MaleBody03</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05</textureChoices>
<textureChoices>Body\MaleBody01a</textureChoices>
<textureChoices>Body\MaleBody02a</textureChoices>
<textureChoices>Body\MaleBody03a</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05a</textureChoices>
</clothingItem>

View File

@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<clothingItem>
<m_MaleModel>Amputation_Right_UpperArm_Male</m_MaleModel>
<m_FemaleModel>Amputation_Right_UpperArm_Female</m_FemaleModel>
<m_GUID>db8ccad2-b76f-44bd-93ab-1eefa25beade</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomHue>false</m_AllowRandomHue>
<m_AllowRandomTint>false</m_AllowRandomTint>
<m_Masks>5</m_Masks>
<m_Masks>6</m_Masks>
<m_MasksFolder>none</m_MasksFolder>
<textureChoices>Amputations\Upperarm\skin01_b</textureChoices>
<textureChoices>Amputations\Upperarm\skin02_b</textureChoices>
<textureChoices>Amputations\Upperarm\skin03_b</textureChoices>
<textureChoices>Amputations\Upperarm\skin04_b</textureChoices>
<textureChoices>Amputations\Upperarm\skin05_b</textureChoices>
<textureChoices>Amputations\Upperarm\skin01_hairy_b</textureChoices>
<textureChoices>Amputations\Upperarm\skin02_hairy_b</textureChoices>
<textureChoices>Amputations\Upperarm\skin03_hairy_b</textureChoices>
<textureChoices>Amputations\Upperarm\skin04_hairy_b</textureChoices>
<textureChoices>Amputations\Upperarm\skin05_hairy_b</textureChoices>
<textureChoices>Body\MaleBody01</textureChoices>
<textureChoices>Body\MaleBody02</textureChoices>
<textureChoices>Body\MaleBody03</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05</textureChoices>
<textureChoices>Body\MaleBody01a</textureChoices>
<textureChoices>Body\MaleBody02a</textureChoices>
<textureChoices>Body\MaleBody03a</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05a</textureChoices>
</clothingItem>

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<clothingItem>
<m_MaleModel>Amputation\Amputation_Left_UpperArm_Male</m_MaleModel>
<m_FemaleModel>Amputation\Amputation_Left_UpperArm_Female</m_FemaleModel>
<m_GUID>646cafa5-3fa1-41af-9ca0-aa57cca3b36d</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomHue>false</m_AllowRandomHue>
<m_AllowRandomTint>false</m_AllowRandomTint>
<m_Masks>3</m_Masks>
<m_Masks>4</m_Masks>
<!-- HUMAN -->
<textureChoices>Amputations\Human\Upperarm\skin01_b</textureChoices>
<textureChoices>Amputations\Human\Upperarm\skin02_b</textureChoices>
<textureChoices>Amputations\Human\Upperarm\skin03_b</textureChoices>
<textureChoices>Amputations\Human\Upperarm\skin04_b</textureChoices>
<textureChoices>Amputations\Human\Upperarm\skin05_b</textureChoices>
<textureChoices>Amputations\Human\Upperarm\skin01_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Upperarm\skin02_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Upperarm\skin03_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Upperarm\skin04_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Upperarm\skin05_hairy_b</textureChoices>
<!-- HUMAN AFTER CICATRIZATION -->
<textureChoices>Body\MaleBody01</textureChoices>
<textureChoices>Body\MaleBody02</textureChoices>
<textureChoices>Body\MaleBody03</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05</textureChoices>
<textureChoices>Body\MaleBody01a</textureChoices>
<textureChoices>Body\MaleBody02a</textureChoices>
<textureChoices>Body\MaleBody03a</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05a</textureChoices>
<!-- ZOMBIE -->
<textureChoices>Amputations\Zombie\Upperarm\z_skin01_l1</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin01_l2</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin01_l3</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin02_l1</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin02_l2</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin02_l3</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin03_l1</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin03_l2</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin03_l3</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin04_l1</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin04_l2</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin04_l3</textureChoices>
</clothingItem>

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<clothingItem>
<m_MaleModel>Amputation\Amputation_Right_UpperArm_Male</m_MaleModel>
<m_FemaleModel>Amputation\Amputation_Right_UpperArm_Female</m_FemaleModel>
<m_GUID>db8ccad2-b76f-44bd-93ab-1eefa25beade</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomHue>false</m_AllowRandomHue>
<m_AllowRandomTint>false</m_AllowRandomTint>
<m_Masks>5</m_Masks>
<m_Masks>6</m_Masks>
<!-- HUMAN -->
<textureChoices>Amputations\Human\Upperarm\skin01_b</textureChoices>
<textureChoices>Amputations\Human\Upperarm\skin02_b</textureChoices>
<textureChoices>Amputations\Human\Upperarm\skin03_b</textureChoices>
<textureChoices>Amputations\Human\Upperarm\skin04_b</textureChoices>
<textureChoices>Amputations\Human\Upperarm\skin05_b</textureChoices>
<textureChoices>Amputations\Human\Upperarm1\skin01_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Upperarm1\skin02_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Upperarm1\skin03_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Upperarm1\skin04_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Upperarm1\skin05_hairy_b</textureChoices>
<!-- HUMAN AFTER CICATRIZATION -->
<textureChoices>Body\MaleBody01</textureChoices>
<textureChoices>Body\MaleBody02</textureChoices>
<textureChoices>Body\MaleBody03</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05</textureChoices>
<textureChoices>Body\MaleBody01a</textureChoices>
<textureChoices>Body\MaleBody02a</textureChoices>
<textureChoices>Body\MaleBody03a</textureChoices>
<textureChoices>Body\MaleBody04</textureChoices>
<textureChoices>Body\MaleBody05a</textureChoices>
<!-- ZOMBIE -->
<textureChoices>Amputations\Zombie\Upperarm\z_skin01_l1</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin01_l2</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin01_l3</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin02_l1</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin02_l2</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin02_l3</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin03_l1</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin03_l2</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin03_l3</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin04_l1</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin04_l2</textureChoices>
<textureChoices>Amputations\Zombie\Upperarm\z_skin04_l3</textureChoices>
</clothingItem>

View File

@@ -0,0 +1,10 @@
<?xml version='1.0' encoding='UTF-8'?>
<clothingItem>
<m_MaleModel>Prosthesis\hookArm_L_F</m_MaleModel>
<m_FemaleModel>Prosthesis\hookArm_L_F</m_FemaleModel>
<m_GUID>05338f5e-e984-49c2-be79-81af9ae8e818</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomTint>false</m_AllowRandomTint>
<textureChoices>Prosthesis\hookArm</textureChoices>
</clothingItem>

View File

@@ -0,0 +1,10 @@
<?xml version='1.0' encoding='UTF-8'?>
<clothingItem>
<m_MaleModel>Prosthesis\hookArm_R_F</m_MaleModel>
<m_FemaleModel>Prosthesis\hookArm_R_F</m_FemaleModel>
<m_GUID>8ee7e1bc-2c21-428e-a15d-760d98df973d</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomTint>false</m_AllowRandomTint>
<textureChoices>Prosthesis\hookArm</textureChoices>
</clothingItem>

View File

@@ -1,10 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<clothingItem>
<m_MaleModel>Prost_Left_LowerArm_Base_Hook_Male</m_MaleModel>
<m_FemaleModel>Prost_Left_LowerArm_Base_Hook_Female</m_FemaleModel>
<m_GUID>129ee688-d4bb-4297-8eb2-f88974001217</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomTint>false</m_AllowRandomTint>
<textureChoices>Prosthesis\metal_hook_male</textureChoices>
<textureChoices>Prosthesis\metal_hook_female</textureChoices>
</clothingItem>

View File

@@ -0,0 +1,10 @@
<?xml version='1.0' encoding='UTF-8'?>
<clothingItem>
<m_MaleModel>Prosthesis\normalArm_L_M</m_MaleModel>
<m_FemaleModel>Prosthesis\normalArm_L_F</m_FemaleModel>
<m_GUID>689318c7-5045-4876-a7e1-360de4aedf89</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomTint>false</m_AllowRandomTint>
<textureChoices>Prosthesis\normalArm</textureChoices>
</clothingItem>

View File

@@ -0,0 +1,10 @@
<?xml version='1.0' encoding='UTF-8'?>
<clothingItem>
<m_MaleModel>Prosthesis\normalArm_R_M</m_MaleModel>
<m_FemaleModel>Prosthesis\normalArm_R_F</m_FemaleModel>
<m_GUID>0e24eb76-4745-46af-9147-ba21e0ebbb2e</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomTint>false</m_AllowRandomTint>
<textureChoices>Prosthesis\normalArm</textureChoices>
</clothingItem>

View File

@@ -1,10 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<clothingItem>
<m_MaleModel>Prost_Right_LowerArm_Base_Hook_Male</m_MaleModel>
<m_FemaleModel>Prost_Right_LowerArm_Base_Hook_Female</m_FemaleModel>
<m_GUID>1eb56768-d7ef-46e4-ac07-91d0e43d15fb</m_GUID>
<m_Static>false</m_Static>
<m_AllowRandomTint>false</m_AllowRandomTint>
<textureChoices>Prosthesis\metal_hook_male</textureChoices>
<textureChoices>Prosthesis\metal_hook_female</textureChoices>
</clothingItem>

View File

@@ -1,100 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<fileGuidTable>
<files>
<path>media/clothing/clothingItems/Amputation_Right_Hand.xml</path>
<path>media/clothing/clothingItems/Amputation_Hand_R.xml</path>
<guid>f114e53a-b92e-4639-8d8c-2b43ab981885</guid>
</files>
<files>
<path>media/clothing/clothingItems/Amputation_Right_LowerArm.xml</path>
<path>media/clothing/clothingItems/Amputation_ForeArm_R.xml</path>
<guid>e6f80efd-22e5-49e0-8b24-537519d42b37</guid>
</files>
<files>
<path>media/clothing/clothingItems/Amputation_Right_UpperArm.xml</path>
<path>media/clothing/clothingItems/Amputation_UpperArm_R.xml</path>
<guid>db8ccad2-b76f-44bd-93ab-1eefa25beade</guid>
</files>
<files>
<path>media/clothing/clothingItems/Amputation_Left_Hand.xml</path>
<path>media/clothing/clothingItems/Amputation_Hand_L.xml</path>
<guid>2de93af2-b7a8-4c04-84d1-28d92cce8a0f</guid>
</files>
<files>
<path>media/clothing/clothingItems/Amputation_Left_LowerArm.xml</path>
<path>media/clothing/clothingItems/Amputation_ForeArm_L.xml</path>
<guid>d3816fe0-48e1-4cf5-a8e4-48c72595edb4</guid>
</files>
<files>
<path>media/clothing/clothingItems/Amputation_Left_UpperArm.xml</path>
<path>media/clothing/clothingItems/Amputation_UpperArm_L.xml</path>
<guid>646cafa5-3fa1-41af-9ca0-aa57cca3b36d</guid>
</files>
<!-- Prosthesis files -->
<!--Prosthetics -->
<!-- TODO RE DO ALL OF THIS!!! THIS IS OLD, MISSING STUFF, AND OLD OLD OLD-->
<files>
<path>media/clothing/clothingItems/Prost_Right_Hand_WoodenHook.xml</path>
<guid>1fcc7523-d577-4cb0-a019-f077ef281d3a</guid>
<path>media/clothing/clothingItems/Prost_HookArm_L.xml</path>
<guid>05338f5e-e984-49c2-be79-81af9ae8e818</guid>
</files>
<files>
<path>media/clothing/clothingItems/Prost_Left_Hand_WoodenHook.xml</path>
<guid>0def629e-fe4f-4485-bdae-2d6032e150be</guid>
</files>
<files>
<path>media/clothing/clothingItems/Prost_Right_Hand_MetalHook.xml</path>
<guid>dd7b749b-7e81-4547-91b0-81b1a1e9f7b8</guid>
</files>
<files>
<path>media/clothing/clothingItems/Prost_Left_Hand_MetalHook.xml</path>
<guid>6b4f4e13-d51f-48ab-80b0-6e0923650fc4</guid>
</files>
<files>
<path>media/clothing/clothingItems/Prost_Right_Hand_MetalHook.xml</path>
<guid>731c280a-9682-4e2e-84cf-470bf00dd02f</guid>
</files>
<files>
<path>media/clothing/clothingItems/Prost_Left_Hand_MetalHand.xml</path>
<guid>2101af26-54b9-455b-abc0-7533ce37f84b</guid>
</files>
<files>
<path>media/clothing/clothingItems/Prost_Right_LowerArm_WoodenHook.xml</path>
<guid>714b78a7-8895-4f48-a29d-b6f12909db0e</guid>
</files>
<files>
<path>media/clothing/clothingItems/Prost_Right_LowerArm_LeatherBase_MetalHook.xml</path>
<guid>1eb56768-d7ef-46e4-ac07-91d0e43d15fb</guid>
</files>
<files>
<path>media/clothing/clothingItems/Prost_Right_LowerArm_MetalHand.xml</path>
<guid>27758f1e-6298-42eb-b027-b9be31465c11</guid>
</files>
<files>
<path>media/clothing/clothingItems/Prost_Left_LowerArm_WoodenHook.xml</path>
<guid>aea8e02a-cba0-48d0-9eb0-7087651306b0</guid>
</files>
<files>
<path>media/clothing/clothingItems/Prost_Left_LowerArm_LeatherBase_MetalHook.xml</path>
<guid>129ee688-d4bb-4297-8eb2-f88974001217</guid>
</files>
<files>
<path>media/clothing/clothingItems/Prost_Left_LowerArm_MetalHand.xml</path>
<guid>0405a4c0-f71b-45a8-9edc-489fc81dca39</guid>
<path>media/clothing/clothingItems/Prost_HookArm_R.xml</path>
<guid>8ee7e1bc-2c21-428e-a15d-760d98df973d</guid>
</files>
<files>
<path>media/clothing/clothingItems/Surgery_Left_Tourniquet.xml</path>
<path>media/clothing/clothingItems/Prost_NormalArm_L.xml</path>
<guid>689318c7-5045-4876-a7e1-360de4aedf89</guid>
</files>
<files>
<path>media/clothing/clothingItems/Prost_NormalArm_R.xml</path>
<guid>0e24eb76-4745-46af-9147-ba21e0ebbb2e</guid>
</files>
<!--Surgery -->
<files>
<path>media/clothing/clothingItems/Surg_Arm_Tourniquet_L.xml</path>
<guid>afbab35d-8bd4-4d61-87c7-054651ead1bd</guid>
</files>
<files>
<path>media/clothing/clothingItems/Surgery_Right_Tourniquet.xml</path>
<path>media/clothing/clothingItems/Surg_Arm_Tourniquet_R.xml</path>
<guid>9a5fe063-63c7-4e6f-81ca-ee77c6678e0d</guid>
</files>
<files>
<path>media/clothing/clothingItems/Amputation_Left_Foot.xml</path>
<guid>506c0fc0-b50c-4667-bafa-ae22e3c2c0dc</guid>
</files>
<files>
<path>media/clothing/clothingItems/Amputation_Right_Foot.xml</path>
<guid>2600c2ab-bfeb-49c3-b0b5-e21c6d83d5c2</guid>
</files>
</fileGuidTable>

View File

@@ -1,142 +0,0 @@
------------------------------------------
-------------- THE ONLY CURE -------------
------------------------------------------
---------- COMPATIBILITY FUNCS -----------
if TOC_Compat == nil then
TOC_Compat = {}
end
-- Gets the old status and turns it into the new.
TOC_Compat.CheckCompatibilityWithOlderVersions = function(modData)
if modData.TOC ~= nil then
print("TOC: found old data from TOC")
if modData.TOC.Limbs ~= nil then
TOC_Compat.MapOldDataToNew(modData)
modData.TOC = nil -- Deletes the old mod data stuff
else
print("TOC: something is wrong, couldn't find Limbs table in old TOC modData")
end
else
print("TOC: couldn't find old TOC data")
end
end
TOC_Compat.MapOldDataToNew = function(modData)
local oldNamesTable = { "RightHand", "RightForearm", "RightArm", "LeftHand", "LeftForearm", "LeftArm" }
local newNamesTable = { "Right_Hand", "Right_LowerArm", "Right_UpperArm", "Left_Hand", "Left_LowerArm", "Left_UpperArm" }
print("TOC: Trying to backup old data from TOC")
if modData == nil then
return
end
print("TOC: found old data from TOC")
TOC_Cheat.ResetEverything()
-- Another check just in case the user is using Mr Bounty og version. I really don't wanna map that out so let's just reset everything directly
local compatEnum = nil
-- Player has the og version of the mod
if modData.TOC.Limbs.RightHand.IsCut ~= nil then
print("TOC: Found TOC Beta data")
compatEnum = 1
elseif modData.TOC.Limbs.Right_Hand.is_cut ~= nil then
print("TOC: Found TOCBB data")
compatEnum = 2
end
if compatEnum == nil then
print("TOC: Couldn't find any compatible data that could be retrieved")
return
end
-- Key setup
local isCutOldKey = nil
local isInfectedOldKey = nil
local isOperatedOldKey = nil
local isCicatrizedOldKey = nil
local isCauterizedOldKey = nil
local isAmputationShownOldKey = nil
local cicatrizationTimeOldKey = nil
local isOtherBodypartInfectedOldKey = nil
if compatEnum == 1 then
isCutOldKey = "IsCut"
isInfectedOldKey = "IsInfected"
isOperatedOldKey = "IsOperated"
isCicatrizedOldKey = "IsCicatrized"
isCauterizedOldKey = "ISBurn"
isAmputationShownOldKey = "ToDisplay"
cicatrizationTimeOldKey = "CicaTimeLeft"
isOtherBodypartInfectedOldKey = "OtherBody_IsInfected"
elseif compatEnum == 2 then
isCutOldKey = "is_cut"
isInfectedOldKey = "is_infected"
isOperatedOldKey = "is_operated"
isCicatrizedOldKey = "is_cicatrized"
isCauterizedOldKey = "is_cauterized"
isAmputationShownOldKey = "is_amputation_shown"
cicatrizationTimeOldKey = "cicatrization_time"
isOtherBodypartInfectedOldKey = "is_other_bodypart_infected"
elseif compatEnum == 3 then
isCutOldKey = "isCut"
isInfectedOldKey = "isInfected"
isOperatedOldKey = "isOperated"
isCicatrizedOldKey = "isCicatrized"
isCauterizedOldKey = "isCauterized"
isAmputationShownOldKey = "isAmputationShwon"
cicatrizationTimeOldKey = "cicatrizationTime"
isOtherBodypartInfectedOldKey = "isOtherBodypartInfected"
end
-- Starts reapplying stuff
modData.TOC.limbs.isOtherBodypartInfected = modData.TOC.Limbs[isOtherBodypartInfectedOldKey]
for i = 1, #newNamesTable do
local oldName = oldNamesTable[i]
local newName = newNamesTable[i]
print("TOC: isCut: " .. oldName .. " " .. tostring(modData.TOC.Limbs[oldName][isCutOldKey]))
print("TOC: isOperated: " .. oldName .. " " .. tostring(modData.TOC.Limbs[oldName][isOperatedOldKey]))
print("TOC: isCicatrized: " .. oldName .. " " .. tostring(modData.TOC.Limbs[oldName][isCicatrizedOldKey]))
print("TOC: isAmputationShown: " .. oldName .. " " .. tostring(modData.TOC.Limbs[oldName][isAmputationShownOldKey]))
print("TOC: cicatrizationTime: " .. oldName .. " " .. tostring(modData.TOC.Limbs[oldName][cicatrizationTimeOldKey]))
modData.TOC.limbs[newName].isCut = modData.TOC.Limbs[oldName][isCutOldKey]
if modData.TOC.limbs[newName].isCut then
print("TOC: Found old cut limb, reapplying model")
local cloth = getPlayer():getInventory():AddItem(TOC_Common.FindAmputatedClothingName(newName))
getPlayer():setWornItem(cloth:getBodyLocation(), cloth)
end
modData.TOC.limbs[newName].isInfected = modData.TOC.Limbs[oldName][isInfectedOldKey]
modData.TOC.limbs[newName].isOperated = modData.TOC.Limbs[oldName][isOperatedOldKey]
modData.TOC.limbs[newName].isCicatrized = modData.TOC.Limbs[oldName][isCicatrizedOldKey]
modData.TOC.limbs[newName].isCauterized = modData.TOC.Limbs[oldName][isCauterizedOldKey]
modData.TOC.limbs[newName].isAmputationShown = modData.TOC.Limbs[oldName][isAmputationShownOldKey]
modData.TOC.limbs[newName].cicatrizationTime = modData.TOC.Limbs[oldName][cicatrizationTimeOldKey]
end
end

View File

@@ -1,294 +0,0 @@
------------------------------------------
-------------- THE ONLY CURE -------------
------------------------------------------
----------- CUT LIMB FUNCTIONS -----------
-- Seems to be the first file loaded, so let's add this
if TOC == nil then
TOC = {}
end
local function CheckIfStillInfected(limbsData)
if limbsData == nil then
return
end
-- Check ALL body part types to check if the player is still gonna die
local check = false
for _, v in pairs(TOC_Common.GetPartNames()) do
if limbsData[v].isInfected then
check = true
end
end
if limbsData.isOtherBodypartInfected then
check = true
end
return check
end
local function CureInfection(bodyDamage, partName)
local bodyPartType = bodyDamage:getBodyPart(TOC_Common.GetBodyPartFromPartName(partName))
bodyDamage:setInfected(false)
bodyPartType:SetInfected(false)
bodyDamage:setInfectionMortalityDuration(-1)
bodyDamage:setInfectionTime(-1)
bodyDamage:setInfectionLevel(0)
local bodypartTypesTable = bodyDamage:getBodyParts()
-- TODO I think this is enough... we should just cycle if with everything instead of that crap up there
for i = bodypartTypesTable:size() - 1, 0, -1 do
local bodyPart = bodypartTypesTable:get(i)
bodyPart:SetInfected(false)
end
if bodyPartType:scratched() then bodyPartType:setScratched(false, false) end
if bodyPartType:haveGlass() then bodyPartType:setHaveGlass(false) end
if bodyPartType:haveBullet() then bodyPartType:setHaveBullet(false, 0) end
if bodyPartType:isInfectedWound() then bodyPartType:setInfectedWound(false) end
if bodyPartType:isBurnt() then bodyPartType:setBurnTime(0) end
if bodyPartType:isCut() then bodyPartType:setCut(false, false) end --Lacerations
if bodyPartType:getFractureTime() > 0 then bodyPartType:setFractureTime(0) end
end
local function DeleteOtherAmputatedLimbs(side)
-- if left hand is cut and we cut left lowerarm, then delete hand
for _, limb in pairs(TOC.limbNames) do
local partName = "TOC.Amputation_" .. TOC_Common.ConcatPartName(side, limb)
local amputatedLimbItem = getPlayer():getInventory():FindAndReturn(partName)
if amputatedLimbItem then
getPlayer():getInventory():Remove(amputatedLimbItem)
end
end
end
---@param player any
---@param perk any The perk to scale down
local function LosePerkLevel(player, perk)
player:LoseLevel(perk)
local actualLevel = player:getPerkLevel(perk)
local perkXp = player:getXp()
perkXp:setXPToLevel(perk, actualLevel)
SyncXp(player)
end
---@param isHealingBite boolean
local function SetParametersForMissingLimb(bodyPart, isHealingBite)
bodyPart:setBleeding(false)
bodyPart:setBleedingTime(0)
bodyPart:setDeepWounded(false)
bodyPart:setDeepWoundTime(0)
bodyPart:setScratched(false, false) -- why the fuck are there 2 booleans TIS?
bodyPart:setScratchTime(0)
bodyPart:setCut(false)
bodyPart:setCutTime(0)
if isHealingBite then
bodyPart:SetBitten(false)
bodyPart:setBiteTime(0)
end
end
function TOC.DamagePlayerDuringAmputation(patient, partName)
-- Since we're cutting that specific part, it only makes sense that the bleeding starts from there.
-- Then, we just delete the bleeding somewhere else before applying the other damage to to upper part of the limb
local bodyPartType = TOC_Common.GetBodyPartFromPartName(partName)
local bodyDamage = patient:getBodyDamage()
local bodyDamagePart = bodyDamage:getBodyPart(bodyPartType)
bodyDamagePart:setBleeding(true)
bodyDamagePart:setCut(true)
bodyDamagePart:setBleedingTime(ZombRand(10, 20))
end
local function FindTourniquetInWornItems(patient, side)
local checkString = "Surgery_" .. side .. "_Tourniquet"
local item = TOC_Common.FindItemInWornItems(patient, checkString)
return item
end
local function FindWristWatchInWornItems(patient, side)
local checkString = "Watch_" .. side
local item = TOC_Common.FindItemInWornItems(patient, checkString)
return item
end
----------------------------------------------------------------------------------
--- Main function for cutting a limb
---@param partName string the part name to amputate
---@param surgeonFactor any the surgeon factor, which will determine some stats for the inflicted wound
---@param bandageTable any bandages info
---@param painkillerTable any painkillers info, not used
TOC.CutLimb = function(partName, surgeonFactor, bandageTable, painkillerTable)
-- TODO Separate Cut Limb in side and limb instead of single part_name
-- Items get unequipped in ISCutLimb.Start
local player = getPlayer()
local TOCModData = player:getModData().TOC
local limbParameters = TOC.limbParameters
local limbsData = TOCModData.limbs
-- Cut Hand -> Damage in forearm
-- Cut Forearm -> Damage in Upperarm
-- Cut UpperArm -> Damage to torso
local bodyDamage = player:getBodyDamage()
local bodyPart = bodyDamage:getBodyPart(TOC_Common.GetBodyPartFromPartName(partName))
local adjacentBodyPart = player:getBodyDamage():getBodyPart(TOC_Common.GetAdjacentBodyPartFromPartName(partName))
local stats = player:getStats()
local side = TOC_Common.GetSideFromPartName(partName)
-- Reset the status of the first body part, since we just cut it off it shouldn't be bleeding anymore
-- The bit will be checked later since we're not sure if the player is not infected from another wound
SetParametersForMissingLimb(bodyPart, false)
-- Use a tourniquet if available
local tourniquetItem = FindTourniquetInWornItems(player, side)
local baseDamageValue = 100
if tourniquetItem ~= nil then
baseDamageValue = 50 -- TODO Decrease mostly blood and damage, add pain, not everything else
if partName == TOC_Common.ConcatPartName(side, "UpperArm") then
player:removeWornItem(tourniquetItem)
end
end
-- Removes wrist watches in case they're amputating the same side where they equipped it
local wristWatchItem = FindWristWatchInWornItems(player, side)
if wristWatchItem ~= nil then
if partName == side .. "_LowerArm" or partName == side .. "_UpperArm" then
player:removeWornItem(wristWatchItem)
end
end
-- Set damage, stress, and low endurance after amputation
adjacentBodyPart:AddDamage(baseDamageValue - surgeonFactor)
adjacentBodyPart:setAdditionalPain(baseDamageValue - surgeonFactor)
adjacentBodyPart:setBleeding(true)
adjacentBodyPart:setBleedingTime(baseDamageValue - surgeonFactor)
adjacentBodyPart:setDeepWounded(true)
adjacentBodyPart:setDeepWoundTime(baseDamageValue - surgeonFactor)
stats:setEndurance(surgeonFactor)
stats:setStress(baseDamageValue - surgeonFactor)
-- Set malus for strength and fitness
-- TODO Make it more "random" with just some XP scaling down instead of a whole level, depending on the limb that we're cutting
LosePerkLevel(player, Perks.Fitness)
LosePerkLevel(player, Perks.Strength)
-- If bandages are available, use them
adjacentBodyPart:setBandaged(bandageTable.useBandage, 10, bandageTable.isBandageSterilized,
bandageTable.bandageType)
-- If painkillers are available, use them
-- TODO add painkiller support
-- A check for isCut shouldn't be necessary here since if we've got here we've already checked it out enough
if limbsData[partName].isCut == false then
limbsData[partName].isCut = true
limbsData[partName].isAmputationShown = true
limbsData[partName].cicatrizationTime = limbParameters[partName].cicatrizationBaseTime - surgeonFactor * 50
for _, depended_v in pairs(limbParameters[partName].dependsOn) do
limbsData[depended_v].isCut = true
limbsData[depended_v].isAmputationShown = false
limbsData[depended_v].cicatrizationTime = limbParameters[partName].cicatrizationBaseTime -
surgeonFactor * 50
local canHealDependedV = limbsData[depended_v].isInfected and
bodyDamage:getInfectionLevel() < 20
local depended_body_part = bodyDamage:getBodyPart(TOC_Common.GetBodyPartFromPartName(depended_v))
SetParametersForMissingLimb(depended_body_part, canHealDependedV)
if canHealDependedV then
limbsData[depended_v].isInfected = false
end
end
-- Heal the infection here
local body_damage = player:getBodyDamage()
if limbsData[partName].isInfected and body_damage:getInfectionLevel() < 20 then
limbsData[partName].isInfected = false
-- NOT THE ADIACENT ONE!!!
bodyPart:SetBitten(false)
bodyPart:setBiteTime(0)
-- Second check, let's see if there is any other infected limb.
if CheckIfStillInfected(limbsData) == false then
CureInfection(body_damage, partName)
getPlayer():Say("I'm gonna be fine...") -- TODO Make it visible to other players, check True Actions as reference
else
getPlayer():Say("I'm still gonna die...")
end
end
-- Check for older amputation models and deletes them from player's inventory
local side = string.match(partName, '(%w+)_')
DeleteOtherAmputatedLimbs(side)
--Equip new model for amputation
local amputation_clothing_item_name = TOC_Common.FindAmputatedClothingName(partName)
print(amputation_clothing_item_name)
local amputation_clothing_item = player:getInventory():AddItem(amputation_clothing_item_name)
TOC_Visuals.SetTextureForAmputation(amputation_clothing_item, player, false)
player:setWornItem(amputation_clothing_item:getBodyLocation(), amputation_clothing_item)
-- Set blood on the amputated limb
TOC_Visuals.SetBloodOnAmputation(getPlayer(), adjacentBodyPart)
if partName == "Left_Foot" or partName == "Right_Foot" then
TOC_Anims.SetMissingFootAnimation(true)
end
end
end

View File

@@ -1,51 +0,0 @@
------------------------------------------
-------------- THE ONLY CURE -------------
------------------------------------------
------------- LOCAL ACTIONS --------------
--Used to handle SP scenarios
if TOC_LocalActions == nil then
TOC_LocalActions = {}
end
function TOC_LocalActions.Cut(_, player, partName)
if TOC_Common.GetSawInInventory(player) ~= nil then
ISTimedActionQueue.add(TOC_CutLimbAction:new(player, player, partName))
else
player:Say("I don't have a saw on me")
end
end
function TOC_LocalActions.Operate(_, player, partName, useOven)
if useOven then
ISTimedActionQueue.add(TOC_OperateLimbAction:new(player, player, _, partName, useOven));
else
local kit = TOC_Common.GetKitInInventory(player)
if kit ~= nil then
ISTimedActionQueue.add(TOC_OperateLimbAction:new(player, player, kit, partName, false))
else
player:Say("I don't have a kit on me")
end
end
end
function TOC_LocalActions.EquipProsthesis(_, player, partName)
local surgeonInv = player:getInventory()
-- TODO Find a better way to filter objects. Disabled for now and only gets LeatherBase
local prosthesisToEquip = surgeonInv:getItemFromType('TOC.LeatherBase_MetalHook')
if prosthesisToEquip then
ISTimedActionQueue.add(TOC_InstallProsthesisAction:new(player, player, prosthesisToEquip, partName))
else
player:Say("I need a prosthesis")
end
end
function TOC_LocalActions.UnequipProsthesis(_, player, partName)
ISTimedActionQueue.add(TOC_UninstallProsthesisAction:new(player, player, partName))
end

View File

@@ -1,73 +0,0 @@
------------------------------------------
-------- THE ONLY CURE --------
------------------------------------------
--------- OPERATE LIMB FUNCTIONS ---------
local function FixSingleBodyPartType(bodyPartType, useOven)
bodyPartType:setDeepWounded(false) --Basically like stitching
bodyPartType:setDeepWoundTime(0)
if useOven then
bodyPartType:AddDamage(100)
bodyPartType:setAdditionalPain(100);
bodyPartType:setBleeding(false)
bodyPartType:setBleedingTime(0) -- no bleeding since it's been cauterized
else
-- TODO Think a little better about this, do we want to trigger bleeding or not?
bodyPartType:setBleeding(false)
--body_part_type:setBleedingTime(ZombRand(1, 5)) -- Reset the bleeding, maybe make it random
end
end
local function SetBodyPartsStatusAfterOperation(player, limbParameters, partName, useOven)
local bodyPartType = player:getBodyDamage():getBodyPart(TOC_Common.GetAdjacentBodyPartFromPartName(partName))
FixSingleBodyPartType(bodyPartType, useOven)
for _, v in pairs(limbParameters[partName].dependsOn) do
local dependedBodyPartType = player:getBodyDamage():getBodyPart(TOC_Common.GetAdjacentBodyPartFromPartName(v))
FixSingleBodyPartType(dependedBodyPartType, useOven)
end
end
----------------------------------------------------------------------------------
---Main function to operate a limb after amputation
---@param partName any
---@param surgeonFactor any
---@param useOven boolean wheter using oven instead of a kit or not
function TOC.OperateLimb(partName, surgeonFactor, useOven)
local player = getPlayer()
local TOCModData = player:getModData().TOC
local limbParameters = TOC.limbParameters
local limbsData = TOCModData.limbs
if useOven then
local stats = player:getStats()
stats:setEndurance(100)
stats:setStress(100)
end
if limbsData[partName].isOperated == false and limbsData[partName].isCut == true then
limbsData[partName].isOperated = true
limbsData[partName].cicatrizationTime = limbsData[partName].cicatrizationTime - (surgeonFactor * 200)
if useOven then limbsData[partName].isCauterized = true end
for _, dependedPart in pairs(limbParameters[partName].dependsOn) do
limbsData[dependedPart].isOperated = true
-- TODO We should not have cicatrization time for depended parts.
-- limbsData[dependedPart].cicatrizationTime = limbsData[dependedPart].cicatrizationTime -
-- (surgeonFactor * 200)
if useOven then limbsData[dependedPart].isCauterized = true end
end
end
SetBodyPartsStatusAfterOperation(player, limbParameters, partName, useOven)
end

View File

@@ -1,85 +0,0 @@
------------------------------------------
------------- THE ONLY CURE --------------
------------------------------------------
---------- PROSTHESIS FUNCTIONS ----------
---Equip a prosthesis transforming a normal item into a clothing item
---@param partName string
---@param prosthesisItem any the prosthesis item
---@param prosthesisBaseName string I don't really remember
function TOC.EquipProsthesis(partName, prosthesisItem, prosthesisBaseName)
-- TODO probably will have to move this from the TOC menu to classic equip to have dynamic durability
-- TODO We need to pass the original item so we can get its data!
local player = getPlayer()
local TOCModData = player:getModData().TOC
local equippedProsthesis = GenerateEquippedProsthesis(prosthesisItem, player:getInventory(), partName)
--print("TOC: Test durability new item " .. added_prosthesis_mod_data.TOC.durability)
-- TODO equippedProsthesis must have something like the ProsthesisFactor from before!!!
if partName ~= nil then
if equippedProsthesis ~= nil then
TOCModData.limbs[partName].isProsthesisEquipped = true
-- Fill equippedProsthesis with the correct stuff
-- TODO For prosthetics we should fetch the data from a modData INSIDE them!
-- TODO Change the value passed, it's wrong
--TOCModData.limbs[partName].equippedProsthesis = TOCModData.Prosthesis[prosthesisBaseName][partName]
if player:isFemale() then
equippedProsthesis:getVisual():setTextureChoice(1)
else
equippedProsthesis:getVisual():setTextureChoice(0)
end
player:setWornItem(equippedProsthesis:getBodyLocation(), equippedProsthesis)
end
end
end
---Unequip a prosthesis clothing item and returns it to the inventory as a normal item
---@param partName string
function TOC.UnequipProsthesis(patient, partName, equippedProsthesis)
-- TODO Pass the parameters generated from EquipProsthesis to the re-generated normal item
local TOCModData = patient:getModData().TOC
TOCModData.limbs[partName].isProsthesisEquipped = false
local equippedProstFullType = equippedProsthesis:getFullType()
for _, prostValue in ipairs(GetProsthesisList()) do
local prostName = string.match(equippedProstFullType, prostValue)
if prostName then
-- Get mod data from equipped prosthesis so we can get its parameters
local equippedProstModData = equippedProsthesis:getModData()
local baseProstItem = patient:getInventory():AddItem("TOC." .. prostName)
local baseProstItemModData = baseProstItem.getModData()
baseProstItemModData.TOC = {
durability = equippedProstModData.TOC.durability,
speed = equippedProstModData.TOC.speed
}
patient:setWornItem(equippedProsthesis:getBodyLocation(), nil)
patient:getInventory():Remove(equippedProsthesis)
TOCModData.limbs[partName].equippedProsthesis = nil
end
end
end

View File

@@ -1,123 +0,0 @@
-- TODO this should be moved
local function TryToToResetEverythingOtherPlayer(_, patient, surgeon)
sendClientCommand(surgeon, "TOC", "AskToResetEverything", { patient:getOnlineID() })
end
----------------------------------------------------------------------------------------------------------
if TOC_ContextMenu == nil then
TOC_ContextMenu = {}
end
TOC_ContextMenu.CreateCheatsMenu = function(playerId, context, worldObjects, _)
local clickedPlayers = {}
local currentClickedPlayer = nil
local localPlayer = getSpecificPlayer(playerId)
--local players = getOnlinePlayers()
for _, v in pairs(worldObjects) do
-- help detecting a player by checking nearby squares
for x = v:getSquare():getX() - 1, v:getSquare():getX() + 1 do
for y = v:getSquare():getY() - 1, v:getSquare():getY() + 1 do
local sq = getCell():getGridSquare(x, y, v:getSquare():getZ())
if sq then
for i = 0, sq:getMovingObjects():size() - 1 do
local o = sq:getMovingObjects():get(i)
if instanceof(o, "IsoPlayer") then
currentClickedPlayer = o
if clickedPlayers[currentClickedPlayer:getUsername()] == nil then
-- FIXME this is to prevent context menu spamming. Find a better way
clickedPlayers[currentClickedPlayer:getUsername()] = true
if localPlayer:getAccessLevel() == "Admin" or isDebugEnabled() then
local rootOption = context:addOption("TOC - Cheats on " .. currentClickedPlayer:getUsername())
local rootMenu = context:getNew(context)
if currentClickedPlayer == localPlayer then
rootMenu:addOption("Reset TOC for me", _, TOC_Cheat.ResetEverything)
else
rootMenu:addOption("Reset TOC for " .. currentClickedPlayer:getUsername(), _, TryToToResetEverythingOtherPlayer,
currentClickedPlayer, localPlayer)
end
context:addSubMenu(rootOption, rootMenu)
end
-- TocContextMenus.FillCutAndOperateMenus(local_player, clicked_player, worldObjects,
-- cut_menu, operate_menu)
--TocContextMenus.FillCheatMenu(context, cheat_menu)
break
end
end
end
end
end
end
end
end
TOC_ContextMenu.CreateOperateWithOvenMenu = function(playerId, context, worldObjects, test)
local player = getSpecificPlayer(playerId)
-- TODO Let the player move towards the oven
local partData = player:getModData().TOC.limbs
local isMainMenuAlreadyCreated = false
for _, currentObject in pairs(worldObjects) do
if instanceof(currentObject, "IsoStove") and (player:HasTrait("Brave") or player:getPerkLevel(Perks.Strength) >= 6) then
-- Check temperature
if currentObject:getCurrentTemperature() > 250 then
for _, partName in ipairs(TOC_Common.GetPartNames()) do
if partData[partName].isCut and partData[partName].isAmputationShown and
not partData[partName].isOperated then
local subMenu = context:getNew(context);
if isMainMenuAlreadyCreated == false then
local rootMenu = context:addOption(getText('UI_ContextMenu_OperateOven'), worldObjects, nil);
context:addSubMenu(rootMenu, subMenu)
isMainMenuAlreadyCreated = true
end
subMenu:addOption(getText('UI_ContextMenu_' .. partName), worldObjects, TOC_LocalActions.Operate,
getSpecificPlayer(playerId), partName,true)
end
end
end
break -- stop searching for stoves
end
end
end
TOC_ContextMenu.CreateNewMenu = function(name, context, rootMenu)
local new_option = rootMenu:addOption(name)
local new_menu = context:getNew(context)
context:addSubMenu(new_option, new_menu)
return new_menu
end
TOC_ContextMenu.FillCheatMenus = function(context, cheat_menu)
if cheat_menu then
local cheat_cut_and_fix_menu = TOC_ContextMenu.CreateNewMenu("Cut and Fix", context, cheat_menu)
end
end
Events.OnFillWorldObjectContextMenu.Add(TOC_ContextMenu.CreateOperateWithOvenMenu) -- this is probably too much
Events.OnFillWorldObjectContextMenu.Add(TOC_ContextMenu.CreateCheatsMenu) -- TODO Add check only when admin is active

View File

@@ -1,700 +0,0 @@
if TOC_UI == nil then
TOC_UI = {}
end
local mainUI, descUI, confirmUI, confirmUIMP
-------------------------
-- MP stuff
-- TODO Strip out all this crap and redo it
local function PrerenderFuncMP()
local toSee = confirmUIMP
if confirmUIMP.responseReceive then
if not confirmUIMP.responseCan then
getPlayer():Say("I can't do that !")
confirmUIMP.responseReceive = false
confirmUIMP:close()
return false;
end
-- Prerender basically hooks onto SendCommandToConfirmUI, dunno how but it does
SendCommandToConfirmUIMP(confirmUIMP.responseAction, confirmUIMP.responseIsBitten,
confirmUIMP.responseUserName, confirmUIMP.responsePartName);
end
end
-----------------------
-- Getters
function GetConfirmUIMP()
return confirmUIMP;
end
------------------------------
-- UI Visible stuff functions
local function GetImageName(partName, limbsData)
local name = ""
local partData = limbsData[partName]
if partData.isCut and partData.isCicatrized and partData.isProsthesisEquipped then -- Cut and equip
if partName == "Right_Hand" or partName == "Left_Hand" then
name = "media/ui/TOC/" .. partName .. "/Hook.png"
else
name = "media/ui/TOC/" .. partName .. "/Prothesis.png"
end
elseif partData.isCut and partData.isCicatrized and not partData.isProsthesisEquipped and
partData.isAmputationShown then -- Cut and heal
name = "media/ui/TOC/" .. partName .. "/Cut.png"
elseif partData.isCut and not partData.isCicatrized and partData.isAmputationShown and
not partData.isOperated then -- Cut but not healead
name = "media/ui/TOC/" .. partName .. "/Bleed.png"
elseif partData.isCut and not partData.isCicatrized and partData.isAmputationShown and partData.isOperated then -- Cut but not healed and operated
name = "media/ui/TOC/" .. partName .. "/Operate.png"
elseif partData.isCut and not partData.isAmputationShown then -- Empty (like hand if forearm cut)
name = "media/ui/TOC/Empty.png"
elseif not partData.isCut and
-- FIXME This doesn't work in MP on another player since we're trying to retrieve bodyDamage from another player
getPlayer():getBodyDamage():getBodyPart(TOC_Common.GetBodyPartFromPartName(partName)):bitten() then -- Not cut but bitten
name = "media/ui/TOC/" .. partName .. "/Bite.png"
else -- Not cut
name = "media/ui/TOC/" .. partName .. "/Base.png"
end
-- If foreaerm equip, change hand
if partName == "Right_Hand" and limbsData["Right_LowerArm"].isProsthesisEquipped then
name = "media/ui/TOC/" .. partName .. "/Hook.png"
elseif partName == "Left_Hand" and limbsData["Left_LowerArm"].isProsthesisEquipped then
name = "media/ui/TOC/" .. partName .. "/Hook.png"
end
return name
end
------------------------------------------
-- Check functions
local function IsProsthesisInstalled(partData)
return partData.isCut and partData.isCicatrized and partData.isProsthesisEquipped
end
local function CanProsthesisBeEquipped(partData)
return partData.isCut and partData.isCicatrized and not partData.isProsthesisEquipped and
partData.isAmputationShown
end
local function IsAmputatedLimbHealed(partData)
return partData.isCut and not partData.isCicatrized and partData.isAmputationShown
end
local function IsAmputatedLimbToBeVisible(partData)
return partData.isCut and not partData.isAmputationShown
end
local function IsPartBitten(partData, partName)
return not partData.isCut and
getPlayer():getBodyDamage():getBodyPart(TOC_Common.GetBodyPartFromPartName(partName)):bitten()
end
local function FindMinMax(lv)
local min, max
if lv == 1 then
min = 0;
max = 75;
elseif lv == 2 then
min = 75;
max = 150 + 75;
elseif lv == 3 then
min = 150;
max = 300 + 75 + 150;
elseif lv == 4 then
min = 300;
max = 750 + 75 + 150 + 300;
elseif lv == 5 then
min = 750;
max = 1500 + 75 + 150 + 300 + 750;
elseif lv == 6 then
min = 1500;
max = 3000 + 75 + 150 + 300 + 750 + 1500;
elseif lv == 7 then
min = 3000;
max = 4500 + 75 + 150 + 300 + 750 + 1500 + 3000;
elseif lv == 8 then
min = 4500;
max = 6000 + 75 + 150 + 300 + 750 + 1500 + 3000 + 4500;
elseif lv == 9 then
min = 6000;
max = 7500 + 75 + 150 + 300 + 750 + 1500 + 3000 + 4500 + 6000;
elseif lv == 10 then
min = 7500;
max = 9000 + 75 + 150 + 300 + 750 + 1500 + 3000 + 4500 + 6000 + 7500;
end
return min, max;
end
-----------------------------------------
-- Setup stuff with variables and shit
TOC_UI.SetupMainUI = function(surgeon, patient, limbsData)
mainUI.surgeon = surgeon -- we shouldn't need an arg for this
mainUI.patient = patient
if limbsData then
mainUI.limbsData = limbsData
mainUI["b11"]:setPath(GetImageName("Right_UpperArm", limbsData))
mainUI["b12"]:setPath(GetImageName("Left_UpperArm", limbsData))
mainUI["b21"]:setPath(GetImageName("Right_LowerArm", limbsData))
mainUI["b22"]:setPath(GetImageName("Left_LowerArm", limbsData))
mainUI["b31"]:setPath(GetImageName("Right_Hand", limbsData))
mainUI["b32"]:setPath(GetImageName("Left_Hand", limbsData))
mainUI["b41"]:setPath(GetImageName("Right_Foot", limbsData))
mainUI["b42"]:setPath(GetImageName("Left_Foot", limbsData))
end
end
TOC_UI.SetupDescUI = function(surgeon, patient, limbsData, partName)
descUI["textTitle"]:setText(getText("UI_ContextMenu_" .. partName))
descUI.partName = partName
descUI.surgeon = surgeon
descUI.patient = patient
local partData = limbsData[partName]
if IsProsthesisInstalled(partData) then
-- Limb cut with prosthesis
descUI["status"]:setText("Prosthesis equipped")
descUI["status"]:setColor(1, 0, 1, 0)
descUI["b1"]:setText("Unequip")
descUI["b1"]:addArg("option", "Unequip")
descUI["b1"]:setVisible(true)
elseif CanProsthesisBeEquipped(partData) then
-- Limb cut but no prosthesis
descUI["status"]:setText("Amputated and healed")
descUI["status"]:setColor(1, 0, 1, 0)
-- Another check for UpperArm
if partName == "Right_UpperArm" or partName == "Left_UpperArm" then
descUI["b1"]:setVisible(false)
else
descUI["b1"]:setText("Equip")
descUI["b1"]:addArg("option", "Equip")
descUI["b1"]:setVisible(true)
end
-- Limb cut but still healing
elseif IsAmputatedLimbHealed(partData) then
-- Limb cut and healed, no prosthesis equipped
if partData.isOperated then
descUI["b1"]:setVisible(false) -- no operate prompt
if partData.cicatrizationTime > 1000 then
descUI["status"]:setText("Still a long way to go")
descUI["status"]:setColor(1, 0.8, 1, 0.2);
elseif partData.cicatrizationTime > 500 then
descUI["status"]:setText("Starting to get better")
descUI["status"]:setColor(1, 0.8, 1, 0.2)
elseif partData.cicatrizationTime > 100 then
descUI["status"]:setText("Almost cicatrized")
descUI["status"]:setColor(1, 0.8, 1, 0.2)
end
else
-- Set the operate button
descUI["b1"]:setText("Operate")
descUI["b1"]:addArg("option", "Operate")
descUI["b1"]:setVisible(true)
if partData.cicatrizationTime > 1000 then
descUI["status"]:setText("It hurts so much...")
descUI["status"]:setColor(1, 1, 0, 0)
elseif partData.cicatrizationTime > 500 then
descUI["status"]:setText("It still hurts a lot")
descUI["status"]:setColor(1, 0.8, 1, 0.2)
elseif partData.cicatrizationTime > 500 then
descUI["status"]:setText("I think it's almost over...")
descUI["status"]:setColor(1, 0.8, 1, 0.2)
end
end
elseif IsAmputatedLimbToBeVisible(partData) then
-- Limb cut and not visible (ex: hand after having amputated forearm)
descUI["status"]:setText("Nothing here")
descUI["status"]:setColor(1, 1, 1, 1)
descUI["b1"]:setVisible(false)
elseif TOC_Common.CheckIfCanBeCut(partName, limbsData) then
-- Everything else
-- TODO add check for cuts and scratches
descUI["status"]:setText("Not cut")
descUI["status"]:setColor(1, 1, 1, 1)
if TOC_Common.GetSawInInventory(surgeon) and not TOC_Common.CheckIfProsthesisAlreadyInstalled(limbsData, partName) then
descUI["b1"]:setVisible(true)
descUI["b1"]:setText("Cut")
descUI["b1"]:addArg("option", "Cut")
elseif TOC_Common.GetSawInInventory(surgeon) and TOC_Common.CheckIfProsthesisAlreadyInstalled(limbsData, partName) then
descUI["b1"]:setVisible(true)
descUI["b1"]:setText("Remove prosthesis before")
descUI["b1"]:addArg("option", "Nothing")
else
descUI["b1"]:setVisible(false)
end
else
descUI["status"]:setText("Not cut")
descUI["status"]:setColor(1, 1, 1, 1)
descUI["b1"]:setVisible(true)
descUI["b1"]:setText("Remove prosthesis before")
descUI["b1"]:addArg("option", "Nothing")
end
-- Prosthesis Level
if string.find(partName, "Right") then
local lv = patient:getPerkLevel(Perks.Right_Hand) + 1
descUI["textLV2"]:setText("Level: " .. lv .. " / 10")
local xp = patient:getXp():getXP(Perks.Right_Hand)
local min, max = FindMinMax(lv)
descUI["pbarNLV"]:setMinMax(min, max)
descUI["pbarNLV"]:setValue(xp)
else
local lv = patient:getPerkLevel(Perks.Left_Hand) + 1
descUI["textLV2"]:setText("Level: " .. lv .. " / 10")
local xp = patient:getXp():getXP(Perks.Left_Hand)
local min, max = FindMinMax(lv)
descUI["pbarNLV"]:setMinMax(min, max)
descUI["pbarNLV"]:setValue(xp)
end
end
------------------------------------------------
-- On Click Functions
local function OnClickMainUI(button, args)
descUI:open()
descUI:setPositionPixel(mainUI:getRight(), mainUI:getY())
TOC_UI.SetupDescUI(mainUI.surgeon, mainUI.patient, mainUI.limbsData, args.partName) -- surgeon is generic.
end
-- Generic TOC action, used in OnClickDescUI
local function TryTOCAction(_, partName, action, surgeon, patient)
-- TODO at this point surgeon doesnt do anything. We'll fix this later
-- Check if SinglePlayer
if not isServer() and not isClient() then
if action == "Cut" then
TOC_LocalActions.Cut(_, surgeon, partName)
elseif action == "Operate" then
TOC_LocalActions.Operate(_, surgeon, partName, false)
elseif action == "Equip" then
TOC_LocalActions.EquipProsthesis(_, surgeon, partName)
elseif action == "Unequip" then
TOC_LocalActions.UnequipProsthesis(_, surgeon, partName)
end
else
local ui = GetConfirmUIMP()
if not ui then
CreateConfirmUIMP()
ui = GetConfirmUIMP()
end
if patient == nil then
patient = surgeon
end
if action == "Cut" then
AskCanCutLimb(patient, partName)
elseif action == "Operate" then
AskCanOperateLimb(patient, partName)
elseif action == "Equip" then
AskCanEquipProsthesis(patient, partName)
elseif action == "Unequip" then
AskCanUnequipProsthesis(patient, partName)
end
ui.actionAct = action
ui.partNameAct = partName
ui.patient = patient
SendCommandToConfirmUIMP("Wait server")
end
end
local function OnClickDescUI(button, args)
-- Gets every arg from main
local patient = descUI.patient
local surgeon = descUI.surgeon
if args.option ~= "Nothing" then
TryTOCAction(_, descUI.partName, args.option, surgeon, patient)
end
mainUI:close()
end
local function OnClickConfirmUIMP(button, args)
local player = getPlayer()
if confirmUIMP.actionAct == "Cut" and args.option == "yes" then
ISTimedActionQueue.add(TOC_CutLimbAction:new(confirmUIMP.patient, player, confirmUIMP.partNameAct))
elseif confirmUIMP.actionAct == "Operate" and args.option == "yes" then
local kit = TOC_Common.GetKitInInventory(player)
if kit then
ISTimedActionQueue.add(TOC_OperateLimbAction:new(confirmUIMP.patient, player, kit, confirmUIMP.partNameAct,
false))
else
player:Say("I need a kit")
end
elseif confirmUIMP.actionAct == "Equip" and args.option == "yes" then
-- TODO Gonna be broken soon!
local surgeon_inventory = player:getInventory()
local prosthesis_to_equip = surgeon_inventory:getItemFromType('TOC.MetalHand') or
surgeon_inventory:getItemFromType('TOC.MetalHook') or
surgeon_inventory:getItemFromType('TOC.WoodenHook')
if prosthesis_to_equip then
ISTimedActionQueue.add(TOC_InstallProsthesisAction:new(player, confirmUIMP.patient, prosthesis_to_equip,
confirmUIMP.partNameAct))
else
player:Say("I don't have a prosthesis right now")
end
elseif confirmUIMP.actionAct == "Unequip" and args.option == "yes" then
-- We can't check if the player has a prosthesis right now, we need to do it later
-- TODO should check if player has a prosthesis equipped before doing it
-- TODO Player is surgeon, but we don't have a confirm_ui_mp.surgeon... awful awful awful
-- TODO Workaround for now, we'd need to send data from patient before doing it since we can't access his inventory from the surgeon
if confirmUIMP.patient == player then
ISTimedActionQueue.add(TOC_UninstallProsthesisAction:new(player, confirmUIMP.patient, confirmUIMP.partNameAct))
else
player:Say("I can't do that, they need to do it themselves")
end
end
confirmUIMP:close()
confirmUIMP.responseReceive = false
end
-----------------------------------------------
-- CREATE UI SECTION
local function CreateMainUI()
mainUI = NewUI()
mainUI:setTitle("The Only Cure Menu")
mainUI:setWidthPercent(0.1)
mainUI:addImageButton("b11", "", OnClickMainUI)
mainUI["b11"]:addArg("partName", "Right_UpperArm")
mainUI:addImageButton("b12", "", OnClickMainUI)
mainUI["b12"]:addArg("partName", "Left_UpperArm")
mainUI:nextLine()
mainUI:addImageButton("b21", "", OnClickMainUI)
mainUI["b21"]:addArg("partName", "Right_LowerArm")
mainUI:addImageButton("b22", "", OnClickMainUI)
mainUI["b22"]:addArg("partName", "Left_LowerArm")
mainUI:nextLine()
mainUI:addImageButton("b31", "", OnClickMainUI)
mainUI["b31"]:addArg("partName", "Right_Hand")
mainUI:addImageButton("b32", "", OnClickMainUI)
mainUI["b32"]:addArg("partName", "Left_Hand")
mainUI:nextLine()
mainUI:addImageButton("b41", "", OnClickMainUI)
mainUI["b41"]:addArg("partName", "Right_Foot")
mainUI:addImageButton("b42", "", OnClickMainUI)
mainUI["b42"]:addArg("partName", "Left_Foot")
mainUI:saveLayout()
end
-- Create a temporary desc UI with fake data (for now)
local function CreateDescUI()
descUI = NewUI()
descUI:setTitle("The only cure description");
descUI:isSubUIOf(mainUI)
descUI:setWidthPixel(250)
descUI:setColumnWidthPixel(1, 100)
descUI:addText("textTitle", "Right arm", "Large", "Center")
descUI:nextLine()
descUI:addText("textLV2", "Level 3/10", _, "Center")
descUI:nextLine()
descUI:addText("textLV", "Next LV:", _, "Right")
descUI:addProgressBar("pbarNLV", 39, 0, 100)
descUI["pbarNLV"]:setMarginPixel(10, 6)
descUI:nextLine()
descUI:addEmpty("border1")
descUI:setLineHeightPixel(1)
descUI["border1"]:setBorder(true)
descUI:nextLine()
descUI:addEmpty()
descUI:nextLine()
descUI:addText("status", "Temporary", "Medium", "Center")
descUI["status"]:setColor(1, 1, 0, 0)
descUI:nextLine()
descUI:addEmpty()
descUI:nextLine()
descUI:addButton("b1", "Operate", OnClickDescUI)
descUI:saveLayout()
end
function CreateConfirmUIMP()
confirmUIMP = NewUI()
confirmUIMP.responseReceive = false
confirmUIMP:addText("text1", "Are you sure?", "Title", "Center");
confirmUIMP:setLineHeightPixel(getTextManager():getFontHeight(confirmUIMP.text1.font) + 10)
confirmUIMP:nextLine();
confirmUIMP:addText("text4", "", "Medium", "Center");
confirmUIMP:setLineHeightPixel(getTextManager():getFontHeight(confirmUIMP.text4.font) + 10)
confirmUIMP:nextLine();
confirmUIMP:addText("text2", "", _, "Center");
confirmUIMP:nextLine();
confirmUIMP:addText("text3", "", _, "Center");
confirmUIMP:nextLine();
confirmUIMP:addEmpty();
confirmUIMP:nextLine();
confirmUIMP:addEmpty();
confirmUIMP:addButton("b1", "Yes", OnClickConfirmUIMP);
confirmUIMP.b1:addArg("option", "yes");
confirmUIMP:addEmpty();
confirmUIMP:addButton("b2", "No", OnClickConfirmUIMP);
confirmUIMP:addEmpty();
confirmUIMP:nextLine();
confirmUIMP:addEmpty();
confirmUIMP:saveLayout();
confirmUIMP:addPrerenderFunction(PrerenderFuncMP);
confirmUIMP:close();
end
-- We create everything from here
TOC_UI.OnCreate = function()
CreateMainUI()
CreateDescUI()
CreateConfirmUIMP()
if isClient() then CreateConfirmUIMP() end
mainUI:close()
end
--------------------------------------------
-- MP Confirm (I should add it to client too but hey not sure how it works tbh)
function SendCommandToConfirmUIMP(action, isBitten, userName, partName)
confirmUIMP:setInCenterOfScreen()
confirmUIMP:bringToTop()
confirmUIMP:open()
if action ~= "Wait server" then
confirmUIMP["text4"]:setText("You're gonna " ..
action .. " the " .. getText("UI_ContextMenu_" .. partName) .. " of " .. userName)
confirmUIMP["text2"]:setText("Are you sure?")
confirmUIMP["text2"]:setColor(1, 0, 0, 0)
confirmUIMP["b1"]:setVisible(true)
confirmUIMP["b2"]:setVisible(true)
else
confirmUIMP["text4"]:setText(action)
confirmUIMP["text3"]:setText("")
confirmUIMP["text2"]:setText("")
confirmUIMP["b1"]:setVisible(false)
confirmUIMP["b2"]:setVisible(false)
end
end
--------------------------------------------
-- Add TOC element to Health Panel
TOC_UI.onlineTempTable = {patient = nil, surgeon = nil}
TOC.RefreshClientMenu = function(_)
if mainUI:getIsVisible() == false then
Events.OnTick.Remove(TOC.RefreshClientMenu)
TOC_UI.onlineTempTable.patient = nil
TOC_UI.onlineTempTable.surgeon = nil
else
local limbs_data = TOC_UI.onlineTempTable.patient:getModData().TOC.limbs
TOC_UI.SetupMainUI(TOC_UI.onlineTempTable.patient, TOC_UI.onlineTempTable.patient, limbs_data)
end
end
TOC.RefreshOtherPlayerMenu = function(_)
if mainUI:getIsVisible() == false then
Events.OnTick.Remove(TOC.RefreshOtherPlayerMenu)
TOC_UI.onlineTempTable.patient = nil
TOC_UI.onlineTempTable.surgeon = nil
else
if ModData.get("TOC_PLAYER_DATA")[TOC_UI.onlineTempTable.patient:getUsername()] ~= nil then
local otherPlayerPartData = ModData.get("TOC_PLAYER_DATA")[TOC_UI.onlineTempTable.patient:getUsername()]
TOC_UI.SetupMainUI(TOC_UI.onlineTempTable.surgeon, TOC_UI.onlineTempTable.patient, otherPlayerPartData[1])
end
end
end
local ISHealthPanel_createChildren = ISHealthPanel.createChildren
local ISHealthPanel_render = ISHealthPanel.render
-- Add button to health panel
function ISNewHealthPanel.onClickTOC(button)
local surgeon = button.otherPlayer
local patient = button.character
TOC_UI.onlineTempTable.patient = patient
TOC_UI.onlineTempTable.surgeon = surgeon
-- MP Handling
if surgeon then
if surgeon == patient then
Events.OnTick.Add(TOC.RefreshClientMenu)
else
Events.OnTick.Add(TOC.RefreshOtherPlayerMenu) -- MP stuff, try to get the other player data and display it on the surgeon display
end
else
-- SP Handling
Events.OnTick.Add(TOC.RefreshClientMenu)
end
-- Set the correct main title
-- TODO sizes of the menu are strange in MP, they're not consistent with SP
local separatedUsername = {}
for v in string.gmatch(patient:getUsername(), "%u%l+") do
table.insert(separatedUsername, v)
end
local main_title
if separatedUsername[1] == nil then
main_title = patient:getUsername() .. " - TOC"
else
main_title = separatedUsername[1] .. " " .. separatedUsername[2] .. " - TOC"
end
mainUI:setTitle(main_title)
mainUI:toggle()
mainUI:setInCenterOfScreen()
end
function ISHealthPanel:createChildren()
ISHealthPanel_createChildren(self)
self.fitness:setWidth(self.fitness:getWidth() / 1.4)
self.TOCButton = ISButton:new(self.fitness:getRight() + 10, self.healthPanel.y, 60, 20, "", self,
ISNewHealthPanel.onClickTOC)
self.TOCButton:setImage(getTexture("media/ui/TOC/iconForMenu.png"))
self.TOCButton.anchorTop = false
self.TOCButton.anchorBottom = true
self.TOCButton:initialise()
self.TOCButton:instantiate()
self:addChild(self.TOCButton)
if getCore():getGameMode() == "Tutorial" then
self.TOCButton:setVisible(false)
end
end
function ISHealthPanel:render()
ISHealthPanel_render(self);
self.TOCButton:setY(self.fitness:getY());
end
-- EVENTS
Events.OnCreateUI.Add(TOC_UI.OnCreate)

View File

@@ -0,0 +1,50 @@
------------------------------------------
-- Compatibility Handler by Dhert
------------------------------------------
local DataController = require("TOC/Controllers/DataController")
local StaticData = require("TOC/StaticData")
local TOC_Compat = {}
-- Raw access, must pass valid part
--- @param player IsoPlayer
--- @param part string
--- @return boolean
function TOC_Compat.hasPart(player, part)
if not player or not part then return false end
local dc = DataController.GetInstance(player:getUsername())
if not dc then return false end
return (dc:getIsCut(part) and dc:getIsProstEquipped(part)) or not dc:getIsCut(part)
end
--- Check if hand is available
---@param player IsoPlayer
---@param left boolean Optional
---@return boolean
function TOC_Compat.hasHand(player, left)
return TOC_Compat.hasPart(player, ((left and StaticData.LIMBS_IND_STR.Hand_L) or StaticData.LIMBS_IND_STR.Hand_R))
end
--- Check if both hands are available
---@param player IsoPlayer
---@return boolean
function TOC_Compat.hasBothHands(player)
return TOC_Compat.hasHand(player, false) and TOC_Compat.hasHand(player, true)
end
-- This returns a number for the hands that you have
----- 11 == both hands
----- 10 == left hand
----- 01 (1) == right hand
----- 00 (0) == no hands
---@param player any
---@return integer
function TOC_Compat.getHands(player)
return ((TOC_Compat.hasHand(player, false) and 1) or 0) + ((TOC_Compat.hasHand(player, true) and 10) or 0)
end
return TOC_Compat

View File

@@ -0,0 +1,86 @@
local CommandsData = require("TOC/CommandsData")
local StaticData = require("TOC/StaticData")
local DataController = require("TOC/Controllers/DataController")
-------------------
---@param playerNum number
---@param context ISContextMenu
---@param worldobjects table
local function AddAdminTocOptions(playerNum, context, worldobjects)
if not isAdmin() then return end
local players = {}
for _, v in ipairs(worldobjects) do
for x = v:getSquare():getX() - 1, v:getSquare():getX() + 1 do
for y = v:getSquare():getY() - 1, v:getSquare():getY() + 1 do
local sq = getCell():getGridSquare(x, y, v:getSquare():getZ());
if sq then
for z = 0, sq:getMovingObjects():size() - 1 do
local o = sq:getMovingObjects():get(z)
if instanceof(o, "IsoPlayer") then
---@cast o IsoPlayer
local oId = o:getOnlineID()
players[oId] = o
end
end
end
end
end
end
for _, pl in pairs(players) do
---@cast pl IsoPlayer
local clickedPlayerNum = pl:getOnlineID()
local option = context:addOption(getText("ContextMenu_Admin_TOC") .. " - " .. pl:getUsername(), nil, nil)
local subMenu = ISContextMenu:getNew(context)
context:addSubMenu(option, subMenu)
subMenu:addOption(getText("ContextMenu_Admin_ResetTOC"), nil, function()
sendClientCommand(CommandsData.modules.TOC_RELAY, CommandsData.server.Relay.RelayExecuteInitialization,
{ patientNum = clickedPlayerNum })
end)
end
end
Events.OnFillWorldObjectContextMenu.Add(AddAdminTocOptions)
--* Override to cheats to fix stuff
local og_ISHealthPanel_onCheatCurrentPlayer = ISHealthPanel.onCheatCurrentPlayer
---Override to onCheatCurrentPlayer to fix behaviour with TOC
---@param bodyPart BodyPart
---@param action any
---@param player IsoPlayer
function ISHealthPanel.onCheatCurrentPlayer(bodyPart, action, player)
og_ISHealthPanel_onCheatCurrentPlayer(bodyPart, action, player)
local bptString = BodyPartType.ToString(bodyPart:getType())
if action == "healthFullBody" then
-- loop all limbs and reset them if infected
local dcInst = DataController.GetInstance()
for i = 1, #StaticData.LIMBS_STR do
local limbName = StaticData.LIMBS_STR[i]
dcInst:setIsInfected(limbName, false)
end
dcInst:setIsIgnoredPartInfected(false)
dcInst:apply()
end
if action == "healthFull" then
-- Get the limbName for that BodyPart and fix the values in TOC Data
local limbName = StaticData.BODYLOCS_TO_LIMBS_IND_STR[bptString]
local dcInst = DataController.GetInstance()
dcInst:setIsInfected(limbName, false)
dcInst:apply()
end
end

View File

@@ -0,0 +1,58 @@
local CommandsData = require("TOC/CommandsData")
local AmputationHandler = require("TOC/Handlers/AmputationHandler")
--------------------------------------------
local ClientRelayCommands = {}
---Initialize Amputation Handler
---@param limbName any
---@param surgeonNum any
---@return AmputationHandler
local function InitAmputationHandler(limbName, surgeonNum)
-- TODO Pretty unclean
local surgeonPl = getSpecificPlayer(surgeonNum)
local handler = AmputationHandler:new(limbName, surgeonPl)
return handler
end
---Receive the damage from another player during the amputation
---@param args receiveDamageDuringAmputationParams
function ClientRelayCommands.ReceiveDamageDuringAmputation(args)
AmputationHandler.ApplyDamageDuringAmputation(getPlayer(), args.limbName)
end
---Creates a new handler and execute the amputation function on this client
---@param args receiveExecuteAmputationActionParams
function ClientRelayCommands.ReceiveExecuteAmputationAction(args)
local handler = InitAmputationHandler(args.limbName, args.surgeonNum)
handler:execute(true)
end
--* APPLY RELAY *--
function ClientRelayCommands.ReceiveApplyFromServer()
TOC_DEBUG.print("Received forced re-apply from server")
local key = CommandsData.GetKey(getPlayer():getUsername())
ModData.request(key)
end
--* TRIGGERED BY ADMINS *--
function ClientRelayCommands.ReceiveExecuteInitialization()
local LocalPlayerController = require("TOC/Controllers/LocalPlayerController")
LocalPlayerController.InitializePlayer(true)
end
-------------------------
local function OnServerRelayCommand(module, command, args)
if module == CommandsData.modules.TOC_RELAY and ClientRelayCommands[command] then
TOC_DEBUG.print("Received Server Relay command - " .. tostring(command))
ClientRelayCommands[command](args)
end
end
Events.OnServerCommand.Add(OnServerRelayCommand)

View File

@@ -0,0 +1,48 @@
local StaticData = require("TOC/StaticData")
-----------------------------------
local CommonMethods = {}
---@param val number
---@param min number
---@param max number
function CommonMethods.Normalize(val, min, max)
if (max - min) == 0 then return 1 end
return (val - min)/(max-min)
end
function CommonMethods.GetLimbNameFromBodyPart(bodyPart)
local bodyPartTypeStr = BodyPartType.ToString(bodyPart:getType())
return StaticData.LIMBS_IND_STR[bodyPartTypeStr]
end
---Returns the side for a certain limb or prosthesis
---@param name string
---@return string "L" or "R"
function CommonMethods.GetSide(name)
if string.find(name, "_L") then return "L" else return "R" end
end
---Returns full name for the side, to be used with BodyLocations
---@param side string
---@return string
function CommonMethods.GetSideFull(side)
if side == 'R' then
return "Right"
elseif side == 'L' then
return 'Left'
end
return nil
end
---Stops and start an event, making sure that we don't stack them up
---@param event string
---@param method function
function CommonMethods.SafeStartEvent(event, method)
Events[event].Remove(method)
Events[event].Add(method)
end
return CommonMethods

View File

@@ -0,0 +1,39 @@
local function HandleModCompatibility()
local activatedMods = getActivatedMods()
TOC_DEBUG.print("Checking for mods compatibility")
--[[
Brutal hands has a TOC_COMPAT but its check is wrong and uses an old API.
]]
if activatedMods:contains('BrutalHandwork') then
TOC_DEBUG.print("found BrutalHandwork, activating compat module")
BrutalHands = BrutalHands or {}
BrutalHands.TOC = require("TOC/API")
end
--[[
Was handled inside old TOC
]]
if activatedMods:contains('FancyHandwork') then
TOC_DEBUG.print("found FancyHandwork, activating compat module")
require("TimedActions/FHSwapHandsAction")
local og_FHSwapHandsAction_isValid = FHSwapHandsAction.isValid
function FHSwapHandsAction:isValid()
local tocApi = require("TOC/API")
if tocApi.hasBothHands(self.character) then
return og_FHSwapHandsAction_isValid(self)
else
return false
end
end
end
end
Events.OnGameStart.Add(HandleModCompatibility)

View File

@@ -0,0 +1,477 @@
if isServer() then return end
local CommandsData = require("TOC/CommandsData")
local StaticData = require("TOC/StaticData")
----------------
--- An instance will be abbreviated with dcInst
--- Handle all TOC mod data related stuff
---@class DataController
---@field username string
---@field tocData tocModDataType
---@field isDataReady boolean
---@field isResetForced boolean
local DataController = {}
DataController.instances = {}
---Setup a new Mod Data Handler
---@param username string
---@param isResetForced boolean?
---@return DataController
function DataController:new(username, isResetForced)
TOC_DEBUG.print("Creating new DataController instance for " .. username)
---@type DataController
---@diagnostic disable-next-line: missing-fields
local o = {}
setmetatable(o, self)
self.__index = self
o.username = username
o.isResetForced = isResetForced or false
o.isDataReady = false
-- We're gonna set it already from here, to prevent it from looping in SP (in case we need to fetch this instance)
DataController.instances[username] = o
local key = CommandsData.GetKey(username)
if isClient() then
-- In MP, we request the data from the server to trigger DataController.ReceiveData
ModData.request(key)
else
-- In SP, we handle it with another function which will reference the saved instance in DataController.instances
o:initSinglePlayer(key)
end
return o
end
---Setup a new toc mod data data class
---@param key string
function DataController:setup(key)
TOC_DEBUG.print("Running setup")
---@type tocModDataType
self.tocData = {
-- Generic stuff that does not belong anywhere else
isInitializing = true,
isIgnoredPartInfected = false,
isAnyLimbCut = false,
limbs = {},
prostheses = {}
}
---@type partDataType
local defaultParams = {
isCut = false, isInfected = false, isOperated = false, isCicatrized = false, isCauterized = false,
woundDirtyness = -1, cicatrizationTime = -1,
isVisible = false
}
-- Initialize limbs
for i=1, #StaticData.LIMBS_STR do
local limbName = StaticData.LIMBS_STR[i]
self.tocData.limbs[limbName] = {}
self:setLimbParams(StaticData.LIMBS_STR[i], defaultParams, 0)
end
-- Initialize prostheses stuff
for i=1, #StaticData.AMP_GROUPS_STR do
local group = StaticData.AMP_GROUPS_STR[i]
self.tocData.prostheses[group] = {
isProstEquipped = false,
prostFactor = 0,
}
end
-- Add it to client global mod data
ModData.add(key, self.tocData)
-- Sync with the server
self:apply()
-- -- Disable lock
-- self.tocData.isInitializing = false
-- ModData.add(key, self.tocData)
end
---In case of desync between the table on ModData and the table here
---@param tocData tocModDataType
function DataController:applyOnlineData(tocData)
local key = CommandsData.GetKey(self.username)
ModData.add(key, tocData)
self.tocData = ModData.get(key)
end
---@param key string
function DataController:tryLoadLocalData(key)
self.tocData = ModData.get(key)
--TOC_DEBUG.printTable(self.tocData)
if self.tocData and self.tocData.limbs then
TOC_DEBUG.print("Found and loaded local data")
else
TOC_DEBUG.print("Local data failed to load! Running setup")
self:setup(key)
end
end
-----------------
--* Setters *--
---@param isDataReady boolean
function DataController:setIsDataReady(isDataReady)
self.isDataReady = isDataReady
end
---@param isResetForced boolean
function DataController:setIsResetForced(isResetForced)
self.isResetForced = isResetForced
end
---Set a generic boolean that toggles varies function of the mod
---@param isAnyLimbCut boolean
function DataController:setIsAnyLimbCut(isAnyLimbCut)
self.tocData.isAnyLimbCut = isAnyLimbCut
end
---Set isIgnoredPartInfected
---@param isIgnoredPartInfected boolean
function DataController:setIsIgnoredPartInfected(isIgnoredPartInfected)
self.tocData.isIgnoredPartInfected = isIgnoredPartInfected
end
---Set isCut
---@param limbName string
---@param isCut boolean
function DataController:setIsCut(limbName, isCut)
self.tocData.limbs[limbName].isCut = isCut
end
---Set isInfected
---@param limbName string
---@param isInfected boolean
function DataController:setIsInfected(limbName, isInfected)
self.tocData.limbs[limbName].isInfected = isInfected
end
---Set isCicatrized
---@param limbName string
---@param isCicatrized boolean
function DataController:setIsCicatrized(limbName, isCicatrized)
self.tocData.limbs[limbName].isCicatrized = isCicatrized
end
---Set isCauterized
---@param limbName string
---@param isCauterized boolean
function DataController:setIsCauterized(limbName, isCauterized)
self.tocData.limbs[limbName].isCauterized = isCauterized
end
---Set woundDirtyness
---@param limbName string
---@param woundDirtyness number
function DataController:setWoundDirtyness(limbName, woundDirtyness)
self.tocData.limbs[limbName].woundDirtyness = woundDirtyness
end
---Set cicatrizationTime
---@param limbName string
---@param cicatrizationTime number
function DataController:setCicatrizationTime(limbName, cicatrizationTime)
self.tocData.limbs[limbName].cicatrizationTime = cicatrizationTime
end
---Set isProstEquipped
---@param group string
---@param isProstEquipped boolean
function DataController:setIsProstEquipped(group, isProstEquipped)
self.tocData.prostheses[group].isProstEquipped = isProstEquipped
end
---Set prostFactor
---@param group string
---@param prostFactor number
function DataController:setProstFactor(group, prostFactor)
self.tocData.prostheses[group].prostFactor = prostFactor
end
-----------------
--* Getters *--
---comment
---@return boolean
function DataController:getIsDataReady()
return self.isDataReady
end
---Set a generic boolean that toggles varies function of the mod
---@return boolean
function DataController:getIsAnyLimbCut()
if not self.isDataReady then return false end
return self.tocData.isAnyLimbCut
end
---Get isIgnoredPartInfected
---@return boolean
function DataController:getIsIgnoredPartInfected()
if not self.isDataReady then return false end
return self.tocData.isIgnoredPartInfected
end
---Get isCut
---@param limbName string
---@return boolean
function DataController:getIsCut(limbName)
if not self.isDataReady then return false end
if self.tocData.limbs[limbName] then
return self.tocData.limbs[limbName].isCut
else
return false
end
end
---Get isVisible
---@param limbName string
---@return boolean
function DataController:getIsVisible(limbName)
if not self.isDataReady then return false end
return self.tocData.limbs[limbName].isVisible
end
---Get isCicatrized
---@param limbName string
---@return boolean
function DataController:getIsCicatrized(limbName)
if not self.isDataReady then return false end
return self.tocData.limbs[limbName].isCicatrized
end
---Get isCauterized
---@param limbName string
---@return boolean
function DataController:getIsCauterized(limbName)
if not self.isDataReady then return false end
return self.tocData.limbs[limbName].isCauterized
end
---Get isInfected
---@param limbName string
---@return boolean
function DataController:getIsInfected(limbName)
return self.tocData.limbs[limbName].isInfected
end
---Get woundDirtyness
---@param limbName string
---@return number
function DataController:getWoundDirtyness(limbName)
if not self.isDataReady then return -1 end
return self.tocData.limbs[limbName].woundDirtyness
end
---Get cicatrizationTime
---@param limbName string
---@return number
function DataController:getCicatrizationTime(limbName)
if not self.isDataReady then return -1 end
return self.tocData.limbs[limbName].cicatrizationTime
end
---Get isProstEquipped
---@param limbName string
---@return boolean
function DataController:getIsProstEquipped(limbName)
local prostGroup = StaticData.LIMBS_TO_AMP_GROUPS_MATCH_IND_STR[limbName]
return self.tocData.prostheses[prostGroup].isProstEquipped
end
---Get prostFactor
---@param group string
---@return number
function DataController:getProstFactor(group)
return self.tocData.prostheses[group].prostFactor
end
--* Limbs data handling *--
---Set a limb and its dependend limbs as cut
---@param limbName string
---@param isOperated boolean
---@param isCicatrized boolean
---@param isCauterized boolean
---@param surgeonFactor number?
function DataController:setCutLimb(limbName, isOperated, isCicatrized, isCauterized, surgeonFactor)
local cicatrizationTime = 0
if isCicatrized == false or isCauterized == false then
cicatrizationTime = StaticData.LIMBS_CICATRIZATION_TIME_IND_NUM[limbName] - surgeonFactor
end
---@type partDataType
local params = {isCut = true, isInfected = false, isOperated = isOperated, isCicatrized = isCicatrized, isCauterized = isCauterized, woundDirtyness = 0, isVisible = true}
self:setLimbParams(limbName, params, cicatrizationTime)
for i=1, #StaticData.LIMBS_DEPENDENCIES_IND_STR[limbName] do
local dependedLimbName = StaticData.LIMBS_DEPENDENCIES_IND_STR[limbName][i]
-- We don't care about isOperated, isCicatrized, isCauterized since this is depending on another limb
-- Same story for cicatrizationTime, which will be 0
-- isCicatrized is to true to prevent it from doing the cicatrization process
self:setLimbParams(dependedLimbName, {isCut = true, isInfected = false, isVisible = false, isCicatrized = true}, 0)
end
-- Set that a limb has been cut, to activate some functions without having to loop through the parts
self:setIsAnyLimbCut(true)
-- TODO In theory we should cache data from here, not AmputationHandler
end
---Set a limb data
---@param limbName string
---@param ampStatus partDataType {isCut, isInfected, isOperated, isCicatrized, isCauterized, isVisible}
---@param cicatrizationTime integer?
function DataController:setLimbParams(limbName, ampStatus, cicatrizationTime)
local limbData = self.tocData.limbs[limbName]
if ampStatus.isCut ~= nil then limbData.isCut = ampStatus.isCut end
if ampStatus.isInfected ~= nil then limbData.isInfected = ampStatus.isInfected end
if ampStatus.isOperated ~= nil then limbData.isOperated = ampStatus.isOperated end
if ampStatus.isCicatrized ~= nil then limbData.isCicatrized = ampStatus.isCicatrized end
if ampStatus.isCauterized ~= nil then limbData.isCauterized = ampStatus.isCauterized end
if ampStatus.woundDirtyness ~= nil then limbData.woundDirtyness = ampStatus.woundDirtyness end
if ampStatus.isVisible ~= nil then limbData.isVisible = ampStatus.isVisible end
if cicatrizationTime ~= nil then limbData.cicatrizationTime = cicatrizationTime end
end
--* Update statuses of a limb *--
---Decreases the cicatrization time
---@param limbName string
function DataController:decreaseCicatrizationTime(limbName)
self.tocData.limbs[limbName].cicatrizationTime = self.tocData.limbs[limbName].cicatrizationTime - 1
end
--* Global Mod Data Handling *--
function DataController:apply()
TOC_DEBUG.print("Applying data for " .. self.username)
ModData.transmit(CommandsData.GetKey(self.username))
-- if getPlayer():getUsername() ~= self.username then
-- sendClientCommand(CommandsData.modules.TOC_RELAY, CommandsData.server.Relay.RelayApplyFromOtherClient, {patientUsername = self.username} )
-- -- force request from the server for that other client...
-- end
end
---Online only, Global Mod Data doesn't trigger this in SP
---@param key string
---@param data tocModDataType
function DataController.ReceiveData(key, data)
-- During startup the game can return Bob as the player username, adding a useless ModData table
if key == "TOC_Bob" then return end
if not luautils.stringStarts(key, StaticData.MOD_NAME .. "_") then return end
TOC_DEBUG.print("ReceiveData for " .. key)
if data == {} or data == nil then
error("Data is nil, new character or something is wrong")
end
-- Get DataController instance if there was none for that user and reapply the correct ModData table as a reference
local username = key:sub(5)
local handler = DataController.GetInstance(username)
-- Bit of a workaround, but in a perfect world, I'd use the server to get the data and that would be it.
-- but Zomboid Mod Data handling is too finnicky at best to be that reliable, in case of an unwanted disconnection and what not,
-- so for now, I'm gonna assume that the local data (for the local client) is the
-- most recent (and correct) one instead of trying to fetch it from the server every single time
-- TODO Add update from server scenario
if handler.isResetForced then
TOC_DEBUG.print("Forced reset")
handler:setup(key)
elseif data then
if data.isUpdateFromServer then
TOC_DEBUG.print("Update from the server")
end
handler:applyOnlineData(data)
elseif username == getPlayer():getUsername() then
TOC_DEBUG.print("loading local data")
handler:tryLoadLocalData(key)
end
handler:setIsResetForced(false)
handler:setIsDataReady(true)
triggerEvent("OnReceivedTocData", handler.username)
-- if username == getPlayer():getUsername() and not handler.isResetForced then
-- handler:loadLocalData(key)
-- elseif handler.isResetForced or data == nil then
-- TOC_DEBUG.print("Data is nil or empty!?")
-- TOC_DEBUG.printTable(data)
-- handler:setup(key)
-- elseif data and data.limbs then
-- handler:applyOnlineData(data)
-- end
-- handler:setIsResetForced(false)
-- handler:setIsDataReady(true)
-- -- Event, triggers caching
-- triggerEvent("OnReceivedTocData", handler.username)
-- Transmit it back to the server
--ModData.transmit(key)
--TOC_DEBUG.print("Transmitting data after receiving it for: " .. handler.username)
end
Events.OnReceiveGlobalModData.Add(DataController.ReceiveData)
--- SP Only initialization
---@param key string
function DataController:initSinglePlayer(key)
self:tryLoadLocalData(key)
if self.tocData == nil or self.isResetForced then
self:setup(key)
end
self:setIsDataReady(true)
self:setIsResetForced(false)
-- Event, triggers caching
triggerEvent("OnReceivedTocData", self.username)
end
-------------------
---@param username string?
---@return DataController
function DataController.GetInstance(username)
if username == nil or username == "Bob" then
username = getPlayer():getUsername()
end
if DataController.instances[username] == nil then
TOC_DEBUG.print("Creating NEW instance for " .. username)
return DataController:new(username)
else
return DataController.instances[username]
end
end
return DataController

View File

@@ -0,0 +1,174 @@
local StaticData = require("TOC/StaticData")
local CommonMethods = require("TOC/CommonMethods")
---------------------------
--- Submodule to handle spawning the correct items after certain actions (ie: cutting a hand). LOCAL ONLY!
---@class ItemsController
local ItemsController = {}
--* Player Methods *--
---@class ItemsController.Player
ItemsController.Player = {}
---Returns the correct index for the textures of the amputation
---@param playerObj IsoPlayer
---@param isCicatrized boolean
---@return number
---@private
function ItemsController.Player.GetAmputationTexturesIndex(playerObj, isCicatrized)
local textureString = playerObj:getHumanVisual():getSkinTexture()
local isHairy = textureString:sub(-1) == "a"
local matchedIndex = tonumber(textureString:match("%d$")) or 0
if isHairy then
matchedIndex = matchedIndex + 5
end
if isCicatrized then
matchedIndex = matchedIndex + (isHairy and 5 or 10) -- We add 5 is it's the texture, else 10
end
TOC_DEBUG.print("isCicatrized= " .. tostring(isCicatrized))
TOC_DEBUG.print("Amputation Texture Index: " .. tostring(matchedIndex))
return matchedIndex - 1
end
---Main function to delete a clothing item
---@param playerObj IsoPlayer
---@param clothingItem InventoryItem
---@return boolean
---@private
function ItemsController.Player.RemoveClothingItem(playerObj, clothingItem)
if clothingItem and instanceof(clothingItem, "InventoryItem") then
playerObj:removeWornItem(clothingItem)
---@diagnostic disable-next-line: param-type-mismatch
playerObj:getInventory():Remove(clothingItem) -- Umbrella is wrong, can be an InventoryItem too
TOC_DEBUG.print("found and deleted" .. tostring(clothingItem))
-- Reset model
playerObj:resetModelNextFrame()
return true
end
return false
end
---Search and deletes an old amputation clothing item on the same side
---@param playerObj IsoPlayer
---@param limbName string
function ItemsController.Player.DeleteOldAmputationItem(playerObj, limbName)
local side = CommonMethods.GetSide(limbName)
for partName, _ in pairs(StaticData.PARTS_IND_STR) do
local othLimbName = partName .. "_" .. side
local othClothingItemName = StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. othLimbName
local othClothingItem = playerObj:getInventory():FindAndReturn(othClothingItemName)
-- If we manage to find and remove an item, then we should stop this function.
---@cast othClothingItem InventoryItem
if ItemsController.Player.RemoveClothingItem(playerObj, othClothingItem) then return end
end
end
---Deletes all the old amputation items, used for resets
---@param playerObj IsoPlayer
function ItemsController.Player.DeleteAllOldAmputationItems(playerObj)
-- This part is a workaround for a pretty shitty implementation on the java side. Check ProsthesisHandler for more infos
local group = BodyLocations.getGroup("Human")
group:setMultiItem("TOC_Arm", false)
group:setMultiItem("TOC_ArmProst", false)
for i = 1, #StaticData.LIMBS_STR do
local limbName = StaticData.LIMBS_STR[i]
local clothItemName = StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limbName
local clothItem = playerObj:getInventory():FindAndReturn(clothItemName)
---@cast clothItem InventoryItem
ItemsController.Player.RemoveClothingItem(playerObj, clothItem)
end
-- Reset model just in case
playerObj:resetModel()
group:setMultiItem("TOC_Arm", true)
group:setMultiItem("TOC_ArmProst", true)
end
---Spawns and equips the correct amputation item to the player.
---@param playerObj IsoPlayer
---@param limbName string
function ItemsController.Player.SpawnAmputationItem(playerObj, limbName)
TOC_DEBUG.print("clothing name " .. StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limbName)
local clothingItem = playerObj:getInventory():AddItem(StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limbName)
local texId = ItemsController.Player.GetAmputationTexturesIndex(playerObj, false)
---@cast clothingItem InventoryItem
clothingItem:getVisual():setTextureChoice(texId) -- it counts from 0, so we have to subtract 1
playerObj:setWornItem(clothingItem:getBodyLocation(), clothingItem)
end
---Search through worn items and modifies a specific amputation item
---@param playerObj IsoPlayer
---@param limbName string
---@param isCicatrized boolean
function ItemsController.Player.OverrideAmputationItemVisuals(playerObj, limbName, isCicatrized)
local wornItems = playerObj:getWornItems()
local fullType = StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limbName
for i = 1, wornItems:size() do
local it = wornItems:get(i - 1)
if it then
local wornItem = wornItems:get(i - 1):getItem()
TOC_DEBUG.print(wornItem:getFullType())
if wornItem:getFullType() == fullType then
TOC_DEBUG.print("Found amputation item for " .. limbName)
-- change it here
local texId = ItemsController.Player.GetAmputationTexturesIndex(playerObj, isCicatrized)
wornItem:getVisual():setTextureChoice(texId)
playerObj:resetModelNextFrame() -- necessary to update the model
return
end
end
end
end
--* Zombie Methods *--
---@class ItemsController.Zombie
ItemsController.Zombie = {}
---Set an amputation to a zombie
---@param zombie IsoZombie
---@param amputationFullType string Full Type
function ItemsController.Zombie.SpawnAmputationItem(zombie, amputationFullType)
local texId = ItemsController.Zombie.GetAmputationTexturesIndex(zombie)
local zombieVisuals = zombie:getItemVisuals()
local itemVisual = ItemVisual:new()
itemVisual:setItemType(amputationFullType)
itemVisual:setTextureChoice(texId)
if zombieVisuals then zombieVisuals:add(itemVisual) end
zombie:resetModelNextFrame()
-- Spawn the item too in the inventory to keep track of stuff this way. It's gonna get deleted when we reload the game
local zombieInv = zombie:getInventory()
zombieInv:AddItem(amputationFullType)
end
function ItemsController.Zombie.GetAmputationTexturesIndex(zombie)
local x = zombie:getHumanVisual():getSkinTexture()
-- Starting ID for zombies = 20
-- 3 levels
local matchedIndex = tonumber(x:match("ZedBody0(%d)")) - 1
matchedIndex = matchedIndex * 3
local level = tonumber(x:match("%d$")) - 1 -- it's from 1 to 3, but we're using it like 0 indexed arrays
local finalId = 20 + matchedIndex + level
--print("Zombie texture index: " .. tostring(finalId))
return finalId
end
return ItemsController

View File

@@ -0,0 +1,311 @@
local LocalPlayerController = require("TOC/Controllers/LocalPlayerController")
local DataController = require("TOC/Controllers/DataController")
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
local CommonMethods = require("TOC/CommonMethods")
local StaticData = require("TOC/StaticData")
-----------------
--* TIMED ACTIONS *--
-- We want to be able to modify how long actions are gonna take,
-- depending on amputation status and kind of action. Also, when the
-- player has not completely cicatrized their own wounds, and try to do any action with
-- a prosthesis on, that can trigger random bleeds.
local function CheckHandFeasibility(limbName)
TOC_DEBUG.print("Checking hand feasibility: " .. limbName)
local dcInst = DataController.GetInstance()
local isFeasible = not dcInst:getIsCut(limbName) or dcInst:getIsProstEquipped(limbName)
TOC_DEBUG.print("isFeasible: " .. tostring(isFeasible))
return isFeasible
end
--* Time to perform actions overrides *--
local og_ISBaseTimedAction_adjustMaxTime = ISBaseTimedAction.adjustMaxTime
--- Adjust time
---@diagnostic disable-next-line: duplicate-set-field
function ISBaseTimedAction:adjustMaxTime(maxTime)
local time = og_ISBaseTimedAction_adjustMaxTime(self, maxTime)
-- Exceptions handling, if we find that parameter then we just use the original time
local queue = ISTimedActionQueue.getTimedActionQueue(getPlayer())
if queue and queue.current and queue.current.skipTOC then return time end
-- Action is valid, check if we have any cut limb and then modify maxTime
local dcInst = DataController.GetInstance()
if time ~= -1 and dcInst and dcInst:getIsAnyLimbCut() then
local pl = getPlayer()
local amputatedLimbs = CachedDataHandler.GetAmputatedLimbs(pl:getUsername())
for k, _ in pairs(amputatedLimbs) do
local limbName = k
--if dcInst:getIsCut(limbName) then
local perk = Perks["Side_" .. CommonMethods.GetSide(limbName)]
local perkLevel = pl:getPerkLevel(perk)
local perkLevelScaled
if perkLevel ~= 0 then perkLevelScaled = perkLevel / 10 else perkLevelScaled = 0 end
time = time * (StaticData.LIMBS_TIME_MULTIPLIER_IND_NUM[limbName] - perkLevelScaled)
--end
end
end
return time
end
--* Random bleeding during cicatrization + Perks leveling override *--
local og_ISBaseTimedAction_perform = ISBaseTimedAction.perform
--- After each action, level up perks
---@diagnostic disable-next-line: duplicate-set-field
function ISBaseTimedAction:perform()
og_ISBaseTimedAction_perform(self)
TOC_DEBUG.print("Running ISBaseTimedAction.perform override")
local dcInst = DataController.GetInstance()
if not dcInst:getIsAnyLimbCut() then return end
local amputatedLimbs = CachedDataHandler.GetAmputatedLimbs(LocalPlayerController.username)
for k, _ in pairs(amputatedLimbs) do
local limbName = k
-- We're checking for only "visible" amputations to prevent from having bleeds everywhere
if dcInst:getIsCut(limbName) and dcInst:getIsVisible(limbName) then
local side = CommonMethods.GetSide(limbName)
LocalPlayerController.playerObj:getXp():AddXP(Perks["Side_" .. side], 1) -- TODO Make it dynamic
if not dcInst:getIsCicatrized(limbName) and dcInst:getIsProstEquipped(limbName) then
TOC_DEBUG.print("Trying for bleed, player met the criteria")
LocalPlayerController.TryRandomBleed(self.character, limbName)
end
end
end
end
--* EQUIPPING ITEMS *--
-- Check wheter the player can equip items or not, for example dual wielding when you only have one
-- hand (and no prosthesis) should be disabled. Same thing for some werable items, like watches.
---@class ISEquipWeaponAction
---@field character IsoPlayer
--* Equipping items overrides *--
local og_ISEquipWeaponAction_isValid = ISEquipWeaponAction.isValid
---Add a condition to check the feasibility of having 2 handed weapons or if both arms are cut off
---@return boolean
---@diagnostic disable-next-line: duplicate-set-field
function ISEquipWeaponAction:isValid()
local isValid = og_ISEquipWeaponAction_isValid(self)
if isValid then
local isPrimaryHandValid = CachedDataHandler.GetHandFeasibility(StaticData.SIDES_IND_STR.R)
local isSecondaryHandValid = CachedDataHandler.GetHandFeasibility(StaticData.SIDES_IND_STR.L)
-- Both hands are cut off, so it's impossible to equip in any way
if not isPrimaryHandValid and not isSecondaryHandValid then
isValid = false
end
end
return isValid
end
---A recreation of the original method, but with amputations in mind
---@param dcInst DataController
function ISEquipWeaponAction:performWithAmputation(dcInst)
TOC_DEBUG.print("running ISEquipWeaponAction performWithAmputation")
local hand = nil
local otherHand = nil
local getMethodFirst = nil
local setMethodFirst = nil
local getMethodSecond = nil
local setMethodSecond = nil
if self.primary then
hand = StaticData.LIMBS_IND_STR.Hand_R
otherHand = StaticData.LIMBS_IND_STR.Hand_L
getMethodFirst = self.character.getSecondaryHandItem
setMethodFirst = self.character.setSecondaryHandItem
getMethodSecond = self.character.getPrimaryHandItem
setMethodSecond = self.character.setPrimaryHandItem
else
hand = StaticData.LIMBS_IND_STR.Hand_L
otherHand = StaticData.LIMBS_IND_STR.Hand_R
getMethodFirst = self.character.getPrimaryHandItem
setMethodFirst = self.character.setPrimaryHandItem
getMethodSecond = self.character.getSecondaryHandItem
setMethodSecond = self.character.setSecondaryHandItem
end
local isFirstValid = CheckHandFeasibility(hand)
local isSecondValid = CheckHandFeasibility(otherHand)
if not self.twoHands then
if getMethodFirst(self.character) and getMethodFirst(self.character):isRequiresEquippedBothHands() then
setMethodFirst(self.character, nil)
-- if this weapon is already equiped in the 2nd hand, we remove it
elseif (getMethodFirst(self.character) == self.item or getMethodFirst(self.character) == getMethodSecond(self.character)) then
setMethodFirst(self.character, nil)
-- if we are equipping a handgun and there is a weapon in the secondary hand we remove it
elseif instanceof(self.item, "HandWeapon") and self.item:getSwingAnim() and self.item:getSwingAnim() == "Handgun" then
if getMethodFirst(self.character) and instanceof(getMethodFirst(self.character), "HandWeapon") then
setMethodFirst(self.character, nil)
end
else
setMethodSecond(self.character, nil)
-- TODO We should use the CachedData indexable instead of dcInst
if isFirstValid then
setMethodSecond(self.character, self.item)
-- Check other HAND!
elseif isSecondValid then
setMethodFirst(self.character, self.item)
end
end
else
setMethodFirst(self.character, nil)
setMethodSecond(self.character, nil)
-- TOC_DEBUG.print("First Hand: " .. tostring(hand))
-- --TOC_DEBUG.print("Prost Group: " .. tostring(prostGroup))
-- TOC_DEBUG.print("Other Hand: " .. tostring(otherHand))
-- --TOC_DEBUG.print("Other Prost Group: " .. tostring(otherProstGroup))
-- TOC_DEBUG.print("isPrimaryHandValid: " .. tostring(isFirstValid))
-- TOC_DEBUG.print("isSecondaryHandValid: " .. tostring(isSecondValid))
if isFirstValid then
setMethodSecond(self.character, self.item)
end
if isSecondValid then
setMethodFirst(self.character, self.item)
end
end
end
local og_ISEquipWeaponAction_perform = ISEquipWeaponAction.perform
---@diagnostic disable-next-line: duplicate-set-field
function ISEquipWeaponAction:perform()
og_ISEquipWeaponAction_perform(self)
-- TODO Can we do it earlier?
local dcInst = DataController.GetInstance(self.character:getUsername())
-- Just check it any limb has been cut. If not, we can just return from here
if dcInst:getIsAnyLimbCut() == true then
self:performWithAmputation(dcInst)
end
end
function ISInventoryPaneContextMenu.doEquipOption(context, playerObj, isWeapon, items, player)
-- check if hands if not heavy damaged
if (not playerObj:isPrimaryHandItem(isWeapon) or (playerObj:isPrimaryHandItem(isWeapon) and playerObj:isSecondaryHandItem(isWeapon))) and not getSpecificPlayer(player):getBodyDamage():getBodyPart(BodyPartType.Hand_R):isDeepWounded() and (getSpecificPlayer(player):getBodyDamage():getBodyPart(BodyPartType.Hand_R):getFractureTime() == 0 or getSpecificPlayer(player):getBodyDamage():getBodyPart(BodyPartType.Hand_R):getSplintFactor() > 0) then
-- forbid reequipping skinned items to avoid multiple problems for now
local add = true
if playerObj:getSecondaryHandItem() == isWeapon and isWeapon:getScriptItem():getReplaceWhenUnequip() then
add = false
end
if add then
local equipOption = context:addOption(getText("ContextMenu_Equip_Primary"), items,
ISInventoryPaneContextMenu.OnPrimaryWeapon, player)
equipOption.notAvailable = not CheckHandFeasibility(StaticData.LIMBS_IND_STR.Hand_R)
end
end
if (not playerObj:isSecondaryHandItem(isWeapon) or (playerObj:isPrimaryHandItem(isWeapon) and playerObj:isSecondaryHandItem(isWeapon))) and not getSpecificPlayer(player):getBodyDamage():getBodyPart(BodyPartType.Hand_L):isDeepWounded() and (getSpecificPlayer(player):getBodyDamage():getBodyPart(BodyPartType.Hand_L):getFractureTime() == 0 or getSpecificPlayer(player):getBodyDamage():getBodyPart(BodyPartType.Hand_L):getSplintFactor() > 0) then
-- forbid reequipping skinned items to avoid multiple problems for now
local add = true
if playerObj:getPrimaryHandItem() == isWeapon and isWeapon:getScriptItem():getReplaceWhenUnequip() then
add = false
end
if add then
local equipOption = context:addOption(getText("ContextMenu_Equip_Secondary"), items,
ISInventoryPaneContextMenu.OnSecondWeapon, player)
equipOption.notAvailable = not CheckHandFeasibility(StaticData.LIMBS_IND_STR.Hand_L)
end
end
end
local noHandsImpossibleActions = {
getText("ContextMenu_Add_escape_rope_sheet"),
getText("ContextMenu_Add_escape_rope"),
getText("ContextMenu_Remove_escape_rope"),
getText("ContextMenu_Barricade"),
getText("ContextMenu_Unbarricade"),
getText("ContextMenu_MetalBarricade"),
getText("ContextMenu_MetalBarBarricade"),
getText("ContextMenu_Open_window"),
getText("ContextMenu_Close_window"),
getText("ContextMenu_PickupBrokenGlass"),
getText("ContextMenu_Open_door"),
getText("ContextMenu_Close_door"),
}
local og_ISWorldObjectContextMenu_createMenu = ISWorldObjectContextMenu.createMenu
---@param player integer
---@param worldobjects any
---@param x any
---@param y any
---@param test any
function ISWorldObjectContextMenu.createMenu(player, worldobjects, x, y, test)
---@type ISContextMenu
local ogContext = og_ISWorldObjectContextMenu_createMenu(player, worldobjects, x, y, test)
-- check if no hands, disable various interactions
if not CachedDataHandler.GetBothHandsFeasibility() then
TOC_DEBUG.print("NO hands :((")
for i = 1, #noHandsImpossibleActions do
local optionName = noHandsImpossibleActions[i]
local option = ogContext:getOptionFromName(optionName)
if option then
option.notAvailable = true
end
end
end
return ogContext
end
--* DISABLE WEARING CERTAIN ITEMS WHEN NO LIMB
local function CheckLimbFeasibility(limbName)
local dcInst = DataController.GetInstance()
local isFeasible = not dcInst:getIsCut(limbName) or dcInst:getIsProstEquipped(limbName)
--TOC_DEBUG.print("isFeasible="..tostring(isFeasible))
return isFeasible
end
---@param obj any
---@param wrappedFunc function
---@param item InventoryItem
---@return boolean
local function WrapClothingAction(obj, wrappedFunc, item)
local isEquippable = wrappedFunc(obj)
if not isEquippable then return isEquippable end
local itemBodyLoc = item:getBodyLocation()
local limbToCheck = StaticData.AFFECTED_BODYLOCS_TO_LIMBS_IND_STR[itemBodyLoc]
if CheckLimbFeasibility(limbToCheck) then return isEquippable else return false end
end
---@diagnostic disable-next-line: duplicate-set-field
local og_ISWearClothing_isValid = ISWearClothing.isValid
function ISWearClothing:isValid()
return WrapClothingAction(self, og_ISWearClothing_isValid, self.item)
end
local og_ISClothingExtraAction_isValid = ISClothingExtraAction.isValid
---@diagnostic disable-next-line: duplicate-set-field
function ISClothingExtraAction:isValid()
return WrapClothingAction(self, og_ISClothingExtraAction_isValid, InventoryItemFactory.CreateItem(self.extra))
end

View File

@@ -0,0 +1,374 @@
local DataController = require("TOC/Controllers/DataController")
local CommonMethods = require("TOC/CommonMethods")
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
local StaticData = require("TOC/StaticData")
require("TOC/Events")
-----------
-- Handle ONLY stuff for the local client
---@class LocalPlayerController
---@field playerObj IsoPlayer
---@field username string
---@field hasBeenDamaged boolean
local LocalPlayerController = {}
--* Initialization
---Setup the Player Handler and modData, only for local client
---@param isForced boolean?
function LocalPlayerController.InitializePlayer(isForced)
local playerObj = getPlayer()
local username = playerObj:getUsername()
TOC_DEBUG.print("Initializing local player: " .. username)
DataController:new(username, isForced)
LocalPlayerController.playerObj = playerObj
LocalPlayerController.username = username
--Setup the CicatrizationUpdate event and triggers it once
Events.OnAmputatedLimb.Add(LocalPlayerController.ToggleUpdateAmputations)
LocalPlayerController.ToggleUpdateAmputations()
-- Manage their traits
LocalPlayerController.ManageTraits(playerObj)
-- Since isForced is used to reset an existing player data, we're gonna clean their ISHealthPanel table too
if isForced then
local ItemsController = require("TOC/Controllers/ItemsController")
ItemsController.Player.DeleteAllOldAmputationItems(playerObj)
CachedDataHandler.Setup(username)
end
-- Set a bool to use an overriding GetDamagedParts
SetHealthPanelTOC()
end
---Handles the traits
---@param playerObj IsoPlayer
function LocalPlayerController.ManageTraits(playerObj)
local AmputationHandler = require("TOC/Handlers/AmputationHandler")
for k, v in pairs(StaticData.TRAITS_BP) do
if playerObj:HasTrait(k) then
-- Once we find one, we should be done since they're exclusive
local tempHandler = AmputationHandler:new(v, playerObj)
tempHandler:execute(false) -- No damage
tempHandler:close()
-- The wound should be already cicatrized
LocalPlayerController.HandleSetCicatrization(DataController.GetInstance(), playerObj, v)
return
end
end
end
----------------------------------------------------------
--* Health *--
---Used to heal an area that has been cut previously. There's an exception for bites, those are managed differently
---@param bodyPart BodyPart
function LocalPlayerController.HealArea(bodyPart)
bodyPart:setFractureTime(0)
bodyPart:setScratched(false, true)
bodyPart:setScratchTime(0)
bodyPart:setBleeding(false)
bodyPart:setBleedingTime(0)
bodyPart:SetBitten(false)
bodyPart:setBiteTime(0)
bodyPart:setCut(false)
bodyPart:setCutTime(0)
bodyPart:setDeepWounded(false)
bodyPart:setDeepWoundTime(0)
bodyPart:setHaveBullet(false, 0)
bodyPart:setHaveGlass(false)
bodyPart:setSplint(false, 0)
end
---@param bodyDamage BodyDamage
---@param bodyPart BodyPart
---@param limbName string
---@param dcInst DataController
function LocalPlayerController.HealZombieInfection(bodyDamage, bodyPart, limbName, dcInst)
if bodyDamage:isInfected() == false then return end
bodyDamage:setInfected(false)
bodyDamage:setInfectionMortalityDuration(-1)
bodyDamage:setInfectionTime(-1)
bodyDamage:setInfectionLevel(-1)
bodyPart:SetInfected(false)
dcInst:setIsInfected(limbName, false)
dcInst:apply()
end
---@param character IsoPlayer
---@param limbName string
function LocalPlayerController.TryRandomBleed(character, limbName)
-- Chance should be determined by the cicatrization time
local cicTime = DataController.GetInstance():getCicatrizationTime(limbName)
if cicTime == 0 then return end
-- TODO This is just a placeholder, we need to figure out a better way to calculate this chance
local normCicTime = CommonMethods.Normalize(cicTime, 0, StaticData.LIMBS_CICATRIZATION_TIME_IND_NUM[limbName]) / 2
TOC_DEBUG.print("OG cicTime: " .. tostring(cicTime))
TOC_DEBUG.print("Normalized cic time : " .. tostring(normCicTime))
local chance = ZombRandFloat(0.0, 1.0)
if chance > normCicTime then
TOC_DEBUG.print("Triggered bleeding from non cicatrized wound")
local adjacentBodyPartType = BodyPartType[StaticData.LIMBS_ADJACENT_IND_STR[limbName]]
character:getBodyDamage():getBodyPart(adjacentBodyPartType):setBleeding(true)
character:getBodyDamage():getBodyPart(adjacentBodyPartType):setBleedingTime(20)
end
end
-------------------------
--* Damage handling *--
--- Locks OnPlayerGetDamage event, to prevent it from getting spammed constantly
LocalPlayerController.hasBeenDamaged = false
---Check if the player has in infected body part or if they have been hit in a cut area
---@param character IsoPlayer|IsoGameCharacter
function LocalPlayerController.HandleDamage(character)
-- TOC_DEBUG.print("Player got hit!")
-- TOC_DEBUG.print(damageType)
if character ~= getPlayer() then return end
local bd = character:getBodyDamage()
local dcInst = DataController.GetInstance()
local modDataNeedsUpdate = false
for i = 1, #StaticData.LIMBS_STR do
local limbName = StaticData.LIMBS_STR[i]
local bptEnum = StaticData.LIMBS_TO_BODYLOCS_IND_BPT[limbName]
local bodyPart = bd:getBodyPart(bptEnum)
if dcInst:getIsCut(limbName) then
-- Generic injury, let's heal it since they already cut the limb off
if bodyPart:HasInjury() then
TOC_DEBUG.print("Healing area - " .. limbName)
LocalPlayerController.HealArea(bodyPart)
end
-- Special case for bites\zombie infections
if bodyPart:IsInfected() then
TOC_DEBUG.print("Healed from zombie infection - " .. limbName)
LocalPlayerController.HealZombieInfection(bd, bodyPart, limbName, dcInst)
end
else
if (bodyPart:bitten() or bodyPart:IsInfected()) and not dcInst:getIsInfected(limbName) then
dcInst:setIsInfected(limbName, true)
modDataNeedsUpdate = true
end
end
end
-- Check other body parts that are not included in the mod, if there's a bite there then the player is fucked
-- We can skip this loop if the player has been infected. The one before we kinda need it to handle correctly the bites in case the player wanna cut stuff off anyway
if dcInst:getIsIgnoredPartInfected() then return end
for i = 1, #StaticData.IGNORED_BODYLOCS_BPT do
local bodyPartType = StaticData.IGNORED_BODYLOCS_BPT[i]
local bodyPart = bd:getBodyPart(bodyPartType)
if bodyPart and (bodyPart:bitten() or bodyPart:IsInfected()) then
dcInst:setIsIgnoredPartInfected(true)
modDataNeedsUpdate = true
end
end
-- TODO in theory should sync modData, but it's gonna be expensive as fuck. Figure it out
if modDataNeedsUpdate then
dcInst:apply()
end
-- Disable the lock
LocalPlayerController.hasBeenDamaged = false
end
---Setup HandleDamage, triggered by OnPlayerGetDamage
---@param character IsoPlayer|IsoGameCharacter
---@param damageType string
---@param damageAmount number
function LocalPlayerController.OnGetDamage(character, damageType, damageAmount)
-- TODO Check if other players in the online triggers this
if LocalPlayerController.hasBeenDamaged == false then
-- Start checks
-- TODO Add a timer before we can re-enable this bool?
LocalPlayerController.hasBeenDamaged = true
LocalPlayerController.HandleDamage(character)
end
end
Events.OnPlayerGetDamage.Add(LocalPlayerController.OnGetDamage)
--* Amputation Loop handling *--
---Updates the cicatrization process, run when a limb has been cut. Run it every 1 hour
function LocalPlayerController.UpdateAmputations()
local dcInst = DataController.GetInstance()
if not dcInst:getIsDataReady() then
TOC_DEBUG.print("Data not ready for UpdateAmputations, waiting next loop")
return
end
if not dcInst:getIsAnyLimbCut() then
Events.EveryHours.Remove(LocalPlayerController.UpdateAmputations)
end
local pl = LocalPlayerController.playerObj
local visual = pl:getHumanVisual()
local amputatedLimbs = CachedDataHandler.GetAmputatedLimbs(pl:getUsername())
local needsUpdate = false
for k, _ in pairs(amputatedLimbs) do
local limbName = k
local isCicatrized = dcInst:getIsCicatrized(limbName)
if not isCicatrized then
needsUpdate = true
local cicTime = dcInst:getCicatrizationTime(limbName)
TOC_DEBUG.print("Updating cicatrization for " .. tostring(limbName))
--* Dirtyness of the wound
-- We need to get the BloodBodyPartType to find out how dirty the zone is
local bbptEnum = BloodBodyPartType[limbName]
local modifier = 0.01 * SandboxVars.TOC.WoundDirtynessMultiplier
local dirtynessVis = visual:getDirt(bbptEnum) + visual:getBlood(bbptEnum)
local dirtynessWound = dcInst:getWoundDirtyness(limbName) + modifier
local dirtyness = dirtynessVis + dirtynessWound
if dirtyness > 1 then
dirtyness = 1
end
dcInst:setWoundDirtyness(limbName, dirtyness)
TOC_DEBUG.print("Dirtyness for this zone: " .. tostring(dirtyness))
--* Cicatrization
local cicDec = SandboxVars.TOC.CicatrizationSpeed - dirtyness
if cicDec <= 0 then cicDec = 0.1 end
cicTime = cicTime - cicDec
TOC_DEBUG.print("New cicatrization time: " .. tostring(cicTime))
if cicTime <= 0 then
LocalPlayerController.HandleSetCicatrization(dcInst, pl, limbName)
else
dcInst:setCicatrizationTime(limbName, cicTime)
end
end
end
if needsUpdate then
TOC_DEBUG.print("updating modData from cicatrization loop")
dcInst:apply() -- TODO This is gonna be heavy. Not entirely sure
else
TOC_DEBUG.print("Removing UpdateAmputations")
Events.EveryHours.Remove(LocalPlayerController.UpdateAmputations) -- We can remove it safely, no cicatrization happening here boys
end
TOC_DEBUG.print("updating cicatrization and wound dirtyness!")
end
---Starts safely the loop to update cicatrzation
function LocalPlayerController.ToggleUpdateAmputations()
TOC_DEBUG.print("Activating amputation handling loop (if it wasn't active before)")
CommonMethods.SafeStartEvent("EveryHours", LocalPlayerController.UpdateAmputations)
end
--* Cicatrization and cicatrization visuals *--
---Set the boolean and cicTime in DCINST and the visuals for the amputated limb
---@param dcInst DataController
---@param playerObj IsoPlayer
---@param limbName string
function LocalPlayerController.HandleSetCicatrization(dcInst, playerObj, limbName)
TOC_DEBUG.print(tostring(limbName) .. " is cicatrized")
dcInst:setIsCicatrized(limbName, true)
dcInst:setCicatrizationTime(limbName, 0)
-- Set visuals for the amputation
local ItemsController = require("TOC/Controllers/ItemsController")
ItemsController.Player.OverrideAmputationItemVisuals(playerObj, limbName, true)
end
--* Object drop handling when amputation occurs
function LocalPlayerController.CanItemBeEquipped(itemObj, limbName)
local bl = itemObj:getBodyLocation()
local side = CommonMethods.GetSide(limbName)
local sideStr = CommonMethods.GetSideFull(side)
-- TODO Check from DataController
if string.contains(limbName, "Hand_") and (bl == sideStr .. "_MiddleFinger" or bl == sideStr .. "_RingFinger") then
return false
end
if string.contains(limbName, "ForeArm_") and (bl == sideStr .. "Wrist") then
return false
end
return true
end
--- Drop all items from the affected limb
---@param limbName string
function LocalPlayerController.DropItemsAfterAmputation(limbName)
TOC_DEBUG.print("Triggered DropItemsAfterAmputation")
local side = CommonMethods.GetSide(limbName)
local sideStr = CommonMethods.GetSideFull(side)
local pl = getPlayer()
local wornItems = pl:getWornItems()
for i = 1, wornItems:size() do
local it = wornItems:get(i - 1)
if it then
local wornItem = wornItems:get(i - 1):getItem()
TOC_DEBUG.print(wornItem:getBodyLocation())
local bl = wornItem:getBodyLocation()
if string.contains(limbName, "Hand_") and (bl == sideStr .. "_MiddleFinger" or bl == sideStr .. "_RingFinger") then
pl:removeWornItem(wornItem)
end
if string.contains(limbName, "ForeArm_") and (bl == sideStr .. "Wrist") then
pl:removeWornItem(wornItem)
end
end
end
-- TODO Consider 2 handed weapons too
-- equipped items too
if side == "R" then
pl:setPrimaryHandItem(nil)
elseif side == "L" then
pl:setSecondaryHandItem(nil)
end
end
Events.OnAmputatedLimb.Add(LocalPlayerController.DropItemsAfterAmputation)
Events.OnProsthesisUnequipped.Add(LocalPlayerController.DropItemsAfterAmputation)
return LocalPlayerController

View File

@@ -0,0 +1,112 @@
local CommonMethods = require("TOC/CommonMethods")
---@class TourniquetController
local TourniquetController = {
bodyLoc = "TOC_ArmAccessory"
}
function TourniquetController.CheckTourniquetOnLimb(player, limbName)
local side = CommonMethods.GetSide(limbName)
local wornItems = player:getWornItems()
for j=1,wornItems:size() do
local wornItem = wornItems:get(j-1)
local fType = wornItem:getItem():getFullType()
if TourniquetController.IsItemTourniquet(fType) then
-- Check side
if luautils.stringEnds(fType, side) then
TOC_DEBUG.print("Found acceptable tourniquet")
return true
end
end
end
return false
end
function TourniquetController.IsItemTourniquet(fType)
-- TODO Add legs stuff
return string.contains(fType, "Surg_Arm_Tourniquet_")
end
---@param player IsoPlayer
---@param limbName string
---@return boolean
function TourniquetController.CheckTourniquet(player, limbName)
local side = CommonMethods.GetSide(limbName)
local wornItems = player:getWornItems()
for j=1,wornItems:size() do
local wornItem = wornItems:get(j-1)
local fType = wornItem:getItem():getFullType()
if string.contains(fType, "Surg_Arm_Tourniquet_") then
-- Check side
if luautils.stringEnds(fType, side) then
TOC_DEBUG.print("Found acceptable tourniquet")
return true
end
end
end
return false
end
---@private
---@param obj any self
---@param wrappedFunc function
function TourniquetController.WrapClothingAction(obj, wrappedFunc)
local isTourniquet = TourniquetController.IsItemTourniquet(obj.item:getFullType())
local group
if isTourniquet then
group = BodyLocations.getGroup("Human")
group:setMultiItem(TourniquetController.bodyLoc, false)
end
local ogValue = wrappedFunc(obj)
if isTourniquet then
group:setMultiItem(TourniquetController.bodyLoc, true)
end
return ogValue -- Needed for isValid
end
--[[
Horrendous workaround
To unequp items, the java side uses WornItems.setItem, which has
a check for multiItem. Basically, if it's active, it won't actually remove the item,
fucking things up. So, to be 100% sure that we're removing the items, we're gonna
disable and re-enable the multi-item bool for the Unequip Action.
Same story as the prosthesis item basically.
]]
local og_ISClothingExtraAction_perform = ISClothingExtraAction.perform
function ISClothingExtraAction:perform()
TourniquetController.WrapClothingAction(self, og_ISClothingExtraAction_perform)
end
local og_ISWearClothing_isValid = ISWearClothing.isValid
function ISWearClothing:isValid()
return TourniquetController.WrapClothingAction(self, og_ISWearClothing_isValid)
end
local og_ISUnequipAction_perform = ISUnequipAction.perform
function ISUnequipAction:perform()
return TourniquetController.WrapClothingAction(self, og_ISUnequipAction_perform)
end
return TourniquetController

View File

@@ -0,0 +1,4 @@
--* Setup Events *--
LuaEventManager.AddEvent("OnAmputatedLimb") --Triggered when a limb has been amputated
LuaEventManager.AddEvent("OnProsthesisUnequipped")
LuaEventManager.AddEvent("OnReceivedTocData") -- Triggered when TOC data is ready

View File

@@ -0,0 +1,204 @@
local DataController = require("TOC/Controllers/DataController")
local ItemsController = require("TOC/Controllers/ItemsController")
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
local LocalPlayerController = require("TOC/Controllers/LocalPlayerController")
local StaticData = require("TOC/StaticData")
local TourniquetController = require("TOC/Controllers/TourniquetController")
---------------------------
--- Manages an amputation. Will be run on the patient client
---@class AmputationHandler
---@field patientPl IsoPlayer
---@field limbName string
---@field bodyPartType BodyPartType
---@field surgeonPl IsoPlayer?
local AmputationHandler = {}
---@param limbName string
---@param surgeonPl IsoPlayer?
---@return AmputationHandler
function AmputationHandler:new(limbName, surgeonPl)
local o = {}
setmetatable(o, self)
self.__index = self
o.patientPl = getPlayer()
o.limbName = limbName
o.bodyPartType = BodyPartType[limbName]
-- TOC_DEBUG.print("limbName = " .. o.limbName)
-- TOC_DEBUG.print("bodyPartType = " .. tostring(o.bodyPartType))
if surgeonPl then
o.surgeonPl = surgeonPl
else
o.surgeonPl = o.patientPl
end
AmputationHandler.instance = o
return o
end
--* Static methods *--
---@param player IsoPlayer
---@param limbName string
function AmputationHandler.ApplyDamageDuringAmputation(player, limbName)
local ampGroup = StaticData.LIMBS_TO_AMP_GROUPS_MATCH_IND_STR[limbName]
local isTourniquetEquipped = false
-- Check if tourniquet is applied on the zone
for bl, tournAmpGroup in pairs(StaticData.TOURNIQUET_BODYLOCS_TO_GROUPS_IND_STR) do
local item = player:getWornItem(bl)
-- LimbName -> Group -> BodyLoc
if item and tournAmpGroup == ampGroup then
TOC_DEBUG.print("tourniquet is equipped")
isTourniquetEquipped = true
break
end
end
local bodyDamage = player:getBodyDamage()
local bodyPartType = BodyPartType[limbName]
local bodyDamagePart = bodyDamage:getBodyPart(bodyPartType)
TOC_DEBUG.print("damage patient - " .. tostring(bodyPartType))
bodyDamagePart:setBleeding(true)
bodyDamagePart:setCut(true)
local bleedingTime
if isTourniquetEquipped then
bleedingTime = ZombRand(1,5)
else
bleedingTime = ZombRand(10, 20)
end
bodyDamagePart:setBleedingTime(bleedingTime)
end
---@param prevAction ISBaseTimedAction
---@param limbName string
---@param surgeonPl IsoPlayer
---@param patientPl IsoPlayer
---@param stitchesItem InventoryItem
---@return ISStitch
function AmputationHandler.PrepareStitchesAction(prevAction, limbName, surgeonPl, patientPl, stitchesItem)
local bptEnum = StaticData.LIMBS_TO_BODYLOCS_IND_BPT[limbName]
local bd = patientPl:getBodyDamage()
local bodyPart = bd:getBodyPart(bptEnum)
local stitchesAction = ISStitch:new(surgeonPl, patientPl, stitchesItem, bodyPart, true)
ISTimedActionQueue.addAfter(prevAction, stitchesAction)
return stitchesAction
end
---Setup the ISApplyBandage action that will trigger after the amputation
---@param prevAction ISBaseTimedAction
---@param limbName string
---@param surgeonPl IsoPlayer
---@param patientPl IsoPlayer
---@param bandageItem InventoryItem
---@return ISApplyBandage
function AmputationHandler.PrepareBandagesAction(prevAction, limbName, surgeonPl, patientPl, bandageItem)
local bptEnum = StaticData.LIMBS_TO_BODYLOCS_IND_BPT[limbName]
local bd = patientPl:getBodyDamage()
local bodyPart = bd:getBodyPart(bptEnum)
local bandageAction = ISApplyBandage:new(surgeonPl, patientPl, bandageItem, bodyPart, true)
ISTimedActionQueue.addAfter(prevAction, bandageAction)
return bandageAction
end
--* Main methods *--
---Set the damage to the adjacent part of the cut area
---@param surgeonFactor number
function AmputationHandler:damageAfterAmputation(surgeonFactor)
TOC_DEBUG.print("Applying damage after amputation")
local patientStats = self.patientPl:getStats()
local bd = self.patientPl:getBodyDamage()
local adjacentLimb = StaticData.LIMBS_ADJACENT_IND_STR[self.limbName]
local bodyPart = bd:getBodyPart(BodyPartType[adjacentLimb])
local baseDamage = StaticData.LIMBS_BASE_DAMAGE_IND_NUM[self.limbName]
-- Check if player has tourniquet equipped on the limb
-- TODO Suboptimal checks, but they should work for now.
local hasTourniquet = TourniquetController.CheckTourniquetOnLimb(self.patientPl, self.limbName)
if hasTourniquet then
TOC_DEBUG.print("Do something different for the damage calculation because tourniquet is applied")
baseDamage = baseDamage * 0.5 -- 50% less damage due to tourniquet
end
bodyPart:AddDamage(baseDamage - surgeonFactor)
bodyPart:setAdditionalPain(baseDamage - surgeonFactor)
bodyPart:setBleeding(true)
bodyPart:setBleedingTime(baseDamage - surgeonFactor)
bodyPart:setDeepWounded(true)
bodyPart:setDeepWoundTime(baseDamage - surgeonFactor)
patientStats:setEndurance(surgeonFactor)
patientStats:setStress(baseDamage - surgeonFactor)
end
---Execute the amputation
---@param damagePlayer boolean
function AmputationHandler:execute(damagePlayer)
local surgeonFactor = self.surgeonPl:getPerkLevel(Perks.Doctor) * SandboxVars.TOC.SurgeonAbilityImportance
-- Set the data in modData
local dcInst = DataController.GetInstance()
dcInst:setCutLimb(self.limbName, false, false, false, surgeonFactor)
dcInst:apply() -- This will force rechecking the cached amputated limbs on the other client
-- Heal the area, we're gonna re-set the damage after (if it's enabled)
local bd = self.patientPl:getBodyDamage()
local bodyPart = bd:getBodyPart(self.bodyPartType)
LocalPlayerController.HealArea(bodyPart)
-- Give the player the correct amputation item
ItemsController.Player.DeleteOldAmputationItem(self.patientPl, self.limbName)
ItemsController.Player.SpawnAmputationItem(self.patientPl, self.limbName)
-- Add it to the list of cut limbs on this local client
local username = self.patientPl:getUsername()
CachedDataHandler.AddAmputatedLimb(username, self.limbName)
-- TODO Not optimal, we're already cycling through this when using setCutLimb
for i=1, #StaticData.LIMBS_DEPENDENCIES_IND_STR[self.limbName] do
local dependedLimbName = StaticData.LIMBS_DEPENDENCIES_IND_STR[self.limbName][i]
CachedDataHandler.AddAmputatedLimb(username, dependedLimbName)
end
-- Cache highest amputation and hand feasibility
CachedDataHandler.CalculateCacheableValues(username)
-- If the part was actually infected, heal the player, if they were in time (infectionLevel < 20)
if bd:getInfectionLevel() < 20 and bodyPart:IsInfected() and not dcInst:getIsIgnoredPartInfected() then
LocalPlayerController.HealZombieInfection(bd, bodyPart, self.limbName, dcInst)
end
-- The last part is to handle the damage that the player will receive after the amputation
if not damagePlayer then return end
self:damageAfterAmputation(surgeonFactor)
-- Trigger this event
triggerEvent("OnAmputatedLimb", self.limbName)
end
---Deletes the instance
function AmputationHandler:close()
AmputationHandler.instance = nil
end
return AmputationHandler

View File

@@ -0,0 +1,154 @@
local DataController = require("TOC/Controllers/DataController")
local StaticData = require("TOC/StaticData")
local CommonMethods = require("TOC/CommonMethods")
---------------------------
---@class CachedDataHandler
local CachedDataHandler = {}
---Reset everything cache related for that specific user
---@param username string
function CachedDataHandler.Setup(username)
CachedDataHandler.amputatedLimbs[username] = {}
-- username -> side
CachedDataHandler.highestAmputatedLimbs[username] = {}
-- Local only, doesn't matter for Health Panel
CachedDataHandler.handFeasibility = {}
end
---Will calculate all the values that we need
function CachedDataHandler.CalculateCacheableValues(username)
CachedDataHandler.CalculateHighestAmputatedLimbs(username)
-- FIX This should be run ONLY on the actual client, never on other clients. Just a placeholder fix for now
if getPlayer():getUsername() == username then
CachedDataHandler.CalculateBothHandsFeasibility()
end
end
--* Amputated Limbs caching *--
CachedDataHandler.amputatedLimbs = {}
---Calculate the currently amputated limbs for a certain player
---@param username string
function CachedDataHandler.CalculateAmputatedLimbs(username)
TOC_DEBUG.print("Calculating amputated limbs for " .. username)
CachedDataHandler.amputatedLimbs[username] = {}
local dcInst = DataController.GetInstance(username)
for i=1, #StaticData.LIMBS_STR do
local limbName = StaticData.LIMBS_STR[i]
if dcInst:getIsCut(limbName) then
CachedDataHandler.AddAmputatedLimb(username, limbName)
end
end
end
---Add an amputated limb to the cached list for that user
---@param username string
---@param limbName string
function CachedDataHandler.AddAmputatedLimb(username, limbName)
TOC_DEBUG.print("Added " .. limbName .. " to known amputated limbs for " .. username)
-- Add it to the generic list
if CachedDataHandler.amputatedLimbs[username] == nil then
CachedDataHandler.amputatedLimbs[username] = {}
end
CachedDataHandler.amputatedLimbs[username][limbName] = limbName
end
---Returns a table containing the cached amputated limbs
---@param username string
---@return table
function CachedDataHandler.GetAmputatedLimbs(username)
return CachedDataHandler.amputatedLimbs[username]
end
--* Highest amputated limb per side caching *--
CachedDataHandler.highestAmputatedLimbs = {}
---Calcualate the highest point of amputations achieved by the player
---@param username string
function CachedDataHandler.CalculateHighestAmputatedLimbs(username)
TOC_DEBUG.print("Triggered CalculateHighestAmputatedLimbs")
local dcInst = DataController.GetInstance(username)
if dcInst == nil then
TOC_DEBUG.print("DataController not found for " .. username)
return
end
CachedDataHandler.CalculateAmputatedLimbs(username)
local amputatedLimbs = CachedDataHandler.amputatedLimbs[username]
CachedDataHandler.highestAmputatedLimbs[username] = {}
--TOC_DEBUG.print("Searching highest amputations for " .. username)
for k, _ in pairs(amputatedLimbs) do
local limbName = k
local side = CommonMethods.GetSide(limbName)
if dcInst:getIsCut(limbName) and dcInst:getIsVisible(limbName) then
TOC_DEBUG.print("Added Highest Amputation: " .. limbName)
CachedDataHandler.highestAmputatedLimbs[username][side] = limbName
end
end
end
---Get the cached highest point of amputation for each side
---@param username string
---@return table<string, string>
function CachedDataHandler.GetHighestAmputatedLimbs(username)
return CachedDataHandler.highestAmputatedLimbs[username]
end
--* Hand feasibility caching *--
CachedDataHandler.handFeasibility = {}
---@param limbName string
function CachedDataHandler.CalculateHandFeasibility(limbName)
local dcInst = DataController.GetInstance()
local side = CommonMethods.GetSide(limbName)
CachedDataHandler.handFeasibility[side] = not dcInst:getIsCut(limbName) or dcInst:getIsProstEquipped(limbName)
end
function CachedDataHandler.GetHandFeasibility(side)
return CachedDataHandler.handFeasibility[side]
end
function CachedDataHandler.CalculateBothHandsFeasibility()
CachedDataHandler.CalculateHandFeasibility("Hand_L")
CachedDataHandler.CalculateHandFeasibility("Hand_R")
local interactStr = "Interact"
if not CachedDataHandler.GetBothHandsFeasibility() then
TOC_DEBUG.print("Disabling interact key")
-- Cache the current key
CachedDataHandler.interactKey = getCore():getKey(interactStr)
getCore():addKeyBinding(interactStr, Keyboard.KEY_NONE)
else
TOC_DEBUG.print("Re-enabling interact key")
if not CachedDataHandler.interactKey then CachedDataHandler.interactKey = getCore():getKey(interactStr) end
getCore():addKeyBinding(interactStr, CachedDataHandler.interactKey)
end
end
function CachedDataHandler.GetBothHandsFeasibility()
return CachedDataHandler.handFeasibility["L"] or CachedDataHandler.handFeasibility["R"]
end
return CachedDataHandler

View File

@@ -0,0 +1,186 @@
local CommonMethods = require("TOC/CommonMethods")
local StaticData = require("TOC/StaticData")
local DataController = require("TOC/Controllers/DataController")
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
-------------------------
---@class ProsthesisHandler
local ProsthesisHandler = {}
local bodyLocArmProst = StaticData.MOD_BODYLOCS_BASE_IND_STR.TOC_ArmProst
local bodyLocLegProst = StaticData.MOD_BODYLOCS_BASE_IND_STR.TOC_LegProst
---Check if the following item is a prosthesis or not
---@param item InventoryItem?
---@return boolean
function ProsthesisHandler.CheckIfProst(item)
-- TODO Won't be correct when prost for legs are gonna be in
--TOC_DEBUG.print("Checking if item is prost")
if item == nil then
--TOC_DEBUG.print("Not prost")
return false
end
return item:getBodyLocation():contains(bodyLocArmProst)
end
---Get the grouping for the prosthesis
---@param item InventoryItem
---@return string
function ProsthesisHandler.GetGroup(item)
local fullType = item:getFullType()
local side = CommonMethods.GetSide(fullType)
local bodyLocation = item:getBodyLocation()
local position
if bodyLocation == bodyLocArmProst then
position = "Top_"
elseif bodyLocation == bodyLocLegProst then
position = "Bottom_"
else
TOC_DEBUG.print("Something is wrong, no position in this item")
position = nil
end
local index = position .. side
local group = StaticData.AMP_GROUPS_IND_STR[index]
return group
end
---Check if a prosthesis is equippable. It depends whether the player has a cut limb or not on that specific side. There's an exception for Upper arm, obviously
---@param fullType string
---@return boolean
function ProsthesisHandler.CheckIfEquippable(fullType)
--TOC_DEBUG.print("Current item is a prosthesis")
local side = CommonMethods.GetSide(fullType)
--TOC_DEBUG.print("Checking side: " .. tostring(side))
local highestAmputatedLimbs = CachedDataHandler.GetHighestAmputatedLimbs(getPlayer():getUsername())
if highestAmputatedLimbs then
local hal = highestAmputatedLimbs[side]
if hal and not string.contains(hal, "UpperArm") then
--TOC_DEBUG.print("Found acceptable limb to use prosthesis => " .. tostring(hal))
return true
end
end
-- No acceptable cut limbs
return false
end
---Handle equipping or unequipping prosthetics
---@param item InventoryItem
---@param isEquipping boolean
---@return boolean
function ProsthesisHandler.SearchAndSetupProsthesis(item, isEquipping)
if not ProsthesisHandler.CheckIfProst(item) then return false end
local group = ProsthesisHandler.GetGroup(item)
TOC_DEBUG.print("Setup Prosthesis => " .. group .. " - is equipping? " .. tostring(isEquipping))
local dcInst = DataController.GetInstance()
dcInst:setIsProstEquipped(group, isEquipping)
dcInst:apply()
-- Calculates hands feasibility once again
CachedDataHandler.CalculateBothHandsFeasibility()
return true
end
-------------------------
--* Overrides *--
---@param item InventoryItem
---@param isEquippable boolean
---@return unknown
local function HandleProsthesisValidation(item, isEquippable)
local isProst = ProsthesisHandler.CheckIfProst(item)
if not isProst then return isEquippable end
local fullType = item:getFullType() -- use fulltype for side
if isEquippable then
isEquippable = ProsthesisHandler.CheckIfEquippable(fullType)
else
getPlayer():Say(getText("UI_Say_CantEquip"))
end
return isEquippable
end
---@diagnostic disable-next-line: duplicate-set-field
local og_ISWearClothing_isValid = ISWearClothing.isValid
function ISWearClothing:isValid()
local isEquippable = og_ISWearClothing_isValid(self)
return HandleProsthesisValidation(self.item, isEquippable)
end
local og_ISWearClothing_perform = ISWearClothing.perform
function ISWearClothing:perform()
ProsthesisHandler.SearchAndSetupProsthesis(self.item, true)
og_ISWearClothing_perform(self)
end
local og_ISClothingExtraAction_isValid = ISClothingExtraAction.isValid
---@diagnostic disable-next-line: duplicate-set-field
function ISClothingExtraAction:isValid()
local isEquippable = og_ISClothingExtraAction_isValid(self)
-- self.extra is a string, not the item
local testItem = InventoryItemFactory.CreateItem(self.extra)
return HandleProsthesisValidation(testItem, isEquippable)
end
--[[
Horrendous workaround
To unequp items, the java side uses WornItems.setItem, which has
a check for multiItem. Basically, if it's active, it won't actually remove the item,
fucking things up. So, to be 100% sure that we're removing the items, we're gonna
disable and re-enable the multi-item bool for the Unequip Action.
]]
local og_ISClothingExtraAction_perform = ISClothingExtraAction.perform
function ISClothingExtraAction:perform()
--local extraItem = InventoryItemFactory.CreateItem(self.extra)
local isProst = ProsthesisHandler.SearchAndSetupProsthesis(self.item, true)
local group
if isProst then
group = BodyLocations.getGroup("Human")
group:setMultiItem("TOC_ArmProst", false)
end
og_ISClothingExtraAction_perform(self)
if isProst then
group:setMultiItem("TOC_ArmProst", true)
end
end
local og_ISUnequipAction_perform = ISUnequipAction.perform
function ISUnequipAction:perform()
local isProst = ProsthesisHandler.SearchAndSetupProsthesis(self.item, false)
local group
if isProst then
group = BodyLocations.getGroup("Human")
group:setMultiItem("TOC_ArmProst", false)
end
og_ISUnequipAction_perform(self)
if isProst then
group:setMultiItem("TOC_ArmProst", true)
-- we need to fetch the limbname associated to the prosthesis
local side = CommonMethods.GetSide(self.item:getFullType())
local highestAmputatedLimbs = CachedDataHandler.GetHighestAmputatedLimbs(getPlayer():getUsername())
if highestAmputatedLimbs then
local hal = highestAmputatedLimbs[side]
triggerEvent("OnProsthesisUnequipped", hal)
end
end
end
return ProsthesisHandler

View File

@@ -0,0 +1,65 @@
local LocalPlayerController = require("TOC/Controllers/LocalPlayerController")
local CommonMethods = require("TOC/CommonMethods")
local CommandsData = require("TOC/CommandsData")
require("TOC/Events")
------------------
---@class Main
local Main = {
_version = "2.0.2"
}
function Main.Start()
TOC_DEBUG.print("Starting The Only Cure version " .. tostring(Main._version))
--Main.SetupTraits()
Main.SetupEvents()
end
function Main.SetupEvents()
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
Events.OnReceivedTocData.Add(CachedDataHandler.CalculateCacheableValues)
end
function Main.InitializePlayer()
---Looop until we've successfully initialized the mod
local function TryToInitialize()
local pl = getPlayer()
TOC_DEBUG.print("Current username in TryToInitialize: " .. pl:getUsername())
if pl:getUsername() == "Bob" then
TOC_DEBUG.print("Username is still Bob, waiting")
return
end
LocalPlayerController.InitializePlayer(false)
Events.OnTick.Remove(TryToInitialize)
end
CommonMethods.SafeStartEvent("OnTick", TryToInitialize)
end
---Clean the TOC table for that SP player, to prevent from clogging it up
---@param player IsoPlayer
function Main.WipeData(player)
TOC_DEBUG.print("Wiping data after death")
local key = CommandsData.GetKey(player:getUsername())
--ModData.remove(key)
if not isClient() then
-- For SP, it's enough just removing the data this way
ModData.remove(key)
else
-- Different story for MP, we're gonna 'force' it to reload it
-- at the next character by passing an empty mod data
ModData.add(key, {})
ModData.transmit(key)
end
end
--* Events *--
Events.OnGameStart.Add(Main.Start)
Events.OnCreatePlayer.Add(Main.InitializePlayer)
Events.OnPlayerDeath.Add(Main.WipeData)

View File

@@ -0,0 +1,168 @@
if not getActivatedMods():contains("TEST_FRAMEWORK") or not isDebugEnabled() then return end
local TestFramework = require("TestFramework/TestFramework")
local TestUtils = require("TestFramework/TestUtils")
local LocalPlayerController = require("TOC/Controllers/LocalPlayerController")
local AmputationHandler = require("TOC/Handlers/AmputationHandler")
local DataController = require("TOC/Controllers/DataController")
local StaticData = require("TOC/StaticData")
TestFramework.registerTestModule("LocalPlayerController", "Setup", function()
local Tests = {}
function Tests.InitializePlayer()
local pl = getPlayer()
LocalPlayerController.InitializePlayer(true)
end
return Tests
end)
TestFramework.registerTestModule("LocalPlayerController", "Perks", function()
local Tests = {}
function Tests.SetMaxPerks()
local pl = getPlayer()
for _=0, 10 do
pl:LevelPerk(Perks["Side_L"])
pl:LevelPerk(Perks["Side_R"])
pl:getXp():setXPToLevel(Perks["Side_L"], pl:getPerkLevel(Perks["Side_L"]))
pl:getXp():setXPToLevel(Perks["Side_R"], pl:getPerkLevel(Perks["Side_R"]))
end
SyncXp(pl)
end
function Tests.ResetPerks()
local pl = getPlayer()
for _=0, 10 do
pl:LoseLevel(Perks["Side_L"])
pl:LoseLevel(Perks["Side_R"])
pl:getXp():setXPToLevel(Perks["Side_L"], pl:getPerkLevel(Perks["Side_L"]))
pl:getXp():setXPToLevel(Perks["Side_R"], pl:getPerkLevel(Perks["Side_R"]))
end
SyncXp(pl)
end
return Tests
end)
TestFramework.registerTestModule("LocalPlayerController", "Cicatrization", function()
local Tests = {}
function Tests.SetCicatrizationTimeToOne()
for i=1, #StaticData.LIMBS_STR do
local limbName = StaticData.LIMBS_STR[i]
DataController.GetInstance():setCicatrizationTime(limbName, 1)
TestUtils.assert(DataController.GetInstance():getCicatrizationTime(limbName) == 1)
end
DataController.GetInstance():apply()
TestUtils.assert(DataController.GetInstance():getIsCut("Hand_L"))
end
return Tests
end)
TestFramework.registerTestModule("AmputationHandler", "Top Left", function()
local Tests = {}
function Tests.CutLeftHand()
local handler = AmputationHandler:new("Hand_L")
handler:execute(true)
TestUtils.assert(DataController.GetInstance():getIsCut("Hand_L"))
end
function Tests.CutLeftForearm()
local handler = AmputationHandler:new("ForeArm_L")
handler:execute(true)
TestUtils.assert(DataController.GetInstance():getIsCut("ForeArm_L") and DataController.GetInstance():getIsCut("Hand_L"))
end
function Tests.CutLeftUpperarm()
local handler = AmputationHandler:new("UpperArm_L")
handler:execute(true)
TestUtils.assert(DataController.GetInstance():getIsCut("UpperArm_L") and DataController.GetInstance():getIsCut("ForeArm_L") and DataController.GetInstance():getIsCut("Hand_L"))
end
return Tests
end)
TestFramework.registerTestModule("AmputationHandler", "Top Right", function()
local Tests = {}
function Tests.CutRightHand()
local handler = AmputationHandler:new("Hand_R")
handler:execute(true)
TestUtils.assert(DataController.GetInstance():getIsCut("Hand_R"))
end
function Tests.CutRightForearm()
local handler = AmputationHandler:new("ForeArm_R")
handler:execute(true)
TestUtils.assert(DataController.GetInstance():getIsCut("ForeArm_R") and DataController.GetInstance():getIsCut("Hand_R"))
end
function Tests.CutRightUpperarm()
local handler = AmputationHandler:new("UpperArm_R")
handler:execute(true)
TestUtils.assert(DataController.GetInstance():getIsCut("UpperArm_R") and DataController.GetInstance():getIsCut("ForeArm_R") and DataController.GetInstance():getIsCut("Hand_R"))
end
return Tests
end)
TestFramework.registerTestModule("TimedActions", "CauterizeAction", function()
local Tests = {}
local CauterizeAction = require("TOC/TimedActions/CauterizeAction")
function Tests.CauterizeLeftHand()
ISTimedActionQueue.add(CauterizeAction:new(getPlayer(), "Hand_L", getPlayer()))
end
function Tests.CauterizeLefForeArm()
ISTimedActionQueue.add(CauterizeAction:new(getPlayer(), "ForeArm_L", getPlayer()))
end
function Tests.CauterizeLeftUpperArm()
ISTimedActionQueue.add(CauterizeAction:new(getPlayer(), "UpperArm_L", getPlayer()))
end
function Tests.CauterizeRightHand()
ISTimedActionQueue.add(CauterizeAction:new(getPlayer(), "Hand_R", getPlayer()))
end
function Tests.CauterizeRightForeArm()
ISTimedActionQueue.add(CauterizeAction:new(getPlayer(), "ForeArm_R", getPlayer()))
end
function Tests.CauterizeRightUpperArm()
ISTimedActionQueue.add(CauterizeAction:new(getPlayer(), "UpperArm_R", getPlayer()))
end
return Tests
end)
TestFramework.registerTestModule("Various", "Player", function()
local Tests = {}
function Tests.Kill()
getPlayer():Kill(getPlayer())
end
return Tests
end)
--------------------------------------------------------------------------------------
if not getActivatedMods():contains("PerfTestFramework") or not isDebugEnabled() then return end
local PerfTest = require("PerfTest/main") -- SHould be global anyway
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
PerfTest.RegisterMethod("LocalPlayerController", LocalPlayerController, "InitializePlayer")
PerfTest.RegisterMethod("LocalPlayerController", LocalPlayerController, "UpdateAmputations")
PerfTest.RegisterMethod("CachedDataHandler", CachedDataHandler, "CalculateHighestAmputatedLimbs")
PerfTest.RegisterMethod("ISHealthPanel", ISHealthPanel, "render")
PerfTest.Init()

View File

@@ -0,0 +1,89 @@
require "TimedActions/ISBaseTimedAction"
local DataController = require("TOC/Controllers/DataController")
local LocalPlayerController = require("TOC/Controllers/LocalPlayerController")
---------------
---@class CauterizeAction : ISBaseTimedAction
---@field character IsoPlayer
---@field ovenObj IsoObject
---@field limbName string
local CauterizeAction = ISBaseTimedAction:derive("CauterizeAction")
---@param character IsoPlayer
---@param stoveObj IsoObject
---@param limbName string
---@return CauterizeAction
function CauterizeAction:new(character, limbName, stoveObj)
local o = {}
setmetatable(o, self)
self.__index = self
-- We need to follow ISBaseTimedAction. self.character is gonna be the surgeon
o.character = character
o.stoveObj = stoveObj
o.limbName = limbName
o.stopOnWalk = true
o.stopOnRun = true
-- Max time depends on the strength
o.maxTime = 20
if o.character:isTimedActionInstant() then o.maxTime = 1 end
return o
end
function CauterizeAction:isValid()
return not ISHealthPanel.DidPatientMove(self.character, self.character, self.character:getX(), self.character:getY())
end
function CauterizeAction:waitToStart()
self.character:faceThisObject(self.ovenObj)
return self.character:shouldBeTurning()
end
function CauterizeAction:start()
self:setActionAnim("Loot") -- TODO Better anim pls
-- Setup audio
self.sound = self.character:getEmitter():playSound("Cauterization")
local radius = 5
addSound(self.character, self.character:getX(), self.character:getY(), self.character:getZ(), radius, radius)
end
function CauterizeAction:update()
self.character:setMetabolicTarget(Metabolics.HeavyWork)
end
function CauterizeAction:stopSound()
if self.sound then
self.character:getEmitter():stopSound(self.sound)
self.sound = nil
end
end
function CauterizeAction:stop()
self:stopSound()
ISBaseTimedAction.stop(self)
end
function CauterizeAction:perform()
-- Stop the sound
self:stopSound()
local dcInst = DataController.GetInstance()
dcInst:setCicatrizationTime(self.limbName, 0)
dcInst:setIsCauterized(self.limbName, true)
-- Set isCicatrized and the visuals in one go, since this action is gonna be run only on a single client
LocalPlayerController.HandleSetCicatrization(dcInst, self.character, self.limbName)
-- TODO Add specific visuals for cauterization
-- we don't care about the depended limbs, since they're alread "cicatrized"
dcInst:apply()
ISBaseTimedAction.perform(self)
end
return CauterizeAction

View File

@@ -0,0 +1,123 @@
local DataController = require("TOC/Controllers/DataController")
local CommonMethods = require("TOC/CommonMethods")
--------------------
---@class CleanWoundAction : ISBaseTimedAction
---@field doctor IsoPlayer
---@field otherPlayer IsoPlayer
---@field bandage InventoryItem
---@field bodyPart any
local CleanWoundAction = ISBaseTimedAction:derive("CleanWoundAction")
---@param doctor IsoPlayer
---@param otherPlayer IsoPlayer
---@param bandage InventoryItem
---@param bodyPart any
---@return CleanWoundAction
function CleanWoundAction:new(doctor, otherPlayer, bandage, bodyPart)
local o = {}
setmetatable(o, self)
self.__index = self
o.character = doctor
o.otherPlayer = otherPlayer
o.doctorLevel = doctor:getPerkLevel(Perks.Doctor)
o.bodyPart = bodyPart
o.bandage = bandage
o.stopOnWalk = true
o.stopOnRun = true
o.bandagedPlayerX = otherPlayer:getX()
o.bandagedPlayerY = otherPlayer:getY()
o.maxTime = 250 - (o.doctorLevel * 6)
if doctor:isTimedActionInstant() then
o.maxTime = 1
end
if doctor:getAccessLevel() ~= "None" then
o.doctorLevel = 10
end
return o
end
function CleanWoundAction:isValid()
if ISHealthPanel.DidPatientMove(self.character, self.otherPlayer, self.bandagedPlayerX, self.bandagedPlayerY) then
return false
end
return true
end
function CleanWoundAction:waitToStart()
if self.character == self.otherPlayer then
return false
end
self.character:faceThisObject(self.otherPlayer)
return self.character:shouldBeTurning()
end
function CleanWoundAction:update()
if self.character ~= self.otherPlayer then
self.character:faceThisObject(self.otherPlayer)
end
local jobType = getText("ContextMenu_CleanWound")
ISHealthPanel.setBodyPartActionForPlayer(self.otherPlayer, self.bodyPart, self, jobType, { cleanBurn = true })
self.character:setMetabolicTarget(Metabolics.LightDomestic)
end
function CleanWoundAction:start()
if self.character == self.otherPlayer then
self:setActionAnim(CharacterActionAnims.Bandage)
self:setAnimVariable("BandageType", ISHealthPanel.getBandageType(self.bodyPart))
self.character:reportEvent("EventBandage")
else
self:setActionAnim("Loot")
self.character:SetVariable("LootPosition", "Mid")
self.character:reportEvent("EventLootItem")
end
self:setOverrideHandModels(nil, nil)
end
function CleanWoundAction:stop()
ISHealthPanel.setBodyPartActionForPlayer(self.otherPlayer, self.bodyPart, nil, nil, nil)
ISBaseTimedAction.stop(self)
end
function CleanWoundAction:perform()
TOC_DEBUG.print("CleanWound for " .. self.otherPlayer:getUsername())
if self.character:HasTrait("Hemophobic") then
self.character:getStats():setPanic(self.character:getStats():getPanic() + 15)
end
self.character:getXp():AddXP(Perks.Doctor, 10)
local addPain = (60 - (self.doctorLevel * 1))
self.bodyPart:setAdditionalPain(self.bodyPart:getAdditionalPain() + addPain)
self.bandage:Use()
-- TOC Data handling
local limbName = CommonMethods.GetLimbNameFromBodyPart(self.bodyPart)
local dcInst = DataController.GetInstance(self.otherPlayer:getUsername())
local currentWoundDirtyness = dcInst:getWoundDirtyness(limbName)
local newWoundDirtyness = currentWoundDirtyness - (self.bandage:getBandagePower() * 10)
if newWoundDirtyness < 0 then newWoundDirtyness = 0 end
dcInst:setWoundDirtyness(limbName, newWoundDirtyness)
dcInst:apply()
-- Clean visual
local bbptEnum = BloodBodyPartType[limbName]
---@type HumanVisual
local visual = self.otherPlayer:getHumanVisual()
visual:setDirt(bbptEnum, 0)
visual:setBlood(bbptEnum, 0)
ISHealthPanel.setBodyPartActionForPlayer(self.otherPlayer, self.bodyPart, nil, nil, nil)
-- needed to remove from queue / start next.
ISBaseTimedAction.perform(self)
end
return CleanWoundAction

View File

@@ -0,0 +1,138 @@
require "TimedActions/ISBaseTimedAction"
local AmputationHandler = require("TOC/Handlers/AmputationHandler")
local CommandsData = require("TOC/CommandsData")
-----------------------------
---@class CutLimbAction : ISBaseTimedAction
---@field patient IsoPlayer
---@field character IsoPlayer
---@field patientX number
---@field patientY number
---@field limbName string
---@field item InventoryItem
---@field stitchesItem InventoryItem?
---@field bandageItem InventoryItem?
local CutLimbAction = ISBaseTimedAction:derive("CutLimbAction")
---Starts CutLimbAction
---@param surgeon IsoPlayer This is gonna be self.character to have working animations
---@param patient IsoPlayer
---@param limbName string
---@param item InventoryItem This is gonna be the saw, following ISBaseTimedAction
---@param stitchesItem InventoryItem?
---@param bandageItem InventoryItem?
---@return CutLimbAction
function CutLimbAction:new(surgeon, patient, limbName, item, stitchesItem, bandageItem)
local o = {}
setmetatable(o, self)
self.__index = self
-- We need to follow ISBaseTimedAction. self.character is gonna be the surgeon
o.character = surgeon
o.patient = patient
o.limbName = limbName
o.item = item
o.patientX = patient:getX()
o.patientY = patient:getY()
o.stitchesItem = stitchesItem or nil
o.bandageItem = bandageItem or nil
o.stopOnWalk = true
o.stopOnRun = true
o.maxTime = 1000 - (surgeon:getPerkLevel(Perks.Doctor) * 50)
if o.character:isTimedActionInstant() then o.maxTime = 1 end
return o
end
function CutLimbAction:isValid()
return not ISHealthPanel.DidPatientMove(self.character,self.patient, self.patientX, self.patientY)
end
function CutLimbAction:start()
if self.patient == self.character then
-- Self
AmputationHandler.ApplyDamageDuringAmputation(self.patient, self.limbName)
else
-- Another player
---@type relayDamageDuringAmputationParams
local params = {patientNum = self.patient:getOnlineID(), limbName = self.limbName}
sendClientCommand(CommandsData.modules.TOC_RELAY, CommandsData.server.Relay.RelayDamageDuringAmputation, params )
end
---@type ISBaseTimedAction
local prevAction = self
-- Handle stitching
if self.stitchesItem then
TOC_DEBUG.print("Stitches...")
prevAction = AmputationHandler.PrepareStitchesAction(prevAction, self.limbName, self.character, self.patient, self.stitchesItem)
end
-- Handle bandages
if self.bandageItem then
prevAction = AmputationHandler.PrepareBandagesAction(prevAction, self.limbName, self.character, self.patient, self.bandageItem)
end
-- Setup cosmetic stuff
self:setActionAnim("SawLog")
self:setOverrideHandModels(self.item:getStaticModel())
-- Setup audio
self.sound = self.character:getEmitter():playSound("Amputation")
local radius = 5
addSound(self.character, self.character:getX(), self.character:getY(), self.character:getZ(), radius, radius)
end
function CutLimbAction:waitToStart()
if self.character == self.patient then
return false
end
self.character:faceThisObject(self.patient)
return self.character:shouldBeTurning()
end
function CutLimbAction:update()
self.character:setMetabolicTarget(Metabolics.HeavyWork)
if self.character ~= self.patient then
self.patient:setMetabolicTarget(Metabolics.HeavyWork)
end
end
function CutLimbAction:stopSound()
if self.sound then
self.character:getEmitter():stopSound(self.sound)
self.sound = nil
end
end
function CutLimbAction:stop()
self:stopSound()
ISBaseTimedAction.stop(self)
end
function CutLimbAction:perform()
-- Stop the sound
self:stopSound()
if self.patient == self.character then
TOC_DEBUG.print("patient and surgeon are the same, executing on the client")
local handler = AmputationHandler:new(self.limbName)
handler:execute(true)
else
TOC_DEBUG.print("patient and surgeon not the same, sending relay to server")
-- Other player
---@type relayExecuteAmputationActionParams
local params = {patientNum = self.patient:getOnlineID(), limbName = self.limbName}
sendClientCommand(CommandsData.modules.TOC_RELAY, CommandsData.server.Relay.RelayExecuteAmputationAction, params )
end
ISBaseTimedAction.perform(self)
end
return CutLimbAction

View File

@@ -0,0 +1,58 @@
---@diagnostic disable: duplicate-set-field
-- Bunch of actions shouldn't be modified by the adjusted time
local og_ISEatFoodAction_new = ISEatFoodAction.new
function ISEatFoodAction:new(character, item, percentage)
local action = og_ISEatFoodAction_new(self, character, item, percentage)
TOC_DEBUG.print("Override ISEatFoodAction")
action.skipTOC = true
return action
end
local og_ISReadABook_new = ISReadABook.new
function ISReadABook:new(character, item, time)
local action = og_ISReadABook_new(self, character, item, time)
TOC_DEBUG.print("Override ISReadABook")
action.skipTOC = true
return action
end
local og_ISTakePillAction_new = ISTakePillAction.new
function ISTakePillAction:new(character, item, time)
local action = og_ISTakePillAction_new(self, character, item, time)
TOC_DEBUG.print("Override ISTakePillAction")
action.skipTOC = true
return action
end
local og_ISTakeWaterAction_new = ISTakeWaterAction.new
function ISTakeWaterAction:new(character, item, waterUnit, waterObject, time, oldItem)
local action = og_ISTakeWaterAction_new(self, character, item, waterUnit, waterObject, time, oldItem)
TOC_DEBUG.print("Override ISTakeWaterAction")
action.skipTOC = true
return action
end
local og_ISDrinkFromBottle_new = ISDrinkFromBottle.new
function ISDrinkFromBottle:new(character, item, uses)
local action = og_ISDrinkFromBottle_new(self, character, item, uses)
TOC_DEBUG.print("Override ISDrinkFromBottle")
action.skipTOC = true
return action
end
local og_ISFinalizeDealAction_new = ISFinalizeDealAction.new
function ISFinalizeDealAction:new(player, otherPlayer, itemsToGive, itemsToReceive, time)
local action = og_ISFinalizeDealAction_new(self, player, otherPlayer, itemsToGive, itemsToReceive, time)
TOC_DEBUG.print("Override ISFinalizeDealAction")
action.skipTOC = true
return action
end
local og_ISCampingInfoAction_new = ISCampingInfoAction.new
function ISCampingInfoAction:new(character, campfireObject, campfire)
local action = og_ISCampingInfoAction_new(self, character, campfireObject, campfire)
TOC_DEBUG.print("Override ISCampingInfoAction")
action.skipTOC = true
return action
end

View File

@@ -0,0 +1,281 @@
local StaticData = require("TOC/StaticData")
local CommandsData = require("TOC/CommandsData")
local DataController = require("TOC/Controllers/DataController")
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
local CutLimbInteractionHandler = require("TOC/UI/Interactions/CutLimbInteractionHandler")
local WoundCleaningInteractionHandler = require("TOC/UI/Interactions/WoundCleaningInteractionHandler")
------------------------
local isReady = false
function SetHealthPanelTOC()
isReady = true
end
-- We're overriding ISHealthPanel to add custom textures to the body panel.
-- By doing so we can show the player which limbs have been cut without having to use another menu
-- We can show prosthesis too this way
-- We also manage the drag'n drop of items on the body to let the players use the saw this way too
---@diagnostic disable: duplicate-set-field
--ISHealthBodyPartPanel = ISBodyPartPanel:derive("ISHealthBodyPartPanel")
--* Handling drag n drop of the saw *--
local og_ISHealthPanel_dropItemsOnBodyPart = ISHealthPanel.dropItemsOnBodyPart
function ISHealthPanel:dropItemsOnBodyPart(bodyPart, items)
og_ISHealthPanel_dropItemsOnBodyPart(self, bodyPart, items)
TOC_DEBUG.print("override to dropItemsOnBodyPart running")
local cutLimbInteraction = CutLimbInteractionHandler:new(self, bodyPart)
local woundCleaningInteraction = WoundCleaningInteractionHandler:new(self, bodyPart, self.character:getUsername())
for _,item in ipairs(items) do
cutLimbInteraction:checkItem(item)
woundCleaningInteraction:checkItem(item)
end
if cutLimbInteraction:dropItems(items) or woundCleaningInteraction:dropItems(items) then
return
end
end
local og_ISHealthPanel_doBodyPartContextMenu = ISHealthPanel.doBodyPartContextMenu
function ISHealthPanel:doBodyPartContextMenu(bodyPart, x, y)
og_ISHealthPanel_doBodyPartContextMenu(self, bodyPart, x, y)
local playerNum = self.otherPlayer and self.otherPlayer:getPlayerNum() or self.character:getPlayerNum()
-- To not recreate it but reuse the one that has been created in the original method
-- TODO This will work ONLY when an addOption has been already done in the og method.
local context = getPlayerContextMenu(playerNum)
context:bringToTop()
context:setVisible(true)
local cutLimbInteraction = CutLimbInteractionHandler:new(self, bodyPart)
self:checkItems({cutLimbInteraction})
cutLimbInteraction:addToMenu(context)
local woundCleaningInteraction = WoundCleaningInteractionHandler:new(self, bodyPart, self.character:getUsername())
self:checkItems({woundCleaningInteraction})
woundCleaningInteraction:addToMenu(context)
end
--* Modifications and additional methods to handle visible amputation on the health menu *--
---Get a value between 1 and 0.1 for the cicatrization time
---@param cicTime integer
---@return integer
local function GetColorFromCicatrizationTime(cicTime, limbName)
local defaultTime = StaticData.LIMBS_CICATRIZATION_TIME_IND_NUM[limbName]
local delta = cicTime/defaultTime
return math.max(0.15, math.min(delta, 1))
end
---Try to draw the highest amputation in the health panel, based on the cicatrization time
---@param side string L or R
---@param username string
function ISHealthPanel:tryDrawAmputation(highestAmputations, side, username)
local redColor
local texture
if TOC_DEBUG.enableHealthPanelDebug then
redColor = 1
texture = getTexture("media/ui/test_pattern.png")
else
if highestAmputations[side] == nil then return end
local limbName = highestAmputations[side]
--TOC_DEBUG.print("Drawing " .. tostring(limbName) .. " for " .. username)
local cicTime = DataController.GetInstance(username):getCicatrizationTime(limbName)
redColor = GetColorFromCicatrizationTime(cicTime, limbName)
local sexPl = self.character:isFemale() and "Female" or "Male"
texture = StaticData.HEALTH_PANEL_TEXTURES[sexPl][limbName]
end
self:drawTexture(texture, self.healthPanel.x, self.healthPanel.y, 1, redColor, 0, 0)
end
function ISHealthPanel:tryDrawProsthesis(highestAmputations, side, username)
local dc = DataController.GetInstance(username) -- TODO CACHE PROSTHESIS!!! Don't use DC here
local limbName = highestAmputations[side]
if limbName and dc:getIsProstEquipped(limbName) then
self:drawTexture(StaticData.HEALTH_PANEL_TEXTURES.ProstArm[side], self.healthPanel.x, self.healthPanel.y, 1, 1, 1, 1)
end
end
local og_ISHealthPanel_render = ISHealthPanel.render
function ISHealthPanel:render()
og_ISHealthPanel_render(self)
local username = self.character:getUsername()
local highestAmputations = CachedDataHandler.GetHighestAmputatedLimbs(username)
if highestAmputations ~= nil then
-- Left Texture
self:tryDrawAmputation(highestAmputations, "L", username)
self:tryDrawProsthesis(highestAmputations, "L", username)
-- Right Texture
self:tryDrawAmputation(highestAmputations, "R", username)
self:tryDrawProsthesis(highestAmputations, "R", username)
end
end
local og_ISHealthPanel_update = ISHealthPanel.update
function ISHealthPanel:update()
og_ISHealthPanel_update(self)
-- TODO Listen for changes on other player side instead of looping this
-- FIX Re-enable it, just for test
if self.character then
local locPlUsername = getPlayer():getUsername()
local remPlUsername = self.character:getUsername()
if locPlUsername ~= remPlUsername and self:isReallyVisible() then
-- Request update for TOC DATA
local key = CommandsData.GetKey(remPlUsername)
--ModData.request(key)
end
end
end
-- We need to override this to force the alpha to 1
local og_ISCharacterInfoWindow_render = ISCharacterInfoWindow.prerender
function ISCharacterInfoWindow:prerender()
og_ISCharacterInfoWindow_render(self)
self.backgroundColor.a = 1
end
-- We need to override this to force the alpha to 1 for the Medical Check in particular
local og_ISHealthPanel_prerender = ISHealthPanel.prerender
function ISHealthPanel:prerender()
og_ISHealthPanel_prerender(self)
self.backgroundColor.a = 1
end
--- The medical check wrap the health panel into this. We need to override its color
local overrideBackgroundColor = true
local og_ISUIElement_wrapInCollapsableWindow = ISUIElement.wrapInCollapsableWindow
---@param title string
---@param resizable any
---@param subClass any
---@return any
function ISUIElement:wrapInCollapsableWindow(title, resizable, subClass)
local panel = og_ISUIElement_wrapInCollapsableWindow(self, title, resizable, subClass)
if overrideBackgroundColor then
TOC_DEBUG.print("Overriding color for panel - " .. title)
self.backgroundColor.a = 1
panel.backgroundColor.a = 1
end
return panel
end
-- This is run when a player is trying the Medical Check action on another player
local og_ISMedicalCheckAction_perform = ISMedicalCheckAction.perform
function ISMedicalCheckAction:perform()
local username = self.otherPlayer:getUsername()
TOC_DEBUG.print("Medical Action on " .. username )
-- We need to recalculate them here before we can create the highest amputations point
CachedDataHandler.CalculateAmputatedLimbs(username)
og_ISMedicalCheckAction_perform(self)
end
local og_ISHealthBodyPartListBox_doDrawItem = ISHealthBodyPartListBox.doDrawItem
function ISHealthBodyPartListBox:doDrawItem(y, item, alt)
y = og_ISHealthBodyPartListBox_doDrawItem(self, y, item, alt)
y = y - 5
local x = 15
local fontHgt = getTextManager():getFontHeight(UIFont.Small)
local username = self.parent.character:getUsername()
--local amputatedLimbs = CachedDataHandler.GetIndexedAmputatedLimbs(username)
---@type BodyPart
local bodyPart = item.item.bodyPart
local bodyPartTypeStr = BodyPartType.ToString(bodyPart:getType())
local limbName = StaticData.LIMBS_IND_STR[bodyPartTypeStr]
-- We should cache a lot of other stuff to have this working with CacheDataHandler :(
if limbName then
local dcInst = DataController.GetInstance(username)
if dcInst:getIsCut(limbName) and dcInst:getIsVisible(limbName) then
if dcInst:getIsCicatrized(limbName) then
if dcInst:getIsCauterized(limbName) then
self:drawText("- " .. getText("IGUI_HealthPanel_Cauterized"), x, y, 0.58, 0.75, 0.28, 1, UIFont.Small)
else
self:drawText("- " .. getText("IGUI_HealthPanel_Cicatrized"), x, y, 0.28, 0.89, 0.28, 1, UIFont.Small)
end
y = y + fontHgt
else
local cicaTime = dcInst:getCicatrizationTime(limbName)
-- Show it in percentage
local maxCicaTime = StaticData.LIMBS_CICATRIZATION_TIME_IND_NUM[limbName]
local percentage = (1 - cicaTime/maxCicaTime) * 100
self:drawText("- " .. getText("IGUI_HealthPanel_Cicatrization") .. string.format(" %.2f", percentage) .. "%", x, y, 0.89, 0.28, 0.28, 1, UIFont.Small)
y = y + fontHgt
local scaledDirtyness = math.floor(dcInst:getWoundDirtyness(limbName) * 100)
self:drawText("- " .. getText("IGUI_HealthPanel_WoundDirtyness") .. string.format(" %d", scaledDirtyness) .. "%", x, y, 0.89, 0.28, 0.28, 1, UIFont.Small)
y = y + fontHgt
end
if dcInst:getIsProstEquipped(limbName) then
self:drawText("- " .. getText("IGUI_HealthPanel_ProstEquipped"), x, y, 0.28, 0.89, 0.28, 1, UIFont.Small)
y = y + fontHgt
end
end
end
y = y + 5
return y
end
local og_ISHealthPanel_getDamagedParts = ISHealthPanel.getDamagedParts
function ISHealthPanel:getDamagedParts()
-- TODO Overriding it is a lot easier, but ew
if isReady then
local result = {}
local bodyParts = self:getPatient():getBodyDamage():getBodyParts()
if isClient() and not self:getPatient():isLocalPlayer() then
bodyParts = self:getPatient():getBodyDamageRemote():getBodyParts()
end
local patientUsername = self:getPatient():getUsername()
local mdh = DataController.GetInstance(patientUsername)
for i=1,bodyParts:size() do
local bodyPart = bodyParts:get(i-1)
local bodyPartTypeStr = BodyPartType.ToString(bodyPart:getType())
local limbName = StaticData.LIMBS_IND_STR[bodyPartTypeStr]
if ISHealthPanel.cheat or bodyPart:HasInjury() or bodyPart:bandaged() or bodyPart:stitched() or bodyPart:getSplintFactor() > 0 or bodyPart:getAdditionalPain() > 10 or bodyPart:getStiffness() > 5 or (mdh:getIsCut(limbName) and mdh:getIsVisible(limbName)) then
table.insert(result, bodyPart)
end
end
return result
else
return og_ISHealthPanel_getDamagedParts(self)
end
end

View File

@@ -0,0 +1,256 @@
local BaseHandler = require("TOC/UI/Interactions/HealthPanelBaseHandler")
local StaticData = require("TOC/StaticData")
local DataController = require("TOC/Controllers/DataController")
local CutLimbAction = require("TOC/TimedActions/CutLimbAction")
---------------------
-- TODO Add interaction to cut and bandage!
--* Various functions to help during these pesky checks
---Check if the item type corresponds to a compatible saw
---@param itemType string
local function CheckIfSaw(itemType)
return itemType:contains(StaticData.SAWS_TYPES_IND_STR.saw) or itemType:contains(StaticData.SAWS_TYPES_IND_STR.gardenSaw)
end
---Return a compatible bandage
---@param player IsoPlayer
---@return InventoryItem?
local function GetBandageItem(player)
local plInv = player:getInventory()
local bandageItem = plInv:FindAndReturn("Base.Bandage") or plInv:FindAndReturn("Base.RippedSheets")
---@cast bandageItem InventoryItem
return bandageItem
end
---Return a suture needle or thread (only if the player has a needle too)
---@param player IsoPlayer
---@return InventoryItem?
local function GetStitchesConsumableItem(player)
local plInv = player:getInventory()
-- Suture needle has priority
local sutureNeedle = plInv:FindAndReturn("Base.SutureNeedle")
---@cast sutureNeedle DrainableComboItem
if sutureNeedle and sutureNeedle:getUsedDelta() > 0 then
return sutureNeedle
else
-- Didn't find the suture one, so let's search for the normal one + thread
local needleItem = plInv:FindAndReturn("Base.Needle")
if needleItem == nil then return nil end
-- Found the normal one, searching for thread
local threadItem = plInv:FindAndReturn("Base.Thread")
---@cast threadItem DrainableComboItem
if threadItem and threadItem:getUsedDelta() > 0 then
return threadItem
end
end
end
---Add the action to the queue
---@param limbName string
---@param surgeon IsoPlayer
---@param patient IsoPlayer
---@param sawItem InventoryItem
---@param stitchesItem InventoryItem?
---@param bandageItem InventoryItem?
local function PerformAction(surgeon, patient, limbName, sawItem, stitchesItem, bandageItem)
-- get saw in hand
-- todo primary or secondary depending on amputation status of surgeon
ISTimedActionQueue.add(ISEquipWeaponAction:new(surgeon, sawItem, 50, true, false))
local lHandItem = surgeon:getSecondaryHandItem()
if lHandItem then
ISTimedActionQueue.add(ISUnequipAction:new(surgeon, lHandItem, 50))
end
ISTimedActionQueue.add(CutLimbAction:new(surgeon, patient, limbName, sawItem, stitchesItem, bandageItem))
end
local textAmp = getText("ContextMenu_Amputate")
local textAmpBandage = getText("ContextMenu_Amputate_Bandage")
local textAmpStitch = getText("ContextMenu_Amputate_Stitch")
local textAmpStitchBandage = getText("ContextMenu_Amputate_Stitch_Bandage")
---Adds the actions to the inventory context menu
---@param player IsoPlayer
---@param context ISContextMenu
---@param sawItem InventoryItem
---@param stitchesItem InventoryItem?
---@param bandageItem InventoryItem?
local function AddInvAmputationOptions(player, context, sawItem, stitchesItem, bandageItem)
local text
-- Set the correct text option
if stitchesItem and bandageItem then
--TOC_DEBUG.print("stitches and bandage")
text = textAmpStitchBandage
elseif not bandageItem and stitchesItem then
--TOC_DEBUG.print("only stitches")
text = textAmpStitch
elseif not stitchesItem and bandageItem then
--TOC_DEBUG.print("only bandages")
text = textAmpBandage
else
text = textAmp
end
TOC_DEBUG.print("Current text " .. tostring(text))
local option = context:addOption(text, nil)
local subMenu = context:getNew(context)
context:addSubMenu(option, subMenu)
-- TODO Separate into groups
-- Amputate -> Top/Bottom - > Left/Right - > Limb
-- for i=1, #StaticData.PROSTHESES_GROUPS_STR do
-- local group = StaticData.PROSTHESES_GROUPS_STR[i]
-- for j=1, #StaticData.SIDES_IND_STR do
-- end
-- end
-- for k,v in pairs(StaticData.LIMBS_TO_PROST_GROUP_MATCH_IND_STR) do
-- TOC_DEBUG.print(k)
-- end
local dc = DataController.GetInstance()
for i = 1, #StaticData.LIMBS_STR do
local limbName = StaticData.LIMBS_STR[i]
if not dc:getIsCut(limbName) then
local limbTranslatedName = getText("ContextMenu_Limb_" .. limbName)
subMenu:addOption(limbTranslatedName, player, PerformAction, player, limbName, sawItem, stitchesItem, bandageItem)
end
end
end
---Handler for OnFillInventoryObjectContextMenu
---@param playerNum number
---@param context ISContextMenu
---@param items table
local function AddInventoryAmputationMenu(playerNum, context, items)
local item
-- We can't access the item if we don't create the loop and start ipairs.
for _, v in ipairs(items) do
item = v
if not instanceof(v, "InventoryItem") then
item = v.items[1]
end
break
end
local itemType = item:getType()
if CheckIfSaw(itemType) then
local player = getSpecificPlayer(playerNum)
local sawItem = item
local stitchesItem = GetStitchesConsumableItem(player)
local bandageItem = GetBandageItem(player)
TOC_DEBUG.print("Stitches item: " .. tostring(stitchesItem))
TOC_DEBUG.print("Bandage item: " .. tostring(bandageItem))
AddInvAmputationOptions(player, context, sawItem, stitchesItem, bandageItem)
end
end
Events.OnFillInventoryObjectContextMenu.Add(AddInventoryAmputationMenu)
-------------------------------------
---@class CutLimbInteractionHandler : BaseHandler
---@field items table
---@field limbName string
---@field itemType string temporary
local CutLimbInteractionHandler = BaseHandler:derive("CutLimbInteractionHandler")
---Creates new CutLimbInteractionHandler
---@param panel ISUIElement
---@param bodyPart BodyPart
---@return CutLimbInteractionHandler
function CutLimbInteractionHandler:new(panel, bodyPart)
local o = BaseHandler.new(self, panel, bodyPart)
o.items.ITEMS = {}
o.limbName = BodyPartType.ToString(bodyPart:getType())
o.itemType = "Saw"
--TOC_DEBUG.print("init CutLimbInteractionHandler")
return o
end
---@param item InventoryItem
function CutLimbInteractionHandler:checkItem(item)
--TOC_DEBUG.print("CutLimbInteractionHandler checkItem")
local itemType = item:getType()
if CheckIfSaw(itemType) then
TOC_DEBUG.print("added to list -> " .. itemType)
self:addItem(self.items.ITEMS, item)
end
end
---@param context ISContextMenu
function CutLimbInteractionHandler:addToMenu(context)
--TOC_DEBUG.print("CutLimbInteractionHandler addToMenu")
local types = self:getAllItemTypes(self.items.ITEMS)
local patientUsername = self:getPatient():getUsername()
if #types > 0 and StaticData.LIMBS_TO_BODYLOCS_IND_BPT[self.limbName] and not DataController.GetInstance(patientUsername):getIsCut(self.limbName) then
TOC_DEBUG.print("addToMenu, types > 0")
for i=1, #types do
context:addOption(getText("ContextMenu_Amputate"), self, self.onMenuOptionSelected, types[i])
end
end
end
function CutLimbInteractionHandler:dropItems(items)
local types = self:getAllItemTypes(items)
if #self.items.ITEMS > 0 and #types == 1 and StaticData.LIMBS_TO_BODYLOCS_IND_BPT[self.limbName] then
self:onMenuOptionSelected(types[1])
return true
end
return false
end
---Check if CutLimbInteractionHandler is valid, the limb must not be cut to be valid
---@return boolean
function CutLimbInteractionHandler:isValid()
--TOC_DEBUG.print("CutLimbInteractionHandler isValid")
self:checkItems()
local patientUsername = self:getPatient():getUsername()
return not DataController.GetInstance(patientUsername):getIsCut(self.limbName)
end
function CutLimbInteractionHandler:perform(previousAction, itemType)
local item = self:getItemOfType(self.items.ITEMS, itemType)
previousAction = self:toPlayerInventory(item, previousAction)
TOC_DEBUG.print("Perform CutLimbInteractionHandler on " .. self.limbName)
local action = CutLimbAction:new(self:getDoctor(),self:getPatient(), self.limbName, item)
ISTimedActionQueue.addAfter(previousAction, action)
end
return CutLimbInteractionHandler

View File

@@ -0,0 +1,139 @@
-- Had to copy and paste this stuff from the base game since this is a local only class. Kinda shit, but eh
---@class BaseHandler : ISBaseObject
---@field panel ISUIElement
---@field bodyPart BodyPart
---@field items table
local BaseHandler = ISBaseObject:derive("BaseHandler")
---@param panel ISUIElement
---@param bodyPart BodyPart
---@return table
function BaseHandler:new(panel, bodyPart)
local o = {}
setmetatable(o, self)
self.__index = self
o.panel = panel
o.bodyPart = bodyPart
o.items = {}
return o
end
function BaseHandler:isInjured()
TOC_DEBUG.print("Running isInjured")
local bodyPart = self.bodyPart
return (bodyPart:HasInjury() or bodyPart:stitched() or bodyPart:getSplintFactor() > 0) and not bodyPart:bandaged()
end
function BaseHandler:checkItems()
for k,v in pairs(self.items) do
table.wipe(v)
end
local containers = ISInventoryPaneContextMenu.getContainers(self:getDoctor())
local done = {}
local childContainers = {}
for i=1,containers:size() do
local container = containers:get(i-1)
done[container] = true
table.wipe(childContainers)
self:checkContainerItems(container, childContainers)
for _,container2 in ipairs(childContainers) do
if not done[container2] then
done[container2] = true
self:checkContainerItems(container2, nil)
end
end
end
end
function BaseHandler:checkContainerItems(container, childContainers)
local containerItems = container:getItems()
for i=1,containerItems:size() do
local item = containerItems:get(i-1)
if item:IsInventoryContainer() then
if childContainers then
table.insert(childContainers, item:getInventory())
end
else
---@diagnostic disable-next-line: undefined-field
self:checkItem(item) -- This is in inherited classes, we never use this class by itself
end
end
end
function BaseHandler:dropItems(items)
return false
end
function BaseHandler:addItem(items, item)
table.insert(items, item)
end
function BaseHandler:getAllItemTypes(items)
local done = {}
local types = {}
for _,item in ipairs(items) do
if not done[item:getFullType()] then
table.insert(types, item:getFullType())
done[item:getFullType()] = true
end
end
return types
end
function BaseHandler:getItemOfType(items, type)
for _,item in ipairs(items) do
if item:getFullType() == type then
return item
end
end
return nil
end
function BaseHandler:getItemOfTag(items, type)
for _,item in ipairs(items) do
if item:hasTag(type) then
return item
end
end
return nil
end
function BaseHandler:getAllItemsOfType(items, type)
local items = {}
for _,item in ipairs(items) do
if item:getFullType() == type then
table.insert(items, item)
end
end
return items
end
function BaseHandler:onMenuOptionSelected(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
ISTimedActionQueue.add(HealthPanelAction:new(self:getDoctor(), self, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8))
end
function BaseHandler:toPlayerInventory(item, previousAction)
if item:getContainer() ~= self:getDoctor():getInventory() then
local action = ISInventoryTransferAction:new(self:getDoctor(), item, item:getContainer(), self:getDoctor():getInventory())
ISTimedActionQueue.addAfter(previousAction, action)
-- FIXME: ISHealthPanel.actions never gets cleared
self.panel.actions = self.panel.actions or {}
self.panel.actions[action] = self.bodyPart
return action
end
return previousAction
end
---@return IsoPlayer
function BaseHandler:getDoctor()
return self.panel.otherPlayer or self.panel.character
end
---@return IsoPlayer
function BaseHandler:getPatient()
return self.panel.character
end
return BaseHandler

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