447 Commits
v1.0 ... dev

Author SHA1 Message Date
ZioPao
bd532dc013 fix: relayed amputations 2026-01-31 17:32:28 +01:00
ZioPao
1d21c0d51c fix: relay wear amputation 2026-01-31 17:21:10 +01:00
ZioPao
e129b325e4 fix: wearing amputation in MP 42.13 2026-01-27 23:01:56 +01:00
ZioPao
df9f559078 fix: working amputations in mp 2026-01-27 01:33:54 +01:00
ZioPao
1fb3899a5a chore: stuff to move around for 42.13 2026-01-15 22:37:13 +01:00
ZioPao
457f89b064 fix: error while amputating 2026-01-15 22:37:06 +01:00
ZioPao
27d79c5802 chore: removed old b41 tasks 2026-01-08 01:56:20 +01:00
ZioPao
92a0b9ef18 fix: encoding UA again 2026-01-08 01:53:02 +01:00
Vinellon
494b4b576f fix encoding for translate files 2026-01-08 01:43:35 +01:00
ZioPao
76fcb1ac97 Merge branch 'dev-42.13' of https://github.com/ZioPao/The-Only-Cure into dev-42.13-merge 2026-01-08 01:39:40 +01:00
ZioPao
fba1b2e643 fix: wip changes for traits and infection healing 2026-01-08 01:26:17 +01:00
ZioPao
2cf7df16b3 refactor: changes to StaticData and registries 2026-01-08 01:25:53 +01:00
ZioPao
898e8ec653 refactor: restored new bodylocs 2026-01-08 01:23:53 +01:00
guiriguy
943e3a8f3c Refactor prosthetic limb string constants
Updated string constants for prosthetic limbs and body locations.
2025-12-18 22:51:51 +01:00
guiriguy
442af10b7a Update Registries.lua 2025-12-18 22:51:27 +01:00
guiriguy
c9d6236b89 Refactor BodyLocations.lua for improved management
Refactored BodyLocations.lua to streamline body location management and improve functionality.
2025-12-18 22:51:06 +01:00
guiriguy
e884985fe3 Refactor player courage and temperature checks 2025-12-18 22:50:28 +01:00
guiriguy
38fe71bc72 Refactor ProsthesisHandler for B42 compatibility 2025-12-18 22:48:17 +01:00
guiriguy
b650b2a590 Refactor tourniquet check and update patient stats 2025-12-18 22:48:00 +01:00
guiriguy
da7621c0f0 Refactor LocalPlayerController to use TOC registries 2025-12-18 22:47:31 +01:00
guiriguy
acd2e9c547 Refactor TOC registries for traits and body locations 2025-12-18 22:46:57 +01:00
ZioPao
27d9b3a513 fix: bodyloc check for tourniquets 2025-12-16 01:02:51 +01:00
ZioPao
aa61a3390c fix: wrong dir for Translate 2025-12-16 00:56:06 +01:00
ZioPao
b84c2a593d fix: items bodylocs 2025-12-16 00:56:00 +01:00
ZioPao
cdf0473065 fix: Type to ItemType in items ZedScripts 2025-12-16 00:51:23 +01:00
ZioPao
40da330b41 fix: trait init in LocalPlayerController 2025-12-16 00:51:05 +01:00
ZioPao
20fa1bd05e refactor: removed compat with b41 entirely 2025-12-16 00:34:40 +01:00
ZioPao
3f888e7f22 fix: bodyloc for b42 mp 2025-12-14 10:39:28 +01:00
Vinellon
d84554ff07 fix encoding for translate files 2025-11-17 19:37:38 +03:00
Pao
ec87d128e4 Merge pull request #217 from ZioPao/dev
v2.2.2
2025-11-15 21:38:24 +01:00
ZioPao
be4588fc43 chore: bump to mod version - 2.2.2 2025-10-12 17:45:09 +02:00
ZioPao
4bafb3a15c fix: went back to previous look for amputations 2025-10-12 17:16:52 +02:00
ZioPao
fd056aea1e fix: amputation items not hiding anymore 2025-10-09 22:32:41 +02:00
ZioPao
100abab2aa fix: workshop.txt 2025-10-05 16:17:55 +02:00
ZioPao
789b0635e0 fix: workshop.txt for b42 2025-10-05 16:08:56 +02:00
ZioPao
d6d9ba7028 Bump to mod version 2025-10-05 16:03:21 +02:00
ZioPao
a182fb07e5 chore: updates to workshop files for b42 2025-10-04 18:55:48 +02:00
ZioPao
528a43247a add: better wound textures 2025-10-04 16:38:50 +02:00
ZioPao
b281ce7d12 add: gen_amp_textures 2025-10-04 16:38:25 +02:00
ZioPao
9db3a1c944 fix: better masks for amputations 2025-10-04 15:40:10 +02:00
ZioPao
a74e33134d fix: switched to multiple body locs to fix some old issues and prepare for better visuals overhaul 2025-10-04 02:32:48 +02:00
ZioPao
1caf4a4b49 fix: fix for cheat prevention with both hands feasibility to false 2025-09-30 01:01:41 +02:00
ZioPao
5f71cebdc0 fix: override for both hands interaction for b42 and cheat prevention 2025-09-30 00:58:37 +02:00
ZioPao
4714bd7b82 fix: recipes 2025-09-30 00:53:15 +02:00
ZioPao
4d20cc2559 fix: baseline recipes for b42 2025-09-29 02:03:00 +02:00
ZioPao
5ec0ec1a9b fix: 42.12 new bodyloc 2025-09-29 01:31:18 +02:00
ZioPao
89a28e846a refactor: cleaning 2025-09-29 01:04:08 +02:00
ZioPao
a511ac777a refactor: reorganized settings for cd 2025-07-14 02:46:43 +02:00
ZioPao
04c7172d82 refactor: removed old code 2025-07-14 01:15:39 +02:00
ZioPao
522c49b40c chore: cleaning and setting up stuff 2025-04-17 18:23:18 +02:00
Pao
b5266cd7cd Merge pull request #200 from ZioPao/dev
v2.1.7
2025-04-17 16:18:41 +02:00
ZioPao
8c8aa8351b mod version 2025-04-17 15:53:49 +02:00
ZioPao
3eeb9d1000 fix: hotfix to IgnoredActions 2025-04-17 15:46:22 +02:00
ZioPao
c60a2c56ec fix: fixed (once and for all, hopefully) hotbar handling 2025-04-17 12:54:50 +02:00
ZioPao
69025c8262 fix: disabled ISDetachItemHotbar override, broken and unneccesary 2025-04-17 12:52:23 +02:00
ZioPao
80e9391db5 fix: fixed ignored actions that should ignore toc calculations 2025-04-17 12:42:35 +02:00
ZioPao
d7bdee1d26 fix: broken skipTOC check for timed actions 2025-04-17 12:18:27 +02:00
ZioPao
8234abd5e2 refactor: changed check for version 2025-04-17 11:26:58 +02:00
ZioPao
9011579f08 chore: changes to structure 2025-04-17 11:01:03 +02:00
ZioPao
be6466976c add: added known incompatibilies to mod.info for B42 2025-04-17 11:00:46 +02:00
ZioPao
9ab584d977 chore: fix to some stupid config issues 2025-04-17 03:38:02 +02:00
ZioPao
b0d3520173 fix: #174 2025-04-15 03:07:20 +02:00
ZioPao
bbd36f7dc2 chore: changes to vscode config and updated gitignore for symink 2025-04-15 03:02:38 +02:00
ZioPao
3fc37f56d6 refactor: cleaning dev stuff 2025-04-13 03:21:34 +02:00
ZioPao
d5fd735de8 refactor: cleaning code 2025-04-13 03:21:06 +02:00
ZioPao
253c5717a9 refactor: removed unused code 2025-04-13 03:16:06 +02:00
ZioPao
d850691053 Chore: changes to vscode config 2025-04-13 03:08:49 +02:00
ZioPao
f728520f9e Chore: fixed config for umbrella 2025-04-12 15:16:02 +02:00
ZioPao
67f51ca845 Change: unified perks into one single parent, added translations for perk tooltips (b42) 2025-04-03 00:37:18 +02:00
ZioPao
e07af54d27 Fix: moved UI files for b42 to correct location 2025-04-03 00:16:21 +02:00
ZioPao
6e47f945c5 Add: #180 2025-04-03 00:00:53 +02:00
ZioPao
644376cea0 Fix: #186 2025-04-02 23:57:17 +02:00
ZioPao
6a3fa76e00 Fix: added more checks in DataController to prevent issues, thanks PhysiksTV 2025-04-02 18:12:45 +02:00
ZioPao
6e674959ab UI: Fixed color for Health panel female covers (b41 only) 2025-04-02 02:04:44 +02:00
ZioPao
d52f3f6bf2 UI: Moved specific b42 health panel files to 42 folder, restored old files for b41 2025-04-02 01:59:58 +02:00
ZioPao
5acab111f2 git: Modified gitignore 2025-04-02 01:57:35 +02:00
ZioPao
61f505fa8e Dev: modified tasks and settings (vscode) 2025-04-02 01:36:37 +02:00
ZioPao
6abd89cea7 UI: Fixed male Health Panel 2025-04-02 01:32:30 +02:00
ZioPao
768a0dbdab UI: Better test pattern for Health Panel debugging 2025-04-02 01:16:51 +02:00
ZioPao
d96c26d099 UI: fixed health panel for Female chars 2025-04-02 00:42:58 +02:00
ZioPao
f93c0503f8 Fix unequip prosthesis for b42 2025-03-31 22:46:12 +02:00
ZioPao
671bf133e3 Fixes for overriden methods, compat with b41 2025-03-31 22:22:17 +02:00
ZioPao
1e754895a0 Re-added lua timers 2025-03-31 20:56:23 +02:00
ZioPao
9a11047e3c Fix to Traits 2025-03-31 19:57:38 +02:00
ZioPao
db6f315f89 Readded translations, finalized folder struct 2025-03-31 19:57:27 +02:00
ZioPao
729e3b62e7 Reverted common for B41 2025-03-31 06:45:44 +02:00
ZioPao
7c8cfb0fcc Added mod.info for b41 2025-03-31 06:43:19 +02:00
ZioPao
bb19da2b4b Removed icon and poster from versioned folder, added dynamically 2025-03-31 02:47:26 +02:00
ZioPao
471608f9ba minVersion=42.6 2025-03-31 02:40:13 +02:00
ZioPao
71b854efe2 Moved everything to common. 2025-03-31 02:36:15 +02:00
ZioPao
e919c8c01b Bump to mod version and changed folder for specific game version 2025-03-31 02:17:40 +02:00
ZioPao
b6b61b872f Bump to mod version 2025-03-31 02:15:49 +02:00
ZioPao
58d5c8e13d Moved TR translation to correct folder 2025-03-31 02:00:19 +02:00
ZioPao
cc5e67aceb Moved Translations to common folder, merged pgmbru changes manually 2025-03-31 01:59:27 +02:00
ZioPao
f77a357dab Updated to current unstable version and fixed Healthpanel again 2025-03-31 01:46:49 +02:00
Pao
499db8cd78 Merge pull request #193 from ZioPao/dev-b42
Dev b42
2025-03-31 00:40:24 +02:00
Pao
ffce5fac2e Merge pull request #182 from VVentos0/main
Turkish Translation Added
2025-02-26 01:51:43 +01:00
VVentos0
81842f7020 Turkish Translate Added
I made Turkish Translate for your mod. This Translate only for B41 because the encode is ANSI (Windows-1254)

If you going to update your mod to B42 .txt files should be UTF-08 I'll post B42 files too. Please don't forget to add B42 Translation after B42 Update <3
2025-01-11 10:17:03 +03:00
ZioPao
d846b853ff Fix for ISHealthPanel 2025-01-04 21:21:05 +01:00
ZioPao
0f4117cd34 moved common files 2025-01-04 21:07:34 +01:00
ZioPao
736d527a13 Fix instanceItem, oops 2025-01-04 21:00:39 +01:00
ZioPao
1ded7f976c Replaced InventoryItemFactory with instanceItem 2025-01-04 20:46:11 +01:00
ZioPao
6635cc19b2 moved folders 2025-01-04 20:44:12 +01:00
ZioPao
5d8e60a2e4 Updated tasks for b42 dev 2025-01-04 20:25:22 +01:00
Pao
5515b6bd4f Merge pull request #157 from ZioPao/dev
v2.1.5
2024-10-22 01:12:27 +02:00
ZioPao
ba267cb900 I should stop trying to fix stuff while I'm falling asleep 2024-10-22 01:09:41 +02:00
Pao
90ca2edbce Merge pull request #155 from ZioPao/dev
v2.1.5
2024-10-22 00:11:35 +02:00
ZioPao
00d5d83ea2 Bump to mod version 2024-10-22 00:10:49 +02:00
ZioPao
849c17051a Fixed broken checks for admin menu 2024-10-22 00:10:40 +02:00
Pao
0071d2ac3f Merge pull request #148 from ZioPao/dev
v2.1.4
2024-10-19 18:42:08 +02:00
ZioPao
68605ddb35 Bump to mod version 2024-10-19 18:37:02 +02:00
Pao
371134faa7 Merge pull request #144 from ZioPao/dev
v2.1.3
2024-10-17 23:52:07 +02:00
ZioPao
132ce24328 Added confirmation for amputations 2024-10-17 23:50:51 +02:00
ZioPao
4af127196c removed useless require 2024-10-17 23:22:08 +02:00
ZioPao
39bccaf895 Disabled other stuff 2024-10-12 19:51:21 +02:00
ZioPao
54c5958715 Disabled some non-functioning overrides 2024-10-12 19:17:46 +02:00
Pao
6b79ebf003 Merge pull request #139 from pllq/main
Ukrainian translation
2024-10-12 18:51:22 +02:00
ZioPao
75d5cb180d Fix to familiarity timing bug 2024-10-12 18:48:52 +02:00
pllq
245f81be26 updated Sandbox_UA.txt
added new translation for ukrainian langauge in Sandbox_UA.txt
2024-10-05 00:11:41 +03:00
pllq
1f950e0663 Delete media/lua/shared/Translate/UA/Sandbox_UA.txt
deleted old Sandbox_UA.txt
2024-10-05 00:11:05 +03:00
pllq
aecb5448ea translation for the UI file
added translation for the UI file
2024-10-05 00:09:44 +03:00
Pao
c02ac7dacc Merge pull request #123 from ZioPao/main
backmerge to dev since i'm an idiot part 2
2024-08-23 04:50:16 +02:00
ZioPao
332e02d9fd Fix to force reinit breaking actions in SP 2024-08-23 04:48:15 +02:00
ZioPao
a2b3db6793 Fix to unequip 2024-08-23 04:48:03 +02:00
Pao
6883c81322 Merge pull request #121 from ZioPao/dev
v2.1.3
2024-08-23 03:04:44 +02:00
ZioPao
600c55f5f8 Bump to mod version 2024-08-23 03:03:01 +02:00
ZioPao
bf20aec94f Fixed admin commands in sp 2024-08-23 02:53:04 +02:00
Pao
26c269e8ba Update README.md 2024-08-21 22:21:19 +02:00
ZioPao
db42feed35 Added exceptions for various equipping actions 2024-08-21 17:23:12 +02:00
ZioPao
10f97cffa1 Fixed picking up broken glass with item in off hand 2024-08-21 13:21:09 +02:00
ZioPao
22e6621300 Removed unused param 2024-08-21 12:36:04 +02:00
ZioPao
4d2b62e4a1 Error check for OnProsthesisEquipped 2024-08-21 01:55:59 +02:00
Pao
dd0d48cd08 Merge pull request #114 from ZioPao/dev
v2.1.2
2024-08-17 19:27:58 +02:00
ZioPao
2de512b447 Bump mod version 2024-08-17 19:26:41 +02:00
ZioPao
1765d82ad9 Implemented Forced Amputation and some additional safety checks 2024-08-17 19:15:58 +02:00
ZioPao
c52cca09f1 Merge branch 'dev' of https://github.com/ZioPao/The-Only-Cure into dev 2024-08-17 18:26:14 +02:00
ZioPao
545d5c7573 Implemented random limb side for Zombie Amputations 2024-08-17 18:26:13 +02:00
ZioPao
99d0847caa Updated workshop.txt 2024-08-17 16:51:37 +02:00
Pao
3b7fe2ec58 Merge pull request #113 from ZioPao/dev
v2.1.1
2024-08-17 16:48:59 +02:00
Pao
92fc80095d Merge pull request #109 from Pgmbru/Pgmbru-patch-FR
Pgmbru patch fr
2024-08-17 16:48:09 +02:00
ZioPao
751e36cba7 Fixed zombie amputations not enabling 2024-08-17 16:47:28 +02:00
Pgmbru
487e616801 Update Sandbox_FR.txt
Add Translate + Update Sandbox_FR
2024-08-12 03:04:07 +01:00
Pao
d5915cd287 Merge pull request #105 from ZioPao/dev
v2.1
2024-08-11 16:15:53 +02:00
ZioPao
d1f63ab55f Removed broken linter 2024-08-11 16:15:38 +02:00
ZioPao
857515576e Disabled zombies amputations by default 2024-08-11 16:14:05 +02:00
ZioPao
69c6177be5 Test linter 2024-08-11 13:32:28 +02:00
Pgmbru
38fa63324c Add Translate FR 2024-08-06 00:05:11 +01:00
ZioPao
940f5486aa Added toggle for Zombie amputations 2024-07-15 12:19:11 +02:00
ZioPao
11622795e1 Working zombies amputations once again 2024-07-15 12:15:35 +02:00
ZioPao
cb7603feea Bump to mod.info (2.1) 2024-07-15 10:59:40 +02:00
ZioPao
7b5e8e15d5 Added failsafe for iMeds compat 2024-07-15 10:59:15 +02:00
ZioPao
9a74710c1e Added preliminary compatibility with iMeds 2024-07-15 10:54:34 +02:00
ZioPao
3f7bf56be2 Added translation credits 2024-07-13 19:33:07 +02:00
Pao
501c2fbabd Merge pull request #93 from ZioPao/dev
v2.0.15
2024-07-13 19:30:30 +02:00
ZioPao
3dad820014 Fixed cicatrization force-applying with traits 2024-07-13 19:27:55 +02:00
ZioPao
2f82021b5d Fixed bandage\stitches action after amputation applying to wrong body part 2024-07-13 19:13:18 +02:00
ZioPao
a3287bb2bc Fixed suture needle causing an error 2024-07-13 19:07:17 +02:00
ZioPao
6c82f1a96f Add logs for traits 2024-07-13 18:57:16 +02:00
ZioPao
f2446c0781 Added script to automatically bump version 2024-07-13 18:57:10 +02:00
ZioPao
4d6ee68054 bump to mod.info 2024-07-13 17:27:48 +02:00
ZioPao
854238ef98 Merge branch 'dev' of https://github.com/ZioPao/The-Only-Cure into dev 2024-07-13 17:26:06 +02:00
ZioPao
2b41745a05 Added Korean translation 2024-07-13 17:26:04 +02:00
Pao
c2e15151a6 Merge pull request #84 from pllq/main
Added Ukrainian localization
2024-06-09 23:28:33 +02:00
pllq
b25f52a40d Add files via upload
Ukrainian localization for the TOC mod
2024-06-09 23:54:59 +03:00
Pao
145ec33620 Merge pull request #80 from ZioPao/dev
v2.0.14
2024-06-04 09:53:14 +02:00
ZioPao
307e19fd52 Fixed bug with saws 2024-06-04 09:48:17 +02:00
Pao
9eb9ea0263 Merge pull request #79 from ZioPao/dev
v2.0.13
2024-06-03 21:59:48 +02:00
ZioPao
f8fc8e7f60 Merge branch 'dev' of https://github.com/ZioPao/The-Only-Cure into dev 2024-06-03 21:59:15 +02:00
ZioPao
3d1cfba85c bump to mod.info 2024-06-03 21:59:13 +02:00
Pao
1f6fc63f46 Merge pull request #78 from Rinary1/main
RU Translate For This Mod
2024-06-03 21:57:45 +02:00
Rinary
3931c0ed4d Add files via upload 2024-06-03 20:29:43 +03:00
Pao
74eb7763cf Merge pull request #75 from ZioPao/dev
v2.0.12
2024-05-31 10:53:56 +02:00
ZioPao
e0e65c022f bump to mod.info 2024-05-31 10:52:24 +02:00
ZioPao
574526aca0 Fix: traits handling 2024-05-31 10:50:51 +02:00
ZioPao
29f21decb4 hotfix to traits 2024-05-31 10:37:39 +02:00
ZioPao
b70dd619a4 Disabled insensitive trait for now 2024-05-28 10:01:03 +02:00
Pao
0d5814dad1 Merge pull request #72 from ZioPao/dev
v2.0.11
2024-05-15 13:14:52 +02:00
ZioPao
5f64ead354 Fixed disabling of Interact Key 2024-05-15 13:13:27 +02:00
ZioPao
80c8b22faa lock for HandleDamage not disengaging in some cases 2024-05-15 11:52:45 +02:00
ZioPao
696edc1f1d Fixed wrong skin color for amputations 2024-05-15 11:47:28 +02:00
ZioPao
a1336c8d2e Destroying the instance will break stuff 2024-05-15 11:38:08 +02:00
Pao
1187f1a1b6 Merge pull request #60 from ZioPao/dev
disabled useless log
2024-05-09 14:52:00 +02:00
ZioPao
dc921e4c04 disabled useless log 2024-05-09 14:50:48 +02:00
Pao
bff6a2d4d6 Merge pull request #59 from ZioPao/dev
v2.0.10
2024-05-09 14:47:52 +02:00
ZioPao
36c3418a3e bump to mod version 2024-05-09 14:47:28 +02:00
ZioPao
a7e32bc69b Fix to HandleDamage breaking when getIsIgnoredPartInfected==true 2024-05-09 14:46:31 +02:00
Pao
139ad5828a Merge pull request #55 from ZioPao/dev
v2.0.9
2024-05-07 17:14:53 +02:00
ZioPao
1636ca7e13 bump to mod version 2024-05-07 17:13:56 +02:00
ZioPao
46a8ddc207 Added some tests 2024-05-07 17:13:27 +02:00
ZioPao
26e0324f4a Fixed bug related to BodyLocations that would break equipping prosthesis 2024-05-07 17:04:32 +02:00
ZioPao
c1faeeaabf Potential fix to bleeding issue 2024-05-07 16:58:55 +02:00
Pao
a93c8a56bb Merge pull request #51 from ZioPao/dev
v2.0.8
2024-05-06 22:07:00 +02:00
ZioPao
b33cee7271 hotfix 2024-05-06 22:06:26 +02:00
Pao
6e68e12b60 Merge pull request #49 from ZioPao/dev
v2.0.7
2024-05-06 17:15:27 +02:00
ZioPao
013f852e7e Fixed huge bug that would break things after a player died 2024-05-06 17:14:45 +02:00
Pao
99dbaea143 Update issue templates 2024-05-06 10:15:31 +02:00
Pao
a09a1520a1 Update issue templates 2024-05-06 01:28:35 +02:00
ZioPao
a7a064119d Added destroy instance in DataController after death 2024-05-05 21:07:33 +02:00
Pao
6458988cc8 Merge pull request #45 from ZioPao/dev
v2.0.6
2024-05-05 18:05:06 +02:00
ZioPao
9b1876b235 mod.info and version bump 2024-05-05 18:04:10 +02:00
ZioPao
0d9ee4203c Revert "Revert "Added tests for blood on limbs""
This reverts commit 2ea03601f5.
2024-05-05 18:03:39 +02:00
ZioPao
2ea03601f5 Revert "Added tests for blood on limbs"
This reverts commit 3d4a54418c.
2024-05-05 18:03:08 +02:00
ZioPao
3d4a54418c Added tests for blood on limbs 2024-05-05 18:02:48 +02:00
Pao
ba4f161122 Merge pull request #44 from ZioPao/dev
v2.0.5
2024-05-05 17:02:45 +02:00
ZioPao
17718cbbca bit of cleaning 2024-05-05 17:02:14 +02:00
ZioPao
f9ad597d2b bump to mod.info 2024-05-05 17:01:40 +02:00
ZioPao
c8b6a8c5ed Wash Yourself override 2024-05-05 16:58:16 +02:00
Pao
df8591b1ef Merge pull request #42 from ZioPao/dev
Added ProstFamiliarity in adjustMaxTime function
2024-05-05 13:57:31 +02:00
ZioPao
9478e0faa1 Added ProstFamiliarity in adjustMaxTime function 2024-05-05 13:56:58 +02:00
Pao
91a6eb7763 Merge pull request #41 from ZioPao/dev
v2.0.4
2024-05-05 13:52:02 +02:00
ZioPao
d512f0ba81 bump to mod.info and _version 2024-05-05 13:51:03 +02:00
ZioPao
31995965f9 Added leveling for ProstFamiliarity, dynamic xp 2024-05-05 13:50:47 +02:00
ZioPao
d35840d825 Added level scaling for prosthetics 2024-05-05 13:27:20 +02:00
ZioPao
e270b4f73b updated steam desc 2024-05-05 12:38:20 +02:00
Pao
1a754f4012 Merge pull request #38 from ZioPao/dev
bump to modversion
2024-05-05 01:01:35 +02:00
ZioPao
4757d9dfa8 bump to modversion 2024-05-05 01:00:49 +02:00
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
534 changed files with 8722 additions and 7558 deletions

24
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,24 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ZioPao
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Logs**
Please upload your game logs too. Server logs are useful too.
**Additional context**
Add any other context about the problem here.

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 }}/*

2
.gitignore vendored
View File

@@ -0,0 +1,2 @@
dev_stuff/gen_amp_textures/.venv
dev_stuff/gen_amp_textures/output

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>

81
.vscode/settings.json vendored
View File

@@ -1,64 +1,27 @@
{ {
"todo-tree.tree.scanMode": "workspace", "Lua.workspace.library": [
"${addons}/umbrella-unstable/module/library"
],
"Lua.runtime.version": "Lua 5.1",
"Lua.runtime.path": [
"?.lua",
"?/init.lua",
"server/?.lua"
],
"Lua.completion.requireSeparator": "/",
"Lua.runtime.builtin": {
"debug": "disable",
"io": "disable",
"package": "disable"
},
"Lua.workspace.checkThirdParty": false,
"Lua.workspace.ignoreDir": [
".vscode",
"dev_stuff",
"workshop_files"
],
"Lua.diagnostics.globals": [ "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", "FHSwapHandsAction",
"getClassFieldVal", "timer"
"SandboxVars",
"getClassField",
"ISWearClothing",
"SyncXp",
"ISClothingExtraAction",
"SwapItConfig",
"getTimestamp",
"addSound"
] ]
} }

99
.vscode/tasks.json vendored
View File

@@ -4,31 +4,110 @@
"version": "2.0.0", "version": "2.0.0",
"tasks": [ "tasks": [
{ {
"label": "Run Zomboid Debug No Steam", "label": "Create Workshop folder",
"type": "shell",
"options": {"statusbar": {"label": "$(combine) Assemble Mod - B42"}},
"command": "python ${config:zomboid_user_folder}/PaosCrap/make_workshop_pack.py \"42\" \"picch\" \"${workspaceFolderBasename}\" \"\"",
},
{
"label": "Create Workshop folder",
"type": "shell",
"options": {"statusbar": {"label": "$(combine) Assemble Mod (Test)"}},
"command": "python ${config:zomboid_user_folder}/PaosCrap/make_workshop_pack.py \"picch\" \"${workspaceFolderBasename}\" \"test\"",
},
{
"label": "Bump Mod Version",
"type": "shell",
"options": {"statusbar": {"label": "$(arrow-up) Bump Mod Version"}},
"command": "python ${config:zomboid_user_folder}/PaosCrap/bump_version.py common/media/lua/client/TOC/Main.lua",
},
{
"label": "Run Zomboid Debug No Steam (42)",
"type": "shell", "type": "shell",
"command": "\"E:\\Steam\\steamapps\\common\\ProjectZomboid\\ProjectZomboid64 - nosteam-debug.bat\"",
"presentation": { "presentation": {
"group": "groupZomboid" "group": "groupZomboid"
}, },
"command": "\"${config:zomboid_folder_b42}\\ProjectZomboid64_Debug_NoSteam.bat\"",
"options": {"statusbar": {"label": "$(run) Zomboid client (42)"}},
"problemMatcher": [ "problemMatcher": [
"$eslint-stylish" "$eslint-stylish"
] ]
}, },
{ {
"label": "Run two instances of Zomboid Debug No Steam", "label": "Run Zomboid Debug No Steam (42) - 2nd Instance",
"dependsOn": [ "type": "shell",
"Run Zomboid Debug No Steam", "presentation": {
"Run Zomboid Debug No Steam" "group": "groupZomboid"
], },
"problemMatcher": [] "command": "\"${config:zomboid_folder_b42}\\ProjectZomboid64_Debug_NoSteam.bat\"",
"options": {"statusbar": {"label": "$(run) Zomboid client (42) - 2nd Instance"}},
"problemMatcher": [
"$eslint-stylish"
]
}, },
// {
// "label": "Run Zomboid Debug No Steam",
// "type": "shell",
// "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 2"],
// "problemMatcher": []
// },
{ {
"label": "Run Zomboid Test Server", "label": "Run Zomboid Test Server",
"options": {"statusbar": {"label": "$(run) Zomboid Server (TOC)"}},
"type": "shell", "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": [ "problemMatcher": [
"$eslint-stylish" "$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"
// ]
// },
// {
// "label": "Run Zomboid Test Server 3",
// "options": {"statusbar": {"label": "$(run) Zomboid Server (TOC+FH+BH+iMedsFixed)"}},
// "type": "shell",
// "command":"\"${config:zomboid_server_folder}\\StartServer64_nosteam_custom.bat\" TOC_FH_BH_imeds",
// "problemMatcher": [
// "$eslint-stylish"
// ]
// }
] ]
} }

View File

@@ -0,0 +1,62 @@
<?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>4</m_Masks>
<m_Masks>3</m_Masks>
<!-- <m_UnderlayMasksFolder>media/textures/Amputations/Masks</m_UnderlayMasksFolder> -->
<!-- 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,60 @@
<?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>
<!-- <m_UnderlayMasksFolder>media/textures/Amputations/Masks</m_UnderlayMasksFolder> -->
<!-- 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,39 @@
<?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\Hand\skin01_b</textureChoices>
<textureChoices>Amputations\Human\Hand\skin02_b</textureChoices>
<textureChoices>Amputations\Human\Hand\skin03_b</textureChoices>
<textureChoices>Amputations\Human\Hand\skin04_b</textureChoices>
<textureChoices>Amputations\Human\Hand\skin05_b</textureChoices>
<textureChoices>Amputations\Human\Hand\skin01_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Hand\skin02_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Hand\skin03_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Hand\skin04_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Hand\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>
</clothingItem>

View File

@@ -0,0 +1,37 @@
<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\Hand\skin01_b</textureChoices>
<textureChoices>Amputations\Human\Hand\skin02_b</textureChoices>
<textureChoices>Amputations\Human\Hand\skin03_b</textureChoices>
<textureChoices>Amputations\Human\Hand\skin04_b</textureChoices>
<textureChoices>Amputations\Human\Hand\skin05_b</textureChoices>
<textureChoices>Amputations\Human\Hand\skin01_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Hand\skin02_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Hand\skin03_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Hand\skin04_hairy_b</textureChoices>
<textureChoices>Amputations\Human\Hand\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>
</clothingItem>

View File

@@ -0,0 +1,58 @@
<?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>
<!-- <m_UnderlayMasksFolder>media/textures/Amputations/Masks</m_UnderlayMasksFolder> -->
<!-- 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,58 @@
<?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>
<!-- <m_UnderlayMasksFolder>media/textures/Amputations/Masks</m_UnderlayMasksFolder> -->
<!-- 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

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

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<fileGuidTable>
<files>
<path>media/clothing/clothingItems/Amputation_Hand_R.xml</path>
<guid>f114e53a-b92e-4639-8d8c-2b43ab981885</guid>
</files>
<files>
<path>media/clothing/clothingItems/Amputation_ForeArm_R.xml</path>
<guid>e6f80efd-22e5-49e0-8b24-537519d42b37</guid>
</files>
<files>
<path>media/clothing/clothingItems/Amputation_UpperArm_R.xml</path>
<guid>db8ccad2-b76f-44bd-93ab-1eefa25beade</guid>
</files>
<files>
<path>media/clothing/clothingItems/Amputation_Hand_L.xml</path>
<guid>2de93af2-b7a8-4c04-84d1-28d92cce8a0f</guid>
</files>
<files>
<path>media/clothing/clothingItems/Amputation_ForeArm_L.xml</path>
<guid>d3816fe0-48e1-4cf5-a8e4-48c72595edb4</guid>
</files>
<files>
<path>media/clothing/clothingItems/Amputation_UpperArm_L.xml</path>
<guid>646cafa5-3fa1-41af-9ca0-aa57cca3b36d</guid>
</files>
<!--Prosthetics -->
<files>
<path>media/clothing/clothingItems/Prost_HookArm_L.xml</path>
<guid>05338f5e-e984-49c2-be79-81af9ae8e818</guid>
</files>
<files>
<path>media/clothing/clothingItems/Prost_HookArm_R.xml</path>
<guid>8ee7e1bc-2c21-428e-a15d-760d98df973d</guid>
</files>
<files>
<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/Surg_Arm_Tourniquet_R.xml</path>
<guid>9a5fe063-63c7-4e6f-81ca-ee77c6678e0d</guid>
</files>
</fileGuidTable>

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,114 @@
local CommandsData = require("TOC/CommandsData")
local ClientRelayCommands = require("TOC/ClientRelayCommands")
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(isClient() and isAdmin() or isDebugEnabled()) 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
-- ugly This whole section should be done better
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()
if isClient() then
sendClientCommand(CommandsData.modules.TOC_RELAY, CommandsData.server.Relay.RelayExecuteInitialization,
{ patientNum = clickedPlayerNum })
else
ClientRelayCommands.ReceiveExecuteInitialization()
end
end)
-- Force amputation
local forceAmpOption = subMenu:addOption(getText("ContextMenu_Admin_ForceAmputation"), nil, nil)
local forceAmpSubMenu = ISContextMenu:getNew(subMenu)
context:addSubMenu(forceAmpOption, forceAmpSubMenu)
for i = 1, #StaticData.LIMBS_STR do
local limbName = StaticData.LIMBS_STR[i]
local limbTranslatedName = getText("ContextMenu_Limb_" .. limbName)
forceAmpSubMenu:addOption(limbTranslatedName, nil, function()
if isClient() then
sendClientCommand(CommandsData.modules.TOC_RELAY, CommandsData.server.Relay.RelayForcedAmputation,
{ patientNum = clickedPlayerNum, limbName = limbName })
else
ClientRelayCommands.ReceiveExecuteAmputationAction({surgeonNum=clickedPlayerNum, limbName=limbName, damagePlayer=false})
end
end)
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,85 @@
local CommandsData = require("TOC/CommandsData")
local AmputationHandler = require("TOC/Handlers/AmputationHandler")
local DataController = require("TOC/Controllers/DataController")
--------------------------------------------
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) -- fix broken, getOnlineID not working anymore
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)
-- Check if player already doesn't have that limb or it's a dependant limb.
-- Mostly a check for admin forced amputations more than anything else, since this case is handled in the GUI already.
local dcInst = DataController.GetInstance()
if dcInst:getIsCut(args.limbName) then return end
local handler = InitAmputationHandler(args.limbName, args.surgeonNum)
handler:execute(args.damagePlayer)
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
--* WEAR AMPUTATION ITEM *--
function ClientRelayCommands.ReceiveWearAmputation(args)
local itemName = args.itemName
local clothingItem = getPlayer():getInventory():FindAndReturn(itemName)
getPlayer():setWornItem(clothingItem:getBodyLocation(), clothingItem)
end
--* TRIGGERED BY ADMINS *--
function ClientRelayCommands.ReceiveExecuteInitialization()
local LocalPlayerController = require("TOC/Controllers/LocalPlayerController")
LocalPlayerController.InitializePlayer(true)
end
---Creates a new handler and execute the amputation function on this client
---@param args receiveForcedCicatrizationParams
function ClientRelayCommands.ReceiveForcedCicatrization(args)
local dcInst = DataController.GetInstance()
--dcInst:setCicatrizationTime(args.limbName, 1)
dcInst:setIsCicatrized(args.limbName, true)
dcInst:apply()
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)
-- TODO temporary
return ClientRelayCommands

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,79 @@
---@class Compat
---@field handlers table<string, {fun : function, isActive : boolean}>
local Compat = {
handlers = {}
}
--- Brutal hands has a TOC_COMPAT but its check is wrong and uses an old API.
function Compat.BrutalHandwork()
BrutalHands = BrutalHands or {}
BrutalHands.TOC = require("TOC/API")
end
--- Was handled inside old TOC
function Compat.FancyHandwork()
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
function Compat.iMeds()
require("Component/Interface/Service/ContextMenu/Menu/HealthPanel/HealthPanelMenuInitializer")
-- placeholder, in case we need to do something more drastic with it.
end
------------------------------
Compat.handlers = {
["BrutalHandwork"] = {
fun = Compat.BrutalHandwork,
isActive = false},
["FancyHandwork"] = {
fun = Compat.FancyHandwork,
isActive = false},
-- either or
['iMeds'] = {
fun = Compat.iMeds,
isActive = false},
['iMedsFixed'] = {
fun = Compat.iMeds,
isActive = false}
-- TODO Check if FirstAidOverhaul can be made compatible
}
function Compat.RunModCompatibility()
local activatedMods = getActivatedMods()
TOC_DEBUG.print("Checking for mods compatibility")
for k, modCompatHandler in pairs(Compat.handlers) do
if activatedMods:contains(k) then
TOC_DEBUG.print("Found " .. k .. ", running compatibility handler")
modCompatHandler.fun()
modCompatHandler.isActive = true
end
end
end
Events.OnGameStart.Add(Compat.RunModCompatibility)
return Compat

View File

@@ -0,0 +1,480 @@
if isServer() then return end
local CommandsData = require("TOC/CommandsData")
local StaticData = require("TOC/StaticData")
----------------
--- An instance will be abbreviated with dcInst
-- https://github.com/ZioPao/The-Only-Cure/issues/187
--- 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
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()
triggerEvent("OnSetupTocData")
end
---In case of desync between the table on ModData and the table here
---@param tocData tocModDataType
function DataController:applyOnlineData(tocData)
if not tocData or not tocData.limbs then
TOC_DEBUG.print("Received invalid tocData")
return
end
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 or not self.tocData or not self.tocData.limbs then return false end
return self.tocData.limbs[limbName] and self.tocData.limbs[limbName].isCut or false
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]
for k, v in pairs(ampStatus) do
if v ~= nil then
limbData[k] = v
end
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 == nil or data.limbs == nil then
-- TOC_DEBUG.print("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 and data.limbs then
-- Let's validate that the data structure is actually valid to prevent issues
handler:applyOnlineData(data)
elseif username == getPlayer():getUsername() then
TOC_DEBUG.print("Trying to load local data or no data is available")
handler:tryLoadLocalData(key)
end
handler:setIsResetForced(false)
handler:setIsDataReady(true)
--TOC_DEBUG.print("Finished ReceiveData, triggering OnReceivedTocData")
triggerEvent("OnReceivedTocData", handler.username)
-- TODO We need an event to track if initialization has been finalized
-- 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
function DataController.DestroyInstance(username)
if DataController.instances[username] ~= nil then
DataController.instances[username] = nil
end
end
return DataController

View File

@@ -0,0 +1,415 @@
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")
local OverridenMethodsArchive = require("TOC/OverridenMethodsArchive")
-----------------
---@class LimitActionsController
local LimitActionsController = {}
--* DISABLE WEARING CERTAIN ITEMS WHEN NO LIMB
function LimitActionsController.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
function LimitActionsController.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 LimitActionsController.CheckLimbFeasibility(limbToCheck) then return isEquippable else return false end
end
-- We need to override when the player changes key binds manually to be sure that TOC changes are re-applied
local og_MainOptions_apply = MainOptions.apply
function MainOptions:apply(closeAfter)
og_MainOptions_apply(self, closeAfter)
CachedDataHandler.OverrideBothHandsFeasibility()
end
--------------------------------------------
--* 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)
--TOC_DEBUG.print("Running override for adjustMaxTime")
-- Exceptions handling, if we find that parameter then we just use the original time
local actionsQueue = ISTimedActionQueue.getTimedActionQueue(getPlayer())
if actionsQueue and actionsQueue.current and actionsQueue.current.skipTOC then
TOC_DEBUG.print("Should skip TOC stuff")
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
--TOC_DEBUG.print("Overriding adjustMaxTime")
local pl = getPlayer()
local amputatedLimbs = CachedDataHandler.GetAmputatedLimbs(pl:getUsername())
for k, _ in pairs(amputatedLimbs) do
local limbName = k
local perkAmp = Perks["Side_" .. CommonMethods.GetSide(limbName)]
local perkLevel = pl:getPerkLevel(perkAmp)
if dcInst:getIsProstEquipped(limbName) then
-- TODO We should separate this in multiple perks, since this is gonna be a generic familiarity and could make no actual sense
local perkProst = Perks["ProstFamiliarity"]
perkLevel = perkLevel + pl:getPerkLevel(perkProst)
end
local perkLevelScaled
if perkLevel ~= 0 then perkLevelScaled = perkLevel / 10 else perkLevelScaled = 0 end
TOC_DEBUG.print("Perk Level: " .. tostring(perkLevel))
TOC_DEBUG.print("OG time: " .. tostring(time))
-- Modified Time shouldn't EVER be lower compared to the og one.
local modifiedTime = time * (StaticData.LIMBS_TIME_MULTIPLIER_IND_NUM[limbName] - perkLevelScaled)
if modifiedTime >= time then
time = modifiedTime
end
--TOC_DEBUG.print("Modified time: " .. tostring(time))
end
end
--TOC_DEBUG.print("New time with amputations: " .. tostring(time))
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")
--TOC_DEBUG.print("max time: " .. tostring(self.maxTime))
local dcInst = DataController.GetInstance()
if not dcInst:getIsAnyLimbCut() or self.noExp then return end
--* LEVELING
-- First check level of perks. if already at max, skip
local amputatedLimbs = CachedDataHandler.GetAmputatedLimbs(LocalPlayerController.username)
local xp = self.maxTime / 100
-- TODO Exp should be added while doing the action, not after it's done
-- Prevent xp from being negative and decreasing perks
if xp < 0 then xp = 0 end
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)
local ampPerk = Perks["Side_" .. side]
local ampPerkLevel = LocalPlayerController.playerObj:getPerkLevel(ampPerk)
if ampPerkLevel < 10 then
--TOC_DEBUG.print("Levelling")
LocalPlayerController.playerObj:getXp():AddXP(ampPerk, xp)
end
-- Level up prosthesis perk
if dcInst:getIsProstEquipped(limbName) then
local prostPerk = Perks["ProstFamiliarity"]
local prostPerkLevel = LocalPlayerController.playerObj:getPerkLevel(prostPerk)
if prostPerkLevel < 10 then
LocalPlayerController.playerObj:getXp():AddXP(prostPerk, xp)
end
end
-- Bleeding when not cicatrized
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
--TOC_DEBUG.print("isPrimaryHandValid : " .. tostring(isPrimaryHandValid))
--TOC_DEBUG.print("isSecondaryHandValid : " .. tostring(isSecondaryHandValid))
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
function ISEquipWeaponAction:performWithAmputation()
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)
--if self.character == getPlayer() then
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() then
self:performWithAmputation()
end
--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)
-- goddamn it, zomboid devs. ogContext could be a boolean...
-- TBH, I don't really care about gamepad support, but all this method can break stuff. Let's just disable thisfor gamepad users.
if type(ogContext) == "boolean" or type(ogContext) == "string" then
return ogContext
end
-- The vanilla game doesn't count an item in the off hand as "equipped" for picking up glass. Let's fix that here
local brokenGlassOption = ogContext:getOptionFromName(getText("ContextMenu_RemoveBrokenGlass"))
if brokenGlassOption then
local playerObj = getSpecificPlayer(player)
if (CachedDataHandler.GetHandFeasibility(StaticData.SIDES_IND_STR.R) and playerObj:getPrimaryHandItem()) or
(CachedDataHandler.GetHandFeasibility(StaticData.SIDES_IND_STR.L) and playerObj:getSecondaryHandItem())
then
brokenGlassOption.notAvailable = false
brokenGlassOption.toolTip = nil -- This is active only when you can't do the action.
end
end
-- 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
---@diagnostic disable-next-line: duplicate-set-field
local og_ISWearClothing_isValid = ISWearClothing.isValid
function ISWearClothing:isValid()
return LimitActionsController.WrapClothingAction(self, og_ISWearClothing_isValid, self.item)
end
local og_ISClothingExtraAction_isValid = OverridenMethodsArchive.Save("ISClothingExtraAction_isValid", ISClothingExtraAction.isValid)
---@diagnostic disable-next-line: duplicate-set-field
function ISClothingExtraAction:isValid()
return LimitActionsController.WrapClothingAction(self, og_ISClothingExtraAction_isValid, instanceItem(self.extra))
end
--* Book exception for exp
local og_ISReadABook_perform = ISReadABook.perform
function ISReadABook:perform()
self.noExp = true
og_ISReadABook_perform(self)
end
return LimitActionsController

View File

@@ -0,0 +1,395 @@
local DataController = require("TOC/Controllers/DataController")
local CommonMethods = require("TOC/CommonMethods")
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
local CommandsData = require("TOC/CommandsData")
local StaticData = require("TOC/StaticData")
require("TOC/Events")
local TOC = require("TOC/Registries")
-----------
-- 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()
-- Since isForced is used to reset an existing player data, we're gonna clean their ISHealthPanel table too
if isForced then
sendClientCommand(CommandsData.modules.TOC_ITEMS, "DeleteAllOldAmputationItems", {playerNum = playerObj:getOnlineID()})
CachedDataHandler.Setup(username)
end
-- Set a bool to use an overriding GetDamagedParts
SetHealthPanelTOC()
end
---Handles the traits
function LocalPlayerController.ManageTraits()
-- Local player
local playerObj = getPlayer()
local AmputationHandler = require("TOC/Handlers/AmputationHandler")
for k, v in pairs(StaticData.TRAITS_BP) do
if playerObj:hasTrait(TOC.traits[k]) then
-- Once we find one, we should be done since they're exclusive
TOC_DEBUG.print("Player has amputation trait " .. k .. ", executing it")
local tempHandler = AmputationHandler:new(v, playerObj)
tempHandler:execute(false) -- No damage
tempHandler:close()
-- The wound should be already cicatrized
local dcInst = DataController.GetInstance()
LocalPlayerController.HandleSetCicatrization(DataController.GetInstance(), playerObj, v)
dcInst:apply()
return
end
end
end
-- We need to manage traits when we're done setupping everything
-- It shouldn't be done every single time we initialize the player, fetching data, etc.
Events.OnSetupTocData.Add(LocalPlayerController.ManageTraits)
----------------------------------------------------------
--* 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:SetInfected(false)
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, limbName, dcInst)
-- FIX Different in B42.13, to be set with stats?
if bodyDamage:isInfected() == false then return end
bodyDamage:setInfected(false)
bodyDamage:setInfectionMortalityDuration(-1)
bodyDamage:setInfectionTime(-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]]
-- we need to check if the wound is already bleeding before doing anything else to prevent issues with bandages
local bp = character:getBodyDamage():getBodyPart(adjacentBodyPartType)
bp:setBleedingTime(20) -- TODO Should depend on cicatrization instead of a fixed time
-- ADD Could break bandages if bleeding is too much?
--character:getBodyDamage():getBodyPart(adjacentBodyPartType):setBleeding(true)
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
-- Disable lock before doing anything else
LocalPlayerController.hasBeenDamaged = false
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, 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 not dcInst:getIsIgnoredPartInfected() then
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
end
if modDataNeedsUpdate then
dcInst:apply()
end
-- Disable the lock
LocalPlayerController.hasBeenDamaged = false
end
---Setup HandleDamage, triggered by OnPlayerGetDamage. To prevent a spam caused by this awful event, we use a bool lock
---@param character IsoPlayer|IsoGameCharacter
---@param damageType string
---@param damageAmount number
function LocalPlayerController.OnGetDamage(character, damageType, damageAmount)
if LocalPlayerController.hasBeenDamaged == false then
-- Start checks
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("Setting cicatrization to " .. tostring(limbName))
dcInst:setIsCicatrized(limbName, true)
dcInst:setCicatrizationTime(limbName, 0)
-- -- Set visuals for the amputation
sendClientCommand(CommandsData.modules.TOC_ITEMS, "OverrideAmputationItemVisuals",
{playerNum = playerObj:getOnlineID(), limbName = limbName, isCicatrized = 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,115 @@
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
---@diagnostic disable-next-line: duplicate-set-field
function ISClothingExtraAction:perform()
TourniquetController.WrapClothingAction(self, og_ISClothingExtraAction_perform)
end
local og_ISWearClothing_isValid = ISWearClothing.isValid
---@diagnostic disable-next-line: duplicate-set-field
function ISWearClothing:isValid()
return TourniquetController.WrapClothingAction(self, og_ISWearClothing_isValid)
end
local og_ISUnequipAction_perform = ISUnequipAction.perform
---@diagnostic disable-next-line: duplicate-set-field
function ISUnequipAction:perform()
return TourniquetController.WrapClothingAction(self, og_ISUnequipAction_perform)
end
return TourniquetController

View File

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

View File

@@ -0,0 +1,224 @@
-- TODO Move this to server side for 42.13
local DataController = require("TOC/Controllers/DataController")
local CommandsData = require("TOC/CommandsData")
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 bd = patientPl:getBodyDamage()
-- we need the adjacent one, not the actual one
local adjacentLimb = StaticData.LIMBS_ADJACENT_IND_STR[limbName]
local bodyPart = bd:getBodyPart(BodyPartType[adjacentLimb])
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 bd = patientPl:getBodyDamage()
-- we need the adjacent one, not the actual one
local adjacentLimb = StaticData.LIMBS_ADJACENT_IND_STR[limbName]
local bodyPart = bd:getBodyPart(BodyPartType[adjacentLimb])
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:set(CharacterStat.ENDURANCE, surgeonFactor)
patientStats:set(CharacterStat.STRESS, baseDamage - surgeonFactor)
end
--- Execute the amputation. This method doesn't check if the upper limb has been amputated or not, so if
--- somehow the method gets triggered and we're trying to cut off a part that doesn't really exist anymore,
--- it will still be executed. This is by design, additional checks must be made BEFORE running the AmputationHandler
---@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
-- FIX This can be done in a single step instead of this crap
sendClientCommand(CommandsData.modules.TOC_ITEMS, "DeleteOldAmputationItem",
{playerNum = self.patientPl:getOnlineID(), limbName = self.limbName})
sendClientCommand(CommandsData.modules.TOC_ITEMS, "SpawnAmputationItem",
{playerNum = self.patientPl:getOnlineID(), limbName = 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)
-- TODO Test this again for 42.13
-- If the part was actually infected, heal the player, if they were in time (infectionLevel < 20)
local infectionLevel = self.patientPl:getStats():get(CharacterStat.ZOMBIE_INFECTION)
if infectionLevel < 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
---Delete the instance
function AmputationHandler:close()
AmputationHandler.instance = nil
end
return AmputationHandler

View File

@@ -0,0 +1,177 @@
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)
if getPlayer():getUsername() == username then
CachedDataHandler.OverrideBothHandsFeasibility()
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)
-- TODO if we re run this too early, it might break everything after a forced re-init
CachedDataHandler.handFeasibility[side] = not dcInst:getIsCut(limbName) or dcInst:getIsProstEquipped(limbName)
TOC_DEBUG.print("Calculated hand feasibility: " .. tostring(side))
end
---@param side string Either "L" or "R"
---@return boolean
function CachedDataHandler.GetHandFeasibility(side)
-- FIX horrendous workaround, but with a forced init we run the caching too early and it breaks this, setting it to nil.
if CachedDataHandler.handFeasibility[side] == nil then
CachedDataHandler.OverrideBothHandsFeasibility()
end
return CachedDataHandler.handFeasibility[side]
end
function CachedDataHandler.OverrideBothHandsFeasibility()
CachedDataHandler.CalculateHandFeasibility("Hand_L")
CachedDataHandler.CalculateHandFeasibility("Hand_R")
local interactStr = "Interact"
if CachedDataHandler.interactKey == nil or CachedDataHandler.interactKey == 0 then
CachedDataHandler.interactKey = getCore():getKey(interactStr)
end
if not CachedDataHandler.GetBothHandsFeasibility() then
TOC_DEBUG.print("Disabling interact key")
TOC_DEBUG.print("Cached current key for interact: " .. tostring(CachedDataHandler.interactKey))
if StaticData.COMPAT_42 then
getCore():addKeyBinding(interactStr, Keyboard.KEY_NONE, 0, false, false, false)
else
getCore():addKeyBinding(interactStr, Keyboard.KEY_NONE)
end
else
--TOC_DEBUG.print("Re-enabling interact key")
--TOC_DEBUG.print("Cached current key for interact: " .. tostring(CachedDataHandler.interactKey))
if StaticData.COMPAT_42 then
getCore():addKeyBinding(interactStr, CachedDataHandler.interactKey, 0, false, false, false)
else
getCore():addKeyBinding(interactStr, CachedDataHandler.interactKey)
end
end
end
function CachedDataHandler.GetBothHandsFeasibility()
return CachedDataHandler.handFeasibility["L"] or CachedDataHandler.handFeasibility["R"]
end
return CachedDataHandler

View File

@@ -0,0 +1,189 @@
local CommonMethods = require("TOC/CommonMethods")
local StaticData = require("TOC/StaticData")
local DataController = require("TOC/Controllers/DataController")
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
local OverridenMethodsArchive = require("TOC/OverridenMethodsArchive")
-------------------------
---@class ProsthesisHandler
local ProsthesisHandler = {}
local bodylocArmProstBaseline = "toc:toc_armprost"
--local bodyLocLegProst = "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
if item == nil then
TOC_DEBUG.print("Not prost")
return false
end
return item:getBodyLocation():toString():contains(bodylocArmProstBaseline)
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:toString():contains(bodylocArmProstBaseline) then
position = "Top_"
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.OverrideBothHandsFeasibility()
return true
end
function ProsthesisHandler.Validate(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
-------------------------
--* Overrides *--
local og_ISWearClothing_isValid = ISWearClothing.isValid
---@diagnostic disable-next-line: duplicate-set-field
function ISWearClothing:isValid()
local isEquippable = og_ISWearClothing_isValid(self)
return ProsthesisHandler.Validate(self.item, isEquippable)
end
local og_ISWearClothing_perform = ISWearClothing.perform
---@diagnostic disable-next-line: duplicate-set-field
function ISWearClothing:perform()
ProsthesisHandler.SearchAndSetupProsthesis(self.item, true)
og_ISWearClothing_perform(self)
end
local og_ISClothingExtraAction_isValid = OverridenMethodsArchive.Save("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
-- B42 Compatibility to add
local testItem = InventoryItemFactory.CreateItem(self.extra)
return ProsthesisHandler.Validate(testItem, isEquippable)
end
local og_ISClothingExtraAction_perform = OverridenMethodsArchive.Save("ISClothingExtraAction_perform", ISClothingExtraAction.perform)
---@diagnostic disable-next-line: duplicate-set-field
function ISClothingExtraAction:perform()
-- B42 Compatibility to add
local extraItem = InventoryItemFactory.CreateItem(self.extra)
ProsthesisHandler.SearchAndSetupProsthesis(extraItem, true)
og_ISClothingExtraAction_perform(self)
end
local og_ISUnequipAction_perform = ISUnequipAction.perform
---@diagnostic disable-next-line: duplicate-set-field
function ISUnequipAction:perform()
--[[
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 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]
if hal then
-- This could break if amputated limbs aren't cached for some reason
triggerEvent("OnProsthesisUnequipped", hal)
end
end
end
end
return ProsthesisHandler

View File

@@ -0,0 +1,71 @@
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.3"
}
function Main.Start()
TOC_DEBUG.print("Starting The Only Cure version " .. tostring(Main._version))
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 it from clogging ModData up
---@param player IsoPlayer
function Main.WipeData(player)
local username = player:getUsername()
TOC_DEBUG.print("Wiping data after death: " .. username)
local key = CommandsData.GetKey(username)
--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
-- Let's wipe the instance too just to be sure
-- TODO This can break things I guess
--local DataController = require("TOC/Controllers/DataController")
--DataController.DestroyInstance(username)
end
--* Events *--
Events.OnGameStart.Add(Main.Start)
Events.OnCreatePlayer.Add(Main.InitializePlayer)
Events.OnPlayerDeath.Add(Main.WipeData)

View File

@@ -0,0 +1,206 @@
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()
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.BleedTest()
local pl = getPlayer()
--pl:getBodyDamage():getBodyPart(BodyPartType.ForeArm_R):setBleeding(true)
pl:getBodyDamage():getBodyPart(BodyPartType.ForeArm_R):setBleedingTime(20)
end
function Tests.Kill()
getPlayer():Kill(getPlayer())
end
return Tests
end)
TestFramework.registerTestModule("Various", "Visuals", function()
local Tests = {}
function Tests.AddBloodLeftForearm()
local playerObj = getPlayer()
local limbName = "ForeArm_L"
local fullType = StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limbName
local item = playerObj:getInventory():FindAndReturn(fullType)
if instanceof(item, "Clothing") then
---@cast item Clothing
print("Found limb to add blood onto")
item:setBloodLevel(100)
local coveredParts = BloodClothingType.getCoveredParts(item:getBloodClothingType())
if coveredParts then
for j=0,coveredParts:size()-1 do
item:setBlood(coveredParts:get(j), 100)
item:setDirt(coveredParts:get(j), 100)
end
end
end
playerObj:resetModelNextFrame()
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,126 @@
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
---@field doctorLevel number
---@field bandagedPlayerX number
---@field bandagedPlayerY number
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 -- B42 Deprecated
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,163 @@
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 = ISBaseTimedAction.new(self, surgeon)
-- 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 = o:getDuration()
return o
end
function CutLimbAction:getDuration()
if self.character:isTimedActionInstant() then
return 1
else
local baseTime = 1000
local perkLevel = self.character:getPerkLevel(Perks.Doctor)
return baseTime - (perkLevel * 50)
end
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:serverStart()
-- emulateAnimEvent(self.netAction, 200, "")
-- end
-- function CutLimbAction:animEvent(event, parameter)
-- 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()
self:stopSound()
ISBaseTimedAction.perform(self)
end
function CutLimbAction:complete()
-- TODO AmputationHandler runs client side, by doing this this would run on the server. AM I missing something?
local handler = AmputationHandler:new(self.limbName, self.character)
handler:execute(true)
end
-- function CutLimbAction:perform()
-- -- Stop the sound
-- 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
-- end
return CutLimbAction

View File

@@ -0,0 +1,139 @@
-- TODO This section must be overhauled
local DataController = require("TOC/Controllers/DataController")
local StaticData = require("TOC/StaticData")
---@diagnostic disable: duplicate-set-field
-- Bunch of actions shouldn't be modified by the adjusted time
-----------------------------------------------
---* Some actions have specific maxTime calculations and we must account for that
---ISAttachItemHotbar
---ISDetachItemHotbar
---ISEquipWeaponAction
---ISUnequipAction
--- We're forced to re-run this crap to fix it
---@param action ISBaseTimedAction
local function OverrideAction(action, maxTime)
-- TODO Add forced debuff instead of just relying on the vanilla values?
action.skipTOC = true
action.maxTime = maxTime
action.animSpeed = 1.0
end
local og_ISAttachItemHotbar_new = ISAttachItemHotbar.new
function ISAttachItemHotbar:new(character, item, slot, slotIndex, slotDef)
local action = og_ISAttachItemHotbar_new(self, character, item, slot, slotIndex, slotDef)
OverrideAction(action, 30) -- Default time for this action
return action
end
local og_ISDetachItemHotbar_new = ISDetachItemHotbar.new
function ISDetachItemHotbar:new(character, item)
local action = og_ISDetachItemHotbar_new(self, character, item)
OverrideAction(action, 25) -- Default time for this action
return action
end
local og_ISEquipWeaponAction_new = ISEquipWeaponAction.new
function ISEquipWeaponAction:new(character, item, time, primary, twoHands)
local action = og_ISEquipWeaponAction_new(self, character, item, time, primary, twoHands)
TOC_DEBUG.print("Override ISEquipWeaponAction New")
-- check if right arm is cut off or not. if it is, penality shall apply
-- if we got here, the action is valid, so we know that we have a prosthesis.
local dcInst = DataController.GetInstance()
-- Brutal Handwork should be considered. Use the twohands thing
if not (dcInst:getIsAnyLimbCut() and twoHands) then
OverrideAction(action, time)
end
return action
end
local og_ISUnequipAction_new = ISUnequipAction.new
function ISUnequipAction:new(character, item, time)
local action = og_ISUnequipAction_new(self, character, item, time)
---@cast item InventoryItem
-- TODO Consider other cases where unequipping something should skip TOC.
if instanceof(item, 'HandWeapon') then
OverrideAction(action, time)
end
return action
end
------------------------------------------------------
--- Normal cases
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
if StaticData.COMPAT_42 == false then
-- TODO confirm that this doesn't exist anymore in B42
-- B42 nenen
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
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,80 @@
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
local StaticData = require("TOC/StaticData")
-- Since amputations are actually clothing items, we need to override ISWashYourself to account for that
-- TODO Clean this up
local og_ISWashYourself_perform = ISWashYourself.perform
function ISWashYourself:perform()
TOC_DEBUG.print("ISWashYourself override")
---@type IsoPlayer
local pl = self.character
local plInv = pl:getInventory()
-- Search for amputations and clean them here
local amputatedLimbs = CachedDataHandler.GetAmputatedLimbs(pl:getUsername())
for limbName, _ in pairs(amputatedLimbs) do
TOC_DEBUG.print("Checking if " .. limbName .. " is in inventory and washing it")
-- get clothing item
local foundItem = plInv:FindAndReturn(StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limbName)
if foundItem and instanceof(foundItem, "Clothing") then
TOC_DEBUG.print("Washing " .. limbName)
---@cast foundItem Clothing
foundItem:setWetness(100)
foundItem:setBloodLevel(0)
foundItem:setDirtyness(0) -- TODO Integrate with other dirtyness
local coveredParts = BloodClothingType.getCoveredParts(foundItem:getBloodClothingType())
for j=0, coveredParts:size() - 1 do
foundItem:setBlood(coveredParts:get(j), 0)
foundItem:setDirt(coveredParts:get(j), 0)
end
end
end
og_ISWashYourself_perform(self)
end
local og_ISWashYourself_GetRequiredWater = ISWashYourself.GetRequiredWater
---@param character IsoPlayer
---@return integer
function ISWashYourself.GetRequiredWater(character)
local units = og_ISWashYourself_GetRequiredWater(character)
local amputatedLimbs = CachedDataHandler.GetAmputatedLimbs(character:getUsername())
local plInv = character:getInventory()
for limbName, _ in pairs(amputatedLimbs) do
TOC_DEBUG.print("Checking if " .. limbName .. " is in inventory and washing it")
-- get clothing item
local item = plInv:FindAndReturn(StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limbName)
if item and instanceof(item, "Clothing") then
local coveredParts = BloodClothingType.getCoveredParts(item:getBloodClothingType())
if coveredParts then
for i=1,coveredParts:size() do
local part = coveredParts:get(i-1)
if item:getBlood(part) > 0 then
units = units + 1
end
end
end
end
end
return units
end

View File

@@ -0,0 +1,116 @@
---@class ConfirmationPanel : ISPanel
local ConfirmationPanel = ISPanel:derive("ConfirmationPanel")
---Starts a new confirmation panel
---@param x number
---@param y number
---@param width number
---@param height number
---@param alertText string
---@param onConfirmFunc function
---@return ConfirmationPanel
function ConfirmationPanel:new(x, y, width, height, alertText, parentPanel, onConfirmFunc)
local o = ISPanel:new(x, y, width, height)
setmetatable(o, self)
self.__index = self
o:initialise()
o.alertText = alertText
o.onConfirmFunc = onConfirmFunc
o.parentPanel = parentPanel
ConfirmationPanel.instance = o
---@cast o ConfirmationPanel
return o
end
function ConfirmationPanel:createChildren()
ISPanel.createChildren(self)
self.borderColor = { r = 1, g = 0, b = 0, a = 1 }
self.textPanel = ISRichTextPanel:new(0, 0, self.width, self.height)
self.textPanel:initialise()
self:addChild(self.textPanel)
self.textPanel.defaultFont = UIFont.Medium
self.textPanel.anchorTop = true
self.textPanel.anchorLeft = false
self.textPanel.anchorBottom = true
self.textPanel.anchorRight = false
self.textPanel.marginLeft = 0
self.textPanel.marginTop = 10
self.textPanel.marginRight = 0
self.textPanel.marginBottom = 0
self.textPanel.autosetheight = false
self.textPanel.background = false
self.textPanel:setText(self.alertText)
self.textPanel:paginate()
local yPadding = 10
local xPadding = self:getWidth() / 4
local btnWidth = 100
local btnHeight = 25
local yButton = self:getHeight() - yPadding - btnHeight
self.btnYes = ISButton:new(xPadding, yButton, btnWidth, btnHeight, getText("IGUI_Yes"), self, self.onClick)
self.btnYes.internal = "YES"
self.btnYes:initialise()
self.btnYes.borderColor = { r = 1, g = 0, b = 0, a = 1 }
self.btnYes:setEnable(true)
self:addChild(self.btnYes)
self.btnNo = ISButton:new(self:getWidth() - xPadding - btnWidth, yButton, btnWidth, btnHeight, getText("IGUI_No"), self,
self.onClick)
self.btnNo.internal = "NO"
self.btnNo:initialise()
self.btnNo:setEnable(true)
self:addChild(self.btnNo)
end
function ConfirmationPanel:onClick(btn)
if btn.internal == 'YES' then
self.onConfirmFunc(self.parentPanel)
self:close()
elseif btn.internal == 'NO' then
self:close()
end
end
-------------------------
---@param alertText string
---@param x any
---@param y any
---@param parentPanel any
---@param onConfirmFunc any
---@return ConfirmationPanel
function ConfirmationPanel.Open(alertText, x, y, parentPanel, onConfirmFunc)
local width = 500
local height = 120
local screenWidth = getCore():getScreenWidth()
local screenHeight = getCore():getScreenHeight()
-- Check for oversize
if x+width > screenWidth then
x = screenWidth - width
end
if y+height > screenHeight then
y = screenHeight - height
end
local panel = ConfirmationPanel:new(x, y, width, height, alertText, parentPanel, onConfirmFunc)
panel:initialise()
panel:addToUIManager()
panel:bringToTop()
return panel
end
function ConfirmationPanel.Close()
ConfirmationPanel.instance:close()
end
return ConfirmationPanel

View File

@@ -0,0 +1,273 @@
local StaticData = require("TOC/StaticData")
local DataController = require("TOC/Controllers/DataController")
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
local Compat = require("TOC/Compat")
local CutLimbInteractionHandler = require("TOC/UI/Interactions/CutLimbInteractionHandler")
local WoundCleaningInteractionHandler = require("TOC/UI/Interactions/WoundCleaningInteractionHandler")
------------------------
local isReady = false
local xMod, yMod
if StaticData.COMPAT_42 then
-- B42 For some reason (I didn't investigate), when applying stuff to the health panel there is an un-accounted shift in B42.
xMod = 5
yMod = 13
else
xMod = 0
yMod = 0
end
function SetHealthPanelTOC()
-- depending on compatibility
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
-- B42, for some reason the positioning of the texture changed. Realigned it manually with those fixed values
self:drawTexture(texture, self.healthPanel.x - xMod, self.healthPanel.y - yMod, 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 - xMod, self.healthPanel.y - yMod, 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
-- 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()
-- check for imeds or if TOC is ready to display its stuff on the health panel
if isReady == false or Compat.handlers['iMeds'].isActive or Compat.handlers['iMedsFixed'].isActive then
return og_ISHealthPanel_getDamagedParts(self)
elseif 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
end
end

View File

@@ -0,0 +1,287 @@
local BaseHandler = require("TOC/UI/Interactions/HealthPanelBaseHandler")
local StaticData = require("TOC/StaticData")
local DataController = require("TOC/Controllers/DataController")
local ConfirmationPanel = require("TOC/UI/ConfirmationPanel")
local CutLimbAction = require("TOC/TimedActions/CutLimbAction")
---------------------
--* 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 == StaticData.SAWS_TYPES_IND_STR.saw
or itemType == 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")
if sutureNeedle 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
local textConfirmAmp = getText("IGUI_Confirmation_Amputate")
local textAmp = getText("ContextMenu_Amputate")
local textAmpBandage = getText("ContextMenu_Amputate_Bandage")
local textAmpStitch = getText("ContextMenu_Amputate_Stitch")
local textAmpStitchBandage = getText("ContextMenu_Amputate_Stitch_Bandage")
---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)
local x = (getCore():getScreenWidth() - 500) / 2
local y = getCore():getScreenHeight() / 2
ConfirmationPanel.Open(textConfirmAmp, x, y, nil, function()
-- 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)
end
---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 x number
---@param y number
---@param type any
function CutLimbInteractionHandler:openConfirmation(x, y, type)
ConfirmationPanel.Open(textConfirmAmp, x, y, nil, function()
self.onMenuOptionSelected(self, type)
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")
local x = (getCore():getScreenWidth() - 500) / 2
local y = getCore():getScreenHeight() / 2
for i=1, #types do
context:addOption(getText("ContextMenu_Amputate"), self, self.openConfirmation, x, y, 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,141 @@
-- 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 = {}
if containers ~= nil then
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
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)
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)
-- FIX: 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

View File

@@ -0,0 +1,84 @@
local BaseHandler = require("TOC/UI/Interactions/HealthPanelBaseHandler")
local CommonMethods = require("TOC/CommonMethods")
local DataController = require("TOC/Controllers/DataController")
local CleanWoundAction = require("TOC/TimedActions/CleanWoundAction")
-------------------------
---@class WoundCleaningInteractionHandler : BaseHandler
---@field username string
---@field limbName string
local WoundCleaningInteractionHandler = BaseHandler:derive("WoundCleaningInteractionHandler")
---@param panel any
---@param bodyPart any
---@param username string
---@return table
function WoundCleaningInteractionHandler:new(panel, bodyPart, username)
local o = BaseHandler.new(self, panel, bodyPart)
o.items.ITEMS = {}
o.username = username
o.limbName = CommonMethods.GetLimbNameFromBodyPart(bodyPart)
return o
end
function WoundCleaningInteractionHandler:checkItem(item)
-- Disinfected rag or bandage
--TOC_DEBUG.print("WoundCleaningInteractionHandler checkItem")
if item:getBandagePower() >=2 and item:isAlcoholic() then
--TOC_DEBUG.print("Adding " .. item:getName())
self:addItem(self.items.ITEMS, item)
end
end
function WoundCleaningInteractionHandler:addToMenu(context)
--TOC_DEBUG.print("WoundCleaningInteraction addToMenu")
local types = self:getAllItemTypes(self.items.ITEMS)
if #types > 0 and self:isValid() then
TOC_DEBUG.print("WoundCleaningInteraction inside addToMenu")
local option = context:addOption(getText("ContextMenu_CleanWound"), nil)
local subMenu = context:getNew(context)
context:addSubMenu(option, subMenu)
for i=1, #types do
local item = self:getItemOfType(self.items.ITEMS, types[i])
subMenu:addOption(item:getName(), self, self.onMenuOptionSelected, item:getFullType())
TOC_DEBUG.print(item:getName())
end
end
end
function WoundCleaningInteractionHandler:dropItems(items)
local types = self:getAllItemTypes(items)
if #self.items.ITEMS > 0 and #types == 1 and self:isActionValid() then
self:onMenuOptionSelected(types[1])
return true
end
return false
end
function WoundCleaningInteractionHandler:isValid()
self:checkItems()
return self:isActionValid()
end
function WoundCleaningInteractionHandler:isActionValid()
if self.limbName == nil then return false end
local dcInst = DataController.GetInstance(self.username)
local check = dcInst:getIsCut(self.limbName) and not dcInst:getIsCicatrized(self.limbName) and dcInst:getWoundDirtyness(self.limbName) > 0
--TOC_DEBUG.print("WoundCleaningInteraction isValid: " .. tostring(check))
return check
end
function WoundCleaningInteractionHandler:perform(previousAction, itemType)
local item = self:getItemOfType(self.items.ITEMS, itemType)
previousAction = self:toPlayerInventory(item, previousAction)
local action = CleanWoundAction:new(self:getDoctor(), self:getPatient(), item, self.bodyPart)
ISTimedActionQueue.addAfter(previousAction, action)
end
return WoundCleaningInteractionHandler

View File

@@ -0,0 +1,99 @@
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
local DataController = require("TOC/Controllers/DataController")
local CauterizeAction = require("TOC/TimedActions/CauterizeAction")
---------------
---@param tooltip ISToolTip
---@param desc string
local function AppendToDescription(tooltip, desc)
if tooltip.description == "" then
desc = string.upper(string.sub(desc, 1, 1)) .. string.sub(desc, 2)
tooltip.description = desc
else
desc = string.lower(string.sub(desc, 1, 1)) .. string.sub(desc, 2)
tooltip.description = tooltip.description .. getText("Tooltip_Surgery_And") .. desc
end
end
---@param playerNum number
---@param context ISContextMenu
---@param worldObjects any
---@param test any
local function AddStoveContextMenu(playerNum, context, worldObjects, test)
if test then return true end
local pl = getSpecificPlayer(playerNum)
local dcInst = DataController.GetInstance()
if not dcInst:getIsAnyLimbCut() then return end
local amputatedLimbs = CachedDataHandler.GetAmputatedLimbs(pl:getUsername())
---@type IsoStove?
local stoveObj = nil
for _, obj in pairs(worldObjects) do
if instanceof(obj, "IsoStove") then
stoveObj = obj
break
end
end
if stoveObj == nil then return end
local tempTooltip = ISToolTip:new()
tempTooltip:initialise()
tempTooltip.description = ""
tempTooltip:setVisible(false)
local addMainOption = false
local subMenu
for k, _ in pairs(amputatedLimbs) do
-- We need to let the player cauterize ONLY the visible one!
---@type string
local limbName = k
if dcInst:getIsVisible(limbName) and not dcInst:getIsCicatrized(limbName) then
if addMainOption == false then
-- Adds the cauterize option ONLY when it's needed
local optionMain = context:addOption(getText("ContextMenu_Cauterize"), nil)
subMenu = context:getNew(context)
context:addSubMenu(optionMain, subMenu)
addMainOption = true
end
local option = subMenu:addOption(getText("ContextMenu_Limb_" .. limbName), nil, function()
local adjacent = AdjacentFreeTileFinder.Find(stoveObj:getSquare(), pl)
ISTimedActionQueue.add(ISWalkToTimedAction:new(pl, adjacent))
ISTimedActionQueue.add(CauterizeAction:new(pl, limbName, stoveObj))
end)
-- Notifications, in case the player can't do the action
local isPlayerCourageous = pl:hasTrait(CharacterTrait.BRAVE) or pl:hasTrait(CharacterTrait.DESENSITIZED) or pl:getPerkLevel(Perks.Strength) > 5
local isTempHighEnough = stoveObj:getCurrentTemperature()-1 >= 2.50
local isLimbFree = not dcInst:getIsProstEquipped(limbName)
TOC_DEBUG.print(stoveObj:getCurrentTemperature())
option.notAvailable = not(isPlayerCourageous and isTempHighEnough and isLimbFree)
if not isTempHighEnough then
AppendToDescription(tempTooltip, getText("Tooltip_Surgery_TempTooLow"))
end
if not isPlayerCourageous then
AppendToDescription(tempTooltip, getText("Tooltip_Surgery_Coward"))
end
if not isLimbFree then
AppendToDescription(tempTooltip, getText("Tooltip_Surgery_LimbNotFree"))
end
if option.notAvailable then
tempTooltip:setName(getText("Tooltip_Surgery_CantCauterize"))
option.toolTip = tempTooltip
end
end
end
end
Events.OnFillWorldObjectContextMenu.Add(AddStoveContextMenu)

View File

@@ -0,0 +1,157 @@
require "lua_timers"
local ItemsController = require("TOC/Controllers/ItemsController")
local StaticData = require("TOC/StaticData")
local CommandsData = require("TOC/CommandsData")
-------------------------------
---@param zombie IsoZombie|IsoGameCharacter|IsoMovingObject|IsoObject
---@return integer trueID
local function GetZombieID(zombie)
-- Big love to Chuck and Sir Doggy Jvla for this code
---@diagnostic disable-next-line: param-type-mismatch
local pID = zombie:getPersistentOutfitID()
local bits = string.split(string.reverse(Long.toUnsignedString(pID, 2)), "")
while #bits < 16 do bits[#bits + 1] = 0 end
-- trueID
bits[16] = 0
local trueID = Long.parseUnsignedLong(string.reverse(table.concat(bits, "")), 2)
return trueID
end
-------------------------------
---@param item InventoryItem
local function PredicateAmputationItems(item)
return item:getType():contains("Amputation_")
end
---@param item InventoryItem
local function PredicateAmputationItemLeft(item)
return item:getType():contains("Amputation_") and item:getType():contains("_L")
end
---@param item InventoryItem
local function PredicateAmputationItemRight(item)
return item:getType():contains("Amputation_") and item:getType():contains("_R")
end
---@param zombie IsoZombie
local function SpawnAmputation(zombie, side)
local index = ZombRand(1, #StaticData.PARTS_STR)
local limb = StaticData.PARTS_STR[index] .. "_" .. side
local amputationFullType = StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limb
ItemsController.Zombie.SpawnAmputationItem(zombie, amputationFullType)
-- Add reference and transmit it to server
local pID = GetZombieID(zombie)
local zombieKey = CommandsData.GetZombieKey()
local zombiesMD = ModData.getOrCreate(zombieKey)
if zombiesMD[pID] == nil then zombiesMD[pID] = {} end
zombiesMD[pID][side] = amputationFullType
ModData.add(zombieKey, zombiesMD)
ModData.transmit(zombieKey)
end
-------------------------------
local bloodAmount = 10
---@param player IsoGameCharacter
---@param zombie IsoZombie
---@param handWeapon HandWeapon
local function HandleZombiesAmputations(player, zombie, handWeapon, damage)
if not SandboxVars.TOC.EnableZombieAmputations then return end
if not instanceof(zombie, "IsoZombie") or not instanceof(player, "IsoPlayer") then return end
if player ~= getPlayer() then return end
-- Check type of weapon. No hands, only knifes or such
local weaponCategories = handWeapon:getScriptItem():getCategories()
if not (weaponCategories:contains("Axe") or weaponCategories:contains("LongBlade")) then return end
local isCrit = player:isCriticalHit()
local randomChance = ZombRand(0, 100) > (100 - SandboxVars.TOC.ZombieAmputationDamageChance)
if (damage > SandboxVars.TOC.ZombieAmputationDamageThreshold and randomChance) or isCrit then
TOC_DEBUG.print("Amputating zombie limbs - damage: " .. tostring(damage))
local zombieInv = zombie:getInventory()
-- Check left or right
local randSide = ZombRand(2) -- Random side
local preferredSide = randSide == 0 and "L" or "R"
local alternateSide = preferredSide == "L" and "R" or "L"
local predicatePreferred = preferredSide == "L" and PredicateAmputationItemLeft or PredicateAmputationItemRight
local predicateAlternate = alternateSide == "L" and PredicateAmputationItemLeft or PredicateAmputationItemRight
if not zombieInv:containsEval(predicatePreferred) then
SpawnAmputation(zombie, preferredSide)
elseif not zombieInv:containsEval(predicateAlternate) then
SpawnAmputation(zombie, alternateSide)
end
TOC_DEBUG.print("Amputating zombie limbs - damage: " .. tostring(damage) .. ", preferred limb side: " .. preferredSide)
-- add blood splat every couple of seconds for a while
addBloodSplat(getCell():getGridSquare(zombie:getX(), zombie:getY(), zombie:getZ()), bloodAmount)
local timerName = tostring(GetZombieID(zombie)) .. "_timer"
timer:Create(timerName, 1, 10, function()
addBloodSplat(getCell():getGridSquare(zombie:getX(), zombie:getY(), zombie:getZ()), bloodAmount)
end)
end
end
Events.OnWeaponHitCharacter.Add(HandleZombiesAmputations)
-----------------------------
local localOnlyZombiesMD
local function SetupZombiesModData()
if not SandboxVars.TOC.EnableZombieAmputations then return end
local zombieKey = CommandsData.GetZombieKey()
localOnlyZombiesMD = ModData.getOrCreate(zombieKey)
end
Events.OnInitGlobalModData.Add(SetupZombiesModData)
---@param zombie IsoZombie
local function ReapplyAmputation(zombie)
if not SandboxVars.TOC.EnableZombieAmputations then return end
local pID = GetZombieID(zombie)
if localOnlyZombiesMD[pID] ~= nil then
-- check if zombie has amputation
local zombiesAmpData = localOnlyZombiesMD[pID]
local zombieInv = zombie:getInventory()
local foundItem = zombieInv:containsEvalRecurse(PredicateAmputationItems)
if foundItem then
return
else
local leftAmp = zombiesAmpData['L']
if leftAmp then
ItemsController.Zombie.SpawnAmputationItem(zombie, leftAmp)
end
local rightAmp = zombiesAmpData['R']
if rightAmp then
ItemsController.Zombie.SpawnAmputationItem(zombie, rightAmp)
end
-- Removes reference, local only
localOnlyZombiesMD[pID] = nil
end
end
end
Events.OnZombieUpdate.Add(ReapplyAmputation)

View File

@@ -0,0 +1,186 @@
-- TODO Should be server side in 42.13
local StaticData = require("TOC/StaticData")
local CommonMethods = require("TOC/CommonMethods")
local CommandsData = require("TOC/CommandsData")
---------------------------
--- 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%d")) -- it must always be at least 1
TOC_DEBUG.print("Texture string: " .. tostring(textureString))
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
sendRemoveItemFromContainer(playerObj:getInventory(), clothingItem)
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)
-- TODO Fix visual bug
-- 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]
-- TODO Won't work with dedicated clothingItems for multi amps
local clothItemName = StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limbName
local clothItem = playerObj:getInventory():FindAndReturn(clothItemName)
---@cast clothItem InventoryItem
ItemsController.Player.RemoveClothingItem(playerObj, clothItem)
end
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 itemName = StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limbName
local clothingItem = playerObj:getInventory():AddItem(itemName)
local texId = ItemsController.Player.GetAmputationTexturesIndex(playerObj, false)
---@cast clothingItem InventoryItem
clothingItem:getVisual():setTextureChoice(texId) -- it counts from 0, so we have to subtract 1
sendAddItemToContainer(playerObj:getInventory(), clothingItem)
sendServerCommand(playerObj, CommandsData.modules.TOC_RELAY, CommandsData.client.Relay.ReceiveWearAmputation, {itemName = itemName, texId = texId})
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)
-- TODO Remove objects in that part of the body to prevent items floating in mid air
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,32 @@
local CommandsData = require("TOC/CommandsData")
local ServerDataHandler = require("TOC/ServerDataHandler")
----------------------------
local DebugCommands = {}
---comment
---@param playerObj IsoPlayer
---@param args table
function DebugCommands.PrintAllTocData(playerObj, args)
TOC_DEBUG.printTable(ServerDataHandler.modData)
end
---Print ALL TOC data
---@param playerObj IsoPlayer
---@param args printTocDataParams
function DebugCommands.PrintTocData(playerObj, args)
local key = CommandsData.GetKey(args.username)
local tocData = ServerDataHandler.GetTable(key)
TOC_DEBUG.printTable(tocData)
end
--------------------
local function OnClientDebugCommand(module, command, playerObj, args)
if module == CommandsData.modules.TOC_DEBUG and DebugCommands[command] then
DebugCommands[command](playerObj, args)
end
end
Events.OnClientCommand.Add(OnClientDebugCommand)

View File

@@ -0,0 +1,31 @@
require('Items/Distributions')
require('Items/SuburbsDistributions')
-- Insert Prosts and various items in the Medical Clinic loot table
local prosthesisLoot = {
[1] = {
name = "TOC.Prost_HookArm_L",
chance = 3
},
[2] = {
name = "TOC.Prost_NormalArm_L",
chance = 2
},
[3] = {
name = "TOC.Surg_Arm_Tourniquet_L",
chance = 20
}
}
for i=1, #prosthesisLoot do
local tab = prosthesisLoot[i]
table.insert(ProceduralDistributions.list.MedicalClinicTools.items, tab.name)
table.insert(ProceduralDistributions.list.MedicalClinicTools.items, tab.chance)
end

View File

@@ -0,0 +1,15 @@
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
------------------------------
local og_ISObjectClickHandler_doClickSpecificObject = ISObjectClickHandler.doClickSpecificObject
---@param object IsoObject
---@param playerNum any
---@param playerObj IsoPlayer
function ISObjectClickHandler.doClickSpecificObject(object, playerNum, playerObj)
if CachedDataHandler.GetBothHandsFeasibility() then
og_ISObjectClickHandler_doClickSpecificObject(object, playerNum, playerObj)
end
return false
end

View File

@@ -0,0 +1,60 @@
if isClient() then return end -- The event makes this necessary to prevent clients from running this file
local StaticData = require("TOC/StaticData")
local CommandsData = require("TOC/CommandsData")
------------------------
local ServerDataHandler = {}
ServerDataHandler.modData = {}
---Get the server mod data table containing that player TOC data
---@param key string
---@return tocModDataType
function ServerDataHandler.GetTable(key)
return ServerDataHandler.modData[key]
end
---Add table to the ModData and a local table
---@param key string
---@param table tocModDataType
function ServerDataHandler.AddTable(key, table)
-- Check if key is valid
if not luautils.stringStarts(key, StaticData.MOD_NAME .. "_") then return end
TOC_DEBUG.print("Received TOC ModData: " .. tostring(key))
--TOC_DEBUG.printTable(table)
-- Set that the data has been modified and it's updated on the server
ModData.add(key, table) -- Add it to the server mod data
ServerDataHandler.modData[key] = table
-- Check integrity of table. if it doesn't contains toc data, it means that we received a reset
if table.limbs == nil then return end
-- Since this could be triggered by a different client than the one referenced in the key, we're gonna
-- apply the changes back to the key client again to be sure that everything is in sync
local username = CommandsData.GetUsername(key)
TOC_DEBUG.print("Reapplying to " .. username)
-- Since getPlayerFromUsername doesn't work in mp, we're gonna do this workaround. ew
local onlinePlayers = getOnlinePlayers()
local selectedPlayer
for i=0, onlinePlayers:size() - 1 do
---@type IsoPlayer
local player = onlinePlayers:get(i)
if player:getUsername() == username then
selectedPlayer = player
break
end
end
TOC_DEBUG.print("Player username from IsoPlayer: " .. selectedPlayer:getUsername())
sendServerCommand(selectedPlayer, CommandsData.modules.TOC_RELAY, CommandsData.client.Relay.ReceiveApplyFromServer, {})
end
Events.OnReceiveGlobalModData.Add(ServerDataHandler.AddTable)
return ServerDataHandler

View File

@@ -0,0 +1,44 @@
require ("TOC/Debug")
local CommandsData = require("TOC/CommandsData")
local ItemsController = require("TOC/Controllers/ItemsController")
--------------------------------------------
local ServerItemsCommands = {}
function ServerItemsCommands.SpawnAmputationItem(_, args)
local playerObj = getPlayerByOnlineID(args.playerNum)
local limbName = args.limbName
ItemsController.Player.SpawnAmputationItem(playerObj, limbName)
end
function ServerItemsCommands.DeleteOldAmputationItem(_, args)
local patientPl = getPlayerByOnlineID(args.playerNum)
local limbName = args.limbName
ItemsController.Player.DeleteOldAmputationItem(patientPl, limbName)
end
function ServerItemsCommands.DeleteAllOldAmputationItems(_, args)
local playerObj = getPlayerByOnlineID(args.playerNum)
ItemsController.Player.DeleteAllOldAmputationItems(playerObj)
end
function ServerItemsCommands.OverrideAmputationItemVisuals(_, args)
local playerObj = getPlayerByOnlineID(args.playerNum)
local limbName = args.limbName
local isCicatrized = args.isCicatrized
ItemsController.Player.OverrideAmputationItemVisuals(playerObj, limbName, isCicatrized)
end
--------------------------------------------------------------------
local function OnClientItemsCommands(module, command, playerObj, args)
if module == CommandsData.modules.TOC_ITEMS and ServerItemsCommands[command] then
TOC_DEBUG.print("Received ItemsController command - " .. tostring(command))
ServerItemsCommands[command](playerObj, args)
end
end
Events.OnClientCommand.Add(OnClientItemsCommands)

View File

@@ -0,0 +1,71 @@
require ("TOC/Debug")
local CommandsData = require("TOC/CommandsData")
--------------------------------------------
local ServerRelayCommands = {}
-- TODO We can easily make this a lot more simple without having functions
---Relay DamageDuringAmputation to another client
---@param args relayDamageDuringAmputationParams
function ServerRelayCommands.RelayDamageDuringAmputation(_, args)
local patientPl = getPlayerByOnlineID(args.patientNum)
---@type receiveDamageDuringAmputationParams
local params = {limbName = args.limbName}
sendServerCommand(patientPl, CommandsData.modules.TOC_RELAY, CommandsData.client.Relay.ReceiveDamageDuringAmputation, params)
end
---Relay ExecuteAmputationAction to another client
---@param surgeonPl IsoPlayer
---@param args relayExecuteAmputationActionParams
function ServerRelayCommands.RelayExecuteAmputationAction(surgeonPl, args)
local patientPl = getPlayerByOnlineID(args.patientNum)
local surgeonNum = surgeonPl:getPlayerNum()
---@type receiveDamageDuringAmputationParams
local params = {surgeonNum = surgeonNum, limbName = args.limbName, damagePlayer = true}
sendServerCommand(patientPl, CommandsData.modules.TOC_RELAY, CommandsData.client.Relay.ReceiveExecuteAmputationAction, params)
end
--* ADMIN ONLY *--
---Relay a local init from another client
---@param adminObj IsoPlayer
---@param args relayExecuteInitializationParams
function ServerRelayCommands.RelayExecuteInitialization(adminObj, args)
local patientPl = getPlayerByOnlineID(args.patientNum)
sendServerCommand(patientPl, CommandsData.modules.TOC_RELAY, CommandsData.client.Relay.ReceiveExecuteInitialization, {})
end
---Relay a forced amputation to another client.
---@param adminObj IsoPlayer
---@param args relayForcedAmputationParams
function ServerRelayCommands.RelayForcedAmputation(adminObj, args)
local patientPl = getPlayerByOnlineID(args.patientNum)
local adminNum = adminObj:getPlayerNum()
---@type receiveDamageDuringAmputationParams
local ampParams = {surgeonNum = adminNum, limbName = args.limbName, damagePlayer = false} -- the only difference between relayExecuteAmputationAction and this is the damage
sendServerCommand(patientPl, CommandsData.modules.TOC_RELAY, CommandsData.client.Relay.ReceiveExecuteAmputationAction, ampParams)
-- Automatic cicatrization
sendServerCommand(patientPl, CommandsData.modules.TOC_RELAY, CommandsData.client.Relay.ReceiveForcedCicatrization, {limbName = args.limbName})
end
function ServerRelayCommands.DeleteAllOldAmputationItems(_, args)
local playerObj = getPlayerByOnlineID(args.playerNum)
local ItemsController = require("TOC/Controllers/ItemsController")
ItemsController.Player.DeleteAllOldAmputationItems(playerObj)
end
-------------------------
local function OnClientRelayCommand(module, command, playerObj, args)
if module == CommandsData.modules.TOC_RELAY and ServerRelayCommands[command] then
TOC_DEBUG.print("Received Client Relay command - " .. tostring(command))
ServerRelayCommands[command](playerObj, args)
end
end
Events.OnClientCommand.Add(OnClientRelayCommand)

View File

@@ -0,0 +1,109 @@
--Based on RabenRabo's bodylocation solution from their mod "Fantasy Bodyparts" and "Fantasy Legs" sub-mod.
--Modified by GanydeBielovzki with permission for batch use for the Frockin' Splendor franchise and spin-offs.
--To copy, further modify or otherwise use this code the original creator and the modifier must be credited.
local function copyBodyLocationProperties(oldGroup, oldLocID, newGroup)
for k = 0, oldGroup:size()-1 do
local otherLocID = oldGroup:getLocationByIndex(k):getId()
if oldGroup:isExclusive(oldLocID, otherLocID)
then
newGroup:setExclusive(oldLocID, otherLocID)
end
if oldGroup:isHideModel(oldLocID, otherLocID)
then
newGroup:setHideModel(oldLocID, otherLocID)
end
if oldGroup:isAltModel(oldLocID, otherLocID)
then
newGroup:setAltModel(oldLocID, otherLocID)
end
end
end
local function addBodyLocationsAt(groupName, locationList)
local results = {}
-- get list (!!actually a view!!) of all groups and copy to array (BodyLocations.reset() will also clear the view)
local allGroupsList = BodyLocations.getAllGroups()
local allGroups = {}
for i = 0, allGroupsList:size()-1 do
allGroups[i + 1] = allGroupsList:get(i)
end
BodyLocations.reset()
-- recreate all groups/bodylocations and insert new bodylocations
for i = 1, #allGroups do
local oldGroup = allGroups[i]
local newGroup = BodyLocations.getGroup(oldGroup:getId())
-- FIRST: Process all original locations AND insert new ones at correct positions
for j = 0, oldGroup:size()-1 do
local oldLoc = oldGroup:getLocationByIndex(j)
local oldLocID = oldLoc:getId()
-- For each location definition, check if it should be inserted here
for _, locDef in ipairs(locationList) do
if oldGroup:getId() == groupName then
local newLocID = type(locDef.name) ~= "string" and locDef.name or
ItemBodyLocation.get(ResourceLocation.of(locDef.name))
local refLocID = type(locDef.reference) ~= "string" and locDef.reference or
ResourceLocation.of(locDef.reference)
local isTargetGroupAndLoc = refLocID == oldLocID
if isTargetGroupAndLoc and locDef.before then
results[locDef.name] = newGroup:getOrCreateLocation(newLocID)
end
end
end
-- Add the original location
newGroup:getOrCreateLocation(oldLocID)
-- Check for "after" insertions
for _, locDef in ipairs(locationList) do
if oldGroup:getId() == groupName then
local newLocID = type(locDef.name) ~= "string" and locDef.name or
ItemBodyLocation.get(ResourceLocation.of(locDef.name))
local refLocID = type(locDef.reference) ~= "string" and locDef.reference or
ResourceLocation.of(locDef.reference)
local isTargetGroupAndLoc = refLocID == oldLocID
if isTargetGroupAndLoc and not locDef.before then
results[locDef.name] = newGroup:getOrCreateLocation(newLocID)
end
end
end
end
-- SECOND: copy bodylocation properties from old groups to new groups
for j = 0, oldGroup:size()-1 do
local oldLocID = oldGroup:getLocationByIndex(j):getId()
newGroup:setMultiItem(oldLocID, oldGroup:isMultiItem(oldLocID))
copyBodyLocationProperties(oldGroup, oldLocID, newGroup)
end
end
return results
end
local results = addBodyLocationsAt("Human", {
{name = "toc:Arm_L", reference = ItemBodyLocation.FULL_TOP, before = false},
{name = "toc:Arm_R", reference = ItemBodyLocation.FULL_TOP, before = false},
{name = "toc:ArmProst_L", reference = ItemBodyLocation.FULL_TOP, before = false},
{name = "toc:ArmProst_R", reference = ItemBodyLocation.FULL_TOP, before = false},
{name = "toc:ArmAccessory_L", reference = ItemBodyLocation.FULL_TOP, before = false},
{name = "toc:ArmAccessory_R", reference = ItemBodyLocation.FULL_TOP, before = false},
})
results['toc:Arm_L']:setMultiItem(true)
results['toc:Arm_R']:setMultiItem(true)
results['toc:ArmProst_L']:setMultiItem(true)
results['toc:ArmProst_R']:setMultiItem(true)
results['toc:ArmAccessory_L']:setMultiItem(true)
results['toc:ArmAccessory_R']:setMultiItem(true)

View File

@@ -0,0 +1,59 @@
local StaticData = require("TOC/StaticData")
------------------------
local CommandsData = {}
CommandsData.modules = {
TOC_DEBUG = "TOC_DEBUG",
TOC_RELAY = "TOC_RELAY",
TOC_ITEMS = "TOC_ITEMS"
}
CommandsData.client = {
Relay = {
ReceiveDamageDuringAmputation = "ReceiveDamageDuringAmputation", ---@alias receiveDamageDuringAmputationParams { limbName : string}
ReceiveExecuteAmputationAction = "ReceiveExecuteAmputationAction", ---@alias receiveExecuteAmputationActionParams {surgeonNum : number, limbName : string, damagePlayer : boolean}
--* APPLY *--
ReceiveApplyFromServer = "ReceiveApplyFromServer",
ReceiveWearAmputation = "ReceiveWearAmputation",
--* ADMIN ONLY --*
ReceiveExecuteInitialization = "ReceiveExecuteInitialization",
ReceiveForcedCicatrization = "ReceiveForcedCicatrization" ---@alias receiveForcedCicatrizationParams {limbName : string}
}
}
CommandsData.server = {
Debug = {
PrintTocData = "PrintTocData", ---@alias printTocDataParams {username : string}
PrintAllTocData = "PrintAllTocData"
},
Relay = {
RelayDamageDuringAmputation = "RelayDamageDuringAmputation", ---@alias relayDamageDuringAmputationParams {patientNum : number, limbName : string}
RelayExecuteAmputationAction = "RelayExecuteAmputationAction", ---@alias relayExecuteAmputationActionParams {patientNum : number, limbName : string}
--* ADMIN ONLY *--
RelayExecuteInitialization = "RelayExecuteInitialization", ---@alias relayExecuteInitializationParams {patientNum : number}
RelayForcedAmputation = "RelayForcedAmputation" ---@alias relayForcedAmputationParams {patientNum : number, limbName : string}
}
}
---Get the correct key for that particular player to be used in the global mod data table
---@param username string
---@return string
function CommandsData.GetKey(username)
return StaticData.MOD_NAME .. "_" .. username
end
function CommandsData.GetUsername(key)
return string.sub(key, #StaticData.MOD_NAME + 2, #key) -- Not sure why +2... Something with kahlua, it should be +1
end
function CommandsData.GetZombieKey()
return StaticData.MOD_NAME .. "_ZOMBIES"
end
return CommandsData

View File

@@ -0,0 +1,112 @@
TOC_DEBUG = {}
TOC_DEBUG.disablePaneMod = false
TOC_DEBUG.enableHealthPanelDebug = false
function TOC_DEBUG.TogglePaneMod()
TOC_DEBUG.disablePaneMod = not TOC_DEBUG.disablePaneMod
end
function TOC_DEBUG.ToggleHealthPanelDebug()
TOC_DEBUG.enableHealthPanelDebug = not TOC_DEBUG.enableHealthPanelDebug
end
---Print debug
---@param string string
function TOC_DEBUG.print(string)
if isDebugEnabled() then
local runningFile = TOC_DEBUG.getRunningFile()
print("[TOC]" .. "[" .. runningFile .. "] " .. tostring(string))
else
print(string)
end
end
---Horrendous but I don't really care about performance for this
---@return string
function TOC_DEBUG.getRunningFile()
local coroutine = getCurrentCoroutine()
local o = getCoroutineObjStack(coroutine, 0)
if o then
local s = KahluaUtil.rawTostring2(o)
local match = string.match(s, "file: (%w+)%.lua")
if match then return match end
end
return ""
end
function TOC_DEBUG.printTable(table, indent)
if not table then return end
indent = indent or ""
for key, value in pairs(table) do
if type(value) == "table" then
print(indent .. key .. " (table):")
TOC_DEBUG.printTable(value, indent .. " ")
else
print(indent .. key .. ":", value)
end
end
end
---------------------------------
--* Random debug commands *--
function TOC_DEBUG.TestBodyDamage(id)
local StaticData = require("TOC/StaticData")
local pl = getPlayerByOnlineID(id)
local bd = pl:getBodyDamage()
TOC_DEBUG.print(tostring(bd))
if bd then
TOC_DEBUG.print("bd for " .. pl:getUsername() .. " exists")
local bptEnum = StaticData.LIMBS_TO_BODYLOCS_IND_BPT["Hand_L"]
local bodyPart = bd:getBodyPart(bptEnum)
bodyPart:setBleeding(true)
bodyPart:setCut(true)
TOC_DEBUG.print(tostring(bodyPart))
end
end
function TOC_DEBUG.TestBloodDrop()
local pl = getPlayer()
--IsoZombieGiblets.GibletType.A
--local giblets = IsoZombieGiblets.new(getCell())
local sq = pl:getSquare()
local t = IsoZombieGiblets.class.GibletType
print(t)
--IsoBall.new(getCell(), pl:getX(), pl:)
addBloodSplat(sq, 100)
--pl:getChunk():addBloodSplat(pl:getX(), pl:getY(), pl:getZ(), 100)
--IsoZombieGiblets.new(x, getCell(), pl:getX(), pl:getY(), pl:getZ(), 100, 1)
end
---------------------------------
--* Debug server commands *--
local CommandsData = require("TOC/CommandsData")
function TOC_DEBUG.printPlayerServerModData(username)
sendClientCommand(CommandsData.modules.TOC_DEBUG, CommandsData.server.Debug.PrintTocData, {username = username})
end
function TOC_DEBUG.printAllServerModData()
sendClientCommand(CommandsData.modules.TOC_DEBUG, CommandsData.server.Debug.PrintAllTocData, {})
end
function TOC_DEBUG.testRelayDamage()
---@type relayDamageDuringAmputationParams
local params = {limbName = "Hand_R", patientNum = getPlayer():getOnlineID()}
sendClientCommand(CommandsData.modules.TOC_RELAY, CommandsData.server.Relay.RelayDamageDuringAmputation, params)
end

View File

@@ -0,0 +1,31 @@
-- instead of relying on local to save og methods, we save them in a table here that we can use later.
---@class OverridenMethodsArchive
local OverridenMethodsArchive = {}
OverridenMethodsArchive.methods = {}
-- Save an original method, if it wasn't already saved and returns it to be used in common
function OverridenMethodsArchive.Save(methodName, method)
if not OverridenMethodsArchive.methods[methodName] then
OverridenMethodsArchive.methods[methodName] = method
TOC_DEBUG.print("Saved method " .. methodName)
end
return method
end
-- Get the original method
function OverridenMethodsArchive.Get(methodName)
--TOC_DEBUG.print("Getting og method " .. methodName)
--TOC_DEBUG.print("OverridenMethodsArchive.list[methodName] = " .. tostring(OverridenMethodsArchive.methods[methodName]))
--TOC_DEBUG.print(methodName)
--TOC_DEBUG.print(OverridenMethodsArchive.methods[methodName])
return OverridenMethodsArchive.methods[methodName]
end
return OverridenMethodsArchive

View File

@@ -0,0 +1 @@
return _TOCRegistries

View File

@@ -0,0 +1,300 @@
---@alias partDataType { isCut : boolean?, isInfected : boolean?, isOperated : boolean?, isCicatrized : boolean?, isCauterized : boolean?, isVisible : boolean?, woundDirtyness : number, cicatrizationTime : number }
---@alias limbsTable {Hand_L : partDataType, ForeArm_L : partDataType, UpperArm_L : partDataType, Hand_R : partDataType, ForeArm_R : partDataType, UpperArm_R : partDataType }
---@alias prosthesisData {isProstEquipped : boolean, prostFactor : number }
---@alias prosthesesTable {Top_L : prosthesisData, Top_R : prosthesisData } -- TODO add Bottom_L and Bottom_R
---@alias tocModDataType { limbs : limbsTable, prostheses : prosthesesTable, isIgnoredPartInfected : boolean, isAnyLimbCut : boolean}
---------------------------
-- _STR = Only strings, no index
-- _IND_STR = indexed Strings
-- _IND_BPT = Indexed BodyPartType
-- PART = Single part, could be hand, forearm, etc
-- LIMB = Part + side
-- BODYLOCS = Body Locations
local StaticData = {}
---Mod name, used to setup Global Mod Data and various stuff
StaticData.MOD_NAME = "TOC"
-- Game version, used to correct some stuff instead of relying on versioned folders
StaticData.COMPAT_42 = luautils.stringStarts(getGameVersion(), "42")
-------------------------
--* Base
-- TODO Add references inside tables instead of making multiple tables
StaticData.SIDES_IND_STR = {
R = "R",
L = "L"
}
StaticData.SIDES_STR = {
"R", "L"
}
StaticData.PARTS_IND_STR = {
Hand = "Hand",
ForeArm = "ForeArm",
UpperArm = "UpperArm"
}
StaticData.PARTS_STR = {
"Hand",
"ForeArm",
"UpperArm"
}
-- No "MAX" here.
StaticData.IGNORED_BODYLOCS_BPT = {
BodyPartType.Foot_L, BodyPartType.Foot_R, BodyPartType.Groin, BodyPartType.Head,
BodyPartType.LowerLeg_L, BodyPartType.LowerLeg_R, BodyPartType.Neck, BodyPartType.Torso_Lower,
BodyPartType.Torso_Upper, BodyPartType.UpperLeg_L, BodyPartType.UpperLeg_R
}
-- Assembled BodyParts string
StaticData.LIMBS_STR = {}
StaticData.LIMBS_IND_STR = {}
StaticData.LIMBS_DEPENDENCIES_IND_STR = {}
StaticData.LIMBS_ADJACENT_IND_STR = {}
StaticData.LIMBS_CICATRIZATION_TIME_IND_NUM = {}
StaticData.LIMBS_BASE_DAMAGE_IND_NUM = {}
StaticData.LIMBS_TIME_MULTIPLIER_IND_NUM = {}
StaticData.LIMBS_TO_BODYLOCS_IND_BPT = {} -- {limbName = bodyLoc}
StaticData.BODYLOCS_TO_LIMBS_IND_STR = {} -- {bodyLoc = limbName}
-- FIXME You weren't considering surgeonFactor, which decreases that base time. Fuck mod 60
-- CicatrizationBaseTime should be mod 60 since we're using EveryHours to update the cicatrizationTime
---@param assembledName string
---@param side string
local function AssembleHandData(assembledName, side)
StaticData.LIMBS_BASE_DAMAGE_IND_NUM[assembledName] = 60
StaticData.LIMBS_CICATRIZATION_TIME_IND_NUM[assembledName] = 120
StaticData.LIMBS_TIME_MULTIPLIER_IND_NUM[assembledName] = 2
StaticData.LIMBS_DEPENDENCIES_IND_STR[assembledName] = {}
StaticData.LIMBS_ADJACENT_IND_STR[assembledName] = StaticData.PARTS_IND_STR.ForeArm .. "_" .. side
end
---@param assembledName string
---@param side string
local function AssembleForearmData(assembledName, side)
StaticData.LIMBS_BASE_DAMAGE_IND_NUM[assembledName] = 80
StaticData.LIMBS_CICATRIZATION_TIME_IND_NUM[assembledName] = 144
StaticData.LIMBS_TIME_MULTIPLIER_IND_NUM[assembledName] = 3
StaticData.LIMBS_DEPENDENCIES_IND_STR[assembledName] = { StaticData.PARTS_IND_STR.Hand .. "_" .. side }
StaticData.LIMBS_ADJACENT_IND_STR[assembledName] = StaticData.PARTS_IND_STR.UpperArm .. "_" .. side
end
---@param assembledName string
---@param side string
local function AssembleUpperarmData(assembledName, side)
StaticData.LIMBS_BASE_DAMAGE_IND_NUM[assembledName] = 100
StaticData.LIMBS_CICATRIZATION_TIME_IND_NUM[assembledName] = 192
StaticData.LIMBS_TIME_MULTIPLIER_IND_NUM[assembledName] = 4
StaticData.LIMBS_DEPENDENCIES_IND_STR[assembledName] = { StaticData.PARTS_IND_STR.Hand .. "_" .. side,
StaticData.PARTS_IND_STR.ForeArm .. "_" .. side }
StaticData.LIMBS_ADJACENT_IND_STR[assembledName] = "Torso_Upper"
end
for side, _ in pairs(StaticData.SIDES_IND_STR) do
for part, _ in pairs(StaticData.PARTS_IND_STR) do
local assembledName = part .. "_" .. side
-- Assembled strings
table.insert(StaticData.LIMBS_STR, assembledName) -- We need a table like this to cycle through it easily
StaticData.LIMBS_IND_STR[assembledName] = assembledName
-- BodyParts stuff
---@type BodyPartType
local bptType = BodyPartType[assembledName]
local bptString = BodyPartType.ToString(bptType)
StaticData.LIMBS_TO_BODYLOCS_IND_BPT[assembledName] = bptType
StaticData.BODYLOCS_TO_LIMBS_IND_STR[bptString] = assembledName
-- Dependencies and cicatrization time
if part == StaticData.PARTS_IND_STR.Hand then
AssembleHandData(assembledName, side)
elseif part == StaticData.PARTS_IND_STR.ForeArm then
AssembleForearmData(assembledName, side)
elseif part == StaticData.PARTS_IND_STR.UpperArm then
AssembleUpperarmData(assembledName, side)
end
end
end
-----------------
--* Amputation Groups
StaticData.AMP_GROUPS_BASE_IND_STR = {
Top = "Top",
Bottom = "Bottom"
}
-- FIX This should be aligned with the body locs, no reason anymore to keep it separated
StaticData.AMP_GROUPS_IND_STR = {}
StaticData.AMP_GROUPS_STR = {}
for side, _ in pairs(StaticData.SIDES_IND_STR) do
for group, _ in pairs(StaticData.AMP_GROUPS_BASE_IND_STR) do
local sidedGroup = group .. "_" .. side
StaticData.AMP_GROUPS_IND_STR[sidedGroup] = sidedGroup
table.insert(StaticData.AMP_GROUPS_STR, sidedGroup)
end
end
-- TODO We can do this in one pass if we do it before
StaticData.AMP_GROUP_TO_LIMBS_MATCH_IND_STR = {} -- This is probably unnecessary
StaticData.LIMBS_TO_AMP_GROUPS_MATCH_IND_STR = {}
for side, _ in pairs(StaticData.SIDES_IND_STR) do
for part, _ in pairs(StaticData.PARTS_IND_STR) do
local limbName = part .. "_" .. side
local group
if part == StaticData.PARTS_IND_STR.Hand or part == StaticData.PARTS_IND_STR.ForeArm or part == StaticData.PARTS_IND_STR.UpperArm then
group = StaticData.AMP_GROUPS_BASE_IND_STR.Top
else
group = StaticData.AMP_GROUPS_BASE_IND_STR.Bottom
end
local sidedGroup = group .. "_" .. side
if StaticData.AMP_GROUP_TO_LIMBS_MATCH_IND_STR[sidedGroup] == nil then
StaticData.AMP_GROUP_TO_LIMBS_MATCH_IND_STR[sidedGroup] = {}
end
table.insert(StaticData.AMP_GROUP_TO_LIMBS_MATCH_IND_STR[sidedGroup], limbName)
StaticData.LIMBS_TO_AMP_GROUPS_MATCH_IND_STR[limbName] = sidedGroup
end
end
StaticData.TOURNIQUET_BODYLOCS_TO_GROUPS_IND_STR = {
[ItemBodyLocation.HANDS_LEFT] = StaticData.AMP_GROUPS_IND_STR.Top_L,
[ItemBodyLocation.HANDS_RIGHT] = StaticData.AMP_GROUPS_IND_STR.Top_R
}
StaticData.AFFECTED_BODYLOCS_TO_LIMBS_IND_STR = {}
local handsBodyLocs = {"Hands%s", "%s_MiddleFinger", "%s_RingFinger"}
local foreArmBodyLocs = {"%sWrist"}
for side, _ in pairs(StaticData.SIDES_IND_STR) do
for part, _ in pairs(StaticData.PARTS_IND_STR) do
local limbName = part .. "_" .. side
local sideFull
if side == 'R' then sideFull = "Right" else sideFull = "Left" end
if part == "Hand" then
for i=1, #handsBodyLocs do
local bl = string.format(handsBodyLocs[i], sideFull)
StaticData.AFFECTED_BODYLOCS_TO_LIMBS_IND_STR[bl] = limbName
end
elseif part == "ForeArm" then
-- -- UGLY very ugly
-- for i=1, #handsBodyLocs do
-- local bl = string.format(handsBodyLocs[i], sideFull)
-- StaticData.AFFECTED_BODYLOCS_TO_LIMBS_IND_STR[bl] = limbName
-- end
for i=1, #foreArmBodyLocs do
local bl = string.format(foreArmBodyLocs[i], sideFull)
StaticData.AFFECTED_BODYLOCS_TO_LIMBS_IND_STR[bl] = limbName
end
end
end
end
-----------------
--* Traits
-- Link a trait to a specific body part
StaticData.TRAITS_BP = {
Amputee_Hand = "Hand_L",
Amputee_ForeArm = "ForeArm_L",
Amputee_UpperArm = "UpperArm_L",
}
-----------------
--* Visuals and clothing
--- Textures
StaticData.HEALTH_PANEL_TEXTURES = {
Female = {
Hand_L = getTexture("media/ui/Female/Hand_L.png"),
ForeArm_L = getTexture("media/ui/Female/ForeArm_L.png"),
UpperArm_L = getTexture("media/ui/Female/UpperArm_L.png"),
Hand_R = getTexture("media/ui/Female/Hand_R.png"),
ForeArm_R = getTexture("media/ui/Female/ForeArm_R.png"),
UpperArm_R = getTexture("media/ui/Female/UpperArm_R.png")
},
Male = {
Hand_L = getTexture("media/ui/Male/Hand_L.png"),
ForeArm_L = getTexture("media/ui/Male/ForeArm_L.png"),
UpperArm_L = getTexture("media/ui/Male/UpperArm_L.png"),
Hand_R = getTexture("media/ui/Male/Hand_R.png"),
ForeArm_R = getTexture("media/ui/Male/ForeArm_R.png"),
UpperArm_R = getTexture("media/ui/Male/UpperArm_R.png")
},
ProstArm = {
L = getTexture("media/ui/ProstArm_L.png"),
R = getTexture("media/ui/ProstArm_R.png")
}
}
StaticData.AMPUTATION_CLOTHING_ITEM_BASE = "TOC.Amputation_"
------------------
--* Items check
local sawObj
local gardenSawObj
if StaticData.COMPAT_42 then
sawObj = instanceItem("Base.Saw")
gardenSawObj = instanceItem("Base.GardenSaw")
else
sawObj = InventoryItemFactory.CreateItem("Base.Saw")
gardenSawObj = InventoryItemFactory.CreateItem("Base.GardenSaw")
end
StaticData.SAWS_NAMES_IND_STR = {
saw = sawObj:getName(),
gardenSaw = gardenSawObj:getName()
}
StaticData.SAWS_TYPES_IND_STR = {
saw = sawObj:getType(),
gardenSaw = gardenSawObj:getType()
}
return StaticData

View File

@@ -0,0 +1,22 @@
ContextMenu_DE = {
ContextMenu_Amputate = "Amputieren",
ContextMenu_Amputate_Bandage = "Amputieren und bandagieren",
ContextMenu_Amputate_Stitch = "Amputieren und nähen",
ContextMenu_Amputate_Stitch_Bandage = "Amputieren, nähen und bandagieren",
ContextMenu_Cauterize = "Kauterisieren",
ContextMenu_Limb_Hand_L = "Linke Hand",
ContextMenu_Limb_ForeArm_L = "Linker Unterarm",
ContextMenu_Limb_UpperArm_L = "Linker Oberarm"
ContextMenu_Limb_Hand_R = "Rechte Hand",
ContextMenu_Limb_ForeArm_R = "Rechter Unterarm",
ContextMenu_Limb_UpperArm_R = "Rechter Oberarm",
ContextMenu_InstallProstRight = "Instaliere Prothese am rechten Arm",
ContextMenu_InstallProstLeft = "Instaliere Prothese am linken Arm",
ContextMenu_PutTourniquetArmLeft = "Lege das Tourniquet am linken Arm an",
ContextMenu_PutTourniquetLegL = "Lege das Tourniquet am linken Bein an",
ContextMenu_PutTourniquetArmRight = "Lege das Tourniquet am rechten Arm an",
ContextMenu_PutTourniquetLegR = "Lege das Tourniquet am rechten Bein an",
ContextMenu_CleanWound = "Saubere Wunde",
}

View File

@@ -0,0 +1,18 @@
IG_UI_DE = {
IGUI_Yes = "Ja",
IGUI_No = "Nein",
IGUI_perks_Amputations = "Amputationen",
IGUI_perks_Side_R = "Rechte Seite",
IGUI_perks_Side_L = "Linke Seite",
IGUI_perks_Prosthesis = "Prothese",
IGUI_perks_ProstFamiliarity= "Vertrautheit",
IGUI_ItemCat_Prosthesis = "Prothese",
IGUI_ItemCat_Surgery = "Operation",
IGUI_ItemCat_Amputation = "Amputation"
IGUI_HealthPanel_Cicatrization = "Vernarbung",
IGUI_HealthPanel_Cicatrized = "Vernarbt",
IGUI_HealthPanel_Cauterized = "Kauterisiert",
IGUI_HealthPanel_WoundDirtyness = "Wundverschmutzung",
IGUI_HealthPanel_ProstEquipped = "Prothese angelegt",
}

View File

@@ -0,0 +1,9 @@
ItemName_DE = {
ItemName_TOC.Surg_Arm_Tourniquet_L = "Tourniquet",
ItemName_TOC.Surg_Arm_Tourniquet_R = "Tourniquet",
ItemName_TOC.Prost_NormalArm_L = "Armprothese Links",
ItemName_TOC.Prost_NormalArm_R = "Armprothese Rechts",
ItemName_TOC.Prost_HookArm_L = "Linke Armprothese - Haken",
ItemName_TOC.Prost_HookArm_R = "Rechte Armprothese - Haken",
}

View File

@@ -0,0 +1,4 @@
Recipes_DE = {
Recipe_Craft_Prosthetic_Arm = "Baue Armprothese",
Recipe_Craft_Prosthetic_Hook = "Baue Hakenprothese",
}

View File

@@ -0,0 +1,6 @@
Sandbox_EN = {
Sandbox_TOC = "The Only Cure - Die einzige Heilung",
Sandbox_TOC_CicatrizationSpeed = "Vernarbungs Geschwindigkeit",
Sandbox_TOC_WoundDirtynessMultiplier = "Wundenverschmutzung Multiplikator",
Sandbox_TOC_SurgeonAbilityImportance = "Relevanz der Fähigkeiten des Chirurgen",
}

View File

@@ -0,0 +1,8 @@
Tooltip_DE = {
Tooltip_Surgery_CantCauterize = "Du kannst die Wunde nicht kauterisieren",
Tooltip_Surgery_And = " und "
Tooltip_Surgery_TempTooLow = "Die Temperatur ist immer noch zu niedrig",
Tooltip_Surgery_Coward = "Du hast nicht den Mut dazu",
Tooltip_Surgery_LimbNotFree = "Zuerst musst du die Prothese abnehmen",
}

View File

@@ -0,0 +1,11 @@
UI_DE = {
UI_trait_Amputee_Hand = "Amputierte linke Hand",
UI_trait_Amputee_Hand_desc = "",
UI_trait_Amputee_ForeArm = "Amputierter linker Unterarm",
UI_trait_Amputee_ForeArm_desc = "",
UI_trait_Amputee_UpperArm = "Amputierter linker Oberarm",
UI_trait_Amputee_UpperArm_desc = "",
UI_trait_Insensitive = "Unempfindlich",
UI_trait_Insensitive_desc = "",
UI_Say_CantEquip = "Ich kann das so nicht ausrüsten..."
}

View File

@@ -0,0 +1,33 @@
ContextMenu_EN = {
ContextMenu_Amputate = "Amputate",
ContextMenu_Amputate_Bandage = "Amputate and bandage",
ContextMenu_Amputate_Stitch = "Amputate and stitches",
ContextMenu_Amputate_Stitch_Bandage = "Amputate, stitches and bandage",
ContextMenu_Cauterize = "Cauterize",
ContextMenu_Limb_Hand_L = "Left Hand",
ContextMenu_Limb_ForeArm_L = "Left Forearm",
ContextMenu_Limb_UpperArm_L = "Left UpperArm"
ContextMenu_Limb_Hand_R = "Right Hand",
ContextMenu_Limb_ForeArm_R = "Right Forearm",
ContextMenu_Limb_UpperArm_R = "Right UpperArm",
ContextMenu_InstallProstRight = "Install prosthesis on right arm",
ContextMenu_InstallProstLeft = "Install prosthesis on left arm",
ContextMenu_PutTourniquetArmLeft = "Put tourniquet on left arm",
ContextMenu_PutTourniquetLegL = "Put tourniquet on left leg",
ContextMenu_PutTourniquetArmRight = "Put tourniquet on right arm",
ContextMenu_PutTourniquetLegR = "Put tourniquet on right leg",
ContextMenu_CleanWound = "Clean Wound",
ContextMenu_Admin_TOC = "TOC",
ContextMenu_Admin_ResetTOC = "Reset Amputations",
ContextMenu_Admin_ForceAmputation = "Force Amputation",
}

View File

@@ -0,0 +1,25 @@
IG_UI_EN = {
IGUI_Yes = "Yes",
IGUI_No = "No",
IGUI_perks_Amputations = "Amputations",
IGUI_perks_Side_R = "Right Side",
IGUI_perks_Right Side_Description = "Familiarity with amputations on the right side of your body",
IGUI_perks_Side_L = "Left Side",
IGUI_perks_Left Side_Description = "Familiarity with amputations on the left side of your body",
IGUI_perks_Prosthesis = "Prosthesis Familiarity",
IGUI_perks_Prosthesis Familiarity_Description = "Familiarity with prosthetic limbs",
IGUI_ItemCat_Prosthesis = "Prosthesis",
IGUI_ItemCat_Surgery = "Surgery",
IGUI_ItemCat_Amputation = "Amputation"
IGUI_HealthPanel_Cicatrization = "Cicatrization",
IGUI_HealthPanel_Cicatrized = "Cicatrized",
IGUI_HealthPanel_Cauterized = "Cauterized",
IGUI_HealthPanel_WoundDirtyness = "Wound Dirtyness",
IGUI_HealthPanel_ProstEquipped = "Prosthesis Equipped",
IGUI_Confirmation_Amputate = " <CENTRE> Do you really want to amputate that limb?"
}

View File

@@ -0,0 +1,11 @@
ItemName_EN = {
ItemName_TOC.Surg_Arm_Tourniquet_L = "Tourniquet",
ItemName_TOC.Surg_Arm_Tourniquet_R = "Tourniquet",
ItemName_TOC.Prost_NormalArm_L = "Prosthetic Arm",
ItemName_TOC.Prost_NormalArm_R = "Prosthetic Arm",
ItemName_TOC.Prost_HookArm_L = "Prosthetic Arm - Hook",
ItemName_TOC.Prost_HookArm_R = "Prosthetic Arm - Hook",
}

View File

@@ -0,0 +1,4 @@
Recipes_EN = {
Recipe_Craft_Prosthetic_Arm = "Craft Prosthetic Arm",
Recipe_Craft_Prosthetic_Hook = "Craft Prosthetic Hook",
}

View File

@@ -0,0 +1,10 @@
Sandbox_EN = {
Sandbox_TOC = "The Only Cure",
Sandbox_TOC_CicatrizationSpeed = "Cicatrization Speed",
Sandbox_TOC_WoundDirtynessMultiplier = "Wound Dirtyness Multiplier",
Sandbox_TOC_SurgeonAbilityImportance = "Relevance of surgeon doctor ability",
Sandbox_TOC_EnableZombieAmputations = "Enable Zombie amputations",
Sandbox_TOC_ZombieAmputationDamageThreshold = "Zombie amputations damage treshold",
Sandbox_TOC_ZombieAmputationDamageChance = "Zombie amputations damage chance",
}

View File

@@ -0,0 +1,10 @@
Tooltip_EN = {
Tooltip_Surgery_CantCauterize = "You can't cauterize the wound",
Tooltip_Surgery_And = " and "
Tooltip_Surgery_TempTooLow = "The temperature is still too low",
Tooltip_Surgery_Coward = "You don't have the guts to do it",
Tooltip_Surgery_LimbNotFree = "You need to remove the prosthesis first",
}

View File

@@ -0,0 +1,16 @@
UI_EN = {
UI_trait_Amputee_Hand = "Amputated Left Hand",
UI_trait_Amputee_Hand_desc = "",
UI_trait_Amputee_ForeArm = "Amputated Left Forearm",
UI_trait_Amputee_ForeArm_desc = "",
UI_trait_Amputee_UpperArm = "Amputated Left Upper arm",
UI_trait_Amputee_UpperArm_desc = "",
UI_trait_Insensitive = "Insensitive",
UI_trait_Insensitive_desc = "",
UI_Say_CantEquip = "I can't equip it like this..."
}

View File

@@ -0,0 +1,29 @@
ContextMenu_FR = {
ContextMenu_Amputate = "Amputer",
ContextMenu_Amputate_Bandage = "Amputer et bander",
ContextMenu_Amputate_Stitch = "Amputer et suturer",
ContextMenu_Amputate_Stitch_Bandage = "Amputer, suturer et bander",
ContextMenu_Cauterize = "Cautériser",
ContextMenu_Limb_Hand_L = "Main gauche",
ContextMenu_Limb_ForeArm_L = "Avant-bras gauche",
ContextMenu_Limb_UpperArm_L = "Bras supérieur gauche",
ContextMenu_Limb_Hand_R = "Main droite",
ContextMenu_Limb_ForeArm_R = "Avant-bras droit",
ContextMenu_Limb_UpperArm_R = "Bras supérieur droit",
ContextMenu_InstallProstRight = "Installer une prothèse sur le bras droit",
ContextMenu_InstallProstLeft = "Installer une prothèse sur le bras gauche",
ContextMenu_PutTourniquetArmLeft = "Mettre un garrot sur le bras gauche",
ContextMenu_PutTourniquetLegL = "Mettre un garrot sur la jambe gauche",
ContextMenu_PutTourniquetArmRight = "Mettre un garrot sur le bras droit",
ContextMenu_PutTourniquetLegR = "Mettre un garrot sur la jambe droite",
ContextMenu_CleanWound = "Nettoyer la plaie",
ContextMenu_Admin_TOC = "TOC",
ContextMenu_Admin_ResetTOC = "Réinitialiser les amputations",
ContextMenu_Admin_ForceAmputation = "Forcer l'amputation",
}

View File

@@ -0,0 +1,21 @@
IG_UI_FR = {
IGUI_Yes = "Oui",
IGUI_No = "Non",
IGUI_perks_Amputations = "Amputations",
IGUI_perks_Side_R = "Côté droit",
IGUI_perks_Side_L = "Côté gauche",
IGUI_perks_Prosthesis = "Prothèse",
IGUI_perks_ProstFamiliarity = "Familiarité",
IGUI_ItemCat_Prosthesis = "Prothèse",
IGUI_ItemCat_Surgery = "Chirurgie",
IGUI_ItemCat_Amputation = "Amputation",
IGUI_HealthPanel_Cicatrization = "Cicatrisation",
IGUI_HealthPanel_Cicatrized = "Cicatrisé",
IGUI_HealthPanel_Cauterized = "Cautérisé",
IGUI_HealthPanel_WoundDirtyness = "Saleté de la plaie",
IGUI_HealthPanel_ProstEquipped = "Prothèse équipée",
}

View File

@@ -0,0 +1,11 @@
ItemName_FR = {
ItemName_TOC.Surg_Arm_Tourniquet_L = "Garrot",
ItemName_TOC.Surg_Arm_Tourniquet_R = "Garrot",
ItemName_TOC.Prost_NormalArm_L = "Bras prothétique",
ItemName_TOC.Prost_NormalArm_R = "Bras prothétique",
ItemName_TOC.Prost_HookArm_L = "Bras prothétique - Crochet",
ItemName_TOC.Prost_HookArm_R = "Bras prothétique - Crochet",
}

View File

@@ -0,0 +1,5 @@
Recipes_FR = {
Recipe_Craft_Prosthetic_Arm = "Fabriquer un bras prothétique",
Recipe_Craft_Prosthetic_Hook = "Fabriquer un crochet prothétique",
}

View File

@@ -0,0 +1,10 @@
Sandbox_FR = {
Sandbox_TOC = "Le Seul Remède",
Sandbox_TOC_CicatrizationSpeed = "Vitesse de cicatrisation",
Sandbox_TOC_WoundDirtynessMultiplier = "Multiplicateur de saleté de la plaie",
Sandbox_TOC_SurgeonAbilityImportance = "Importance de la compétence du chirurgien",
Sandbox_TOC_EnableZombieAmputations = "Activer les amputations de zombies",
Sandbox_TOC_ZombieAmputationDamageThreshold = "Seuil de dégâts pour amputations de zombies",
Sandbox_TOC_ZombieAmputationDamageChance = "Probabilité d'amputations de zombies",
}

View File

@@ -0,0 +1,10 @@
Tooltip_FR = {
Tooltip_Surgery_CantCauterize = "Vous ne pouvez pas cautériser la plaie",
Tooltip_Surgery_And = " et ",
Tooltip_Surgery_TempTooLow = "La température est encore trop basse",
Tooltip_Surgery_Coward = "Vous n'avez pas le courage de le faire",
Tooltip_Surgery_LimbNotFree = "Vous devez d'abord retirer la prothése",
}

View File

@@ -0,0 +1,16 @@
UI_FR = {
UI_trait_Amputee_Hand = "Main gauche amputée",
UI_trait_Amputee_Hand_desc = "",
UI_trait_Amputee_ForeArm = "Avant-bras gauche amputée",
UI_trait_Amputee_ForeArm_desc = "",
UI_trait_Amputee_UpperArm = "Bras supérieur gauche amputée",
UI_trait_Amputee_UpperArm_desc = "",
UI_trait_Insensitive = "Insensible",
UI_trait_Insensitive_desc = "",
UI_Say_CantEquip = "Je ne peux pas l'équiper comme ça..."
}

View File

@@ -0,0 +1,33 @@
ContextMenu_IT = {
ContextMenu_Amputate = "Amputa",
ContextMenu_Amputate_Bandage = "Amputa e fascia",
ContextMenu_Amputate_Stitch = "Amputa e metti i punti",
ContextMenu_Amputate_Stitch_Bandage = "Amputate, metti i punti e fascia",
ContextMenu_Cauterize = "Cauterizza",
ContextMenu_Limb_Hand_L = "Mano Sinistra",
ContextMenu_Limb_ForeArm_L = "Avambraccio Sinistro",
ContextMenu_Limb_UpperArm_L = "Braccio Superiore Sinistro",
ContextMenu_Limb_Hand_R = "Mano Destra",
ContextMenu_Limb_ForeArm_R = "Avambraccio Destro",
ContextMenu_Limb_UpperArm_R = "Braccio Superiore Destro",
ContextMenu_InstallProstRight = "Installa protesi sul braccio destro",
ContextMenu_InstallProstLeft = "Installa protesi sul braccio sinistro",
ContextMenu_PutTourniquetArmLeft = "Metti laccio emostatico sul braccio sinistro",
ContextMenu_PutTourniquetLegL = "Metti laccio emostatico sulla gamba sinistra",
ContextMenu_PutTourniquetArmRight = "Metti laccio emostatico sul braccio destro",
ContextMenu_PutTourniquetLegR = "Metti laccio emostatico sulla gamba destra",
ContextMenu_CleanWound = "Pulisci ferita",
ContextMenu_Admin_TOC = "TOC",
ContextMenu_Admin_ResetTOC = "Reset Amputations",
ContextMenu_Admin_ForceAmputation = "Force Amputation",
}

View File

@@ -0,0 +1,21 @@
IG_UI_IT = {
IGUI_Yes = "Si",
IGUI_No = "No",
IGUI_perks_Amputations = "Amputazioni",
IGUI_perks_Side_R = "Parte destra",
IGUI_perks_Side_L = "Parte sinistra",
IGUI_perks_Prosthesis = "Protesi",
IGUI_perks_ProstFamiliarity= "Familiarità",
IGUI_ItemCat_Prosthesis = "Protesi",
IGUI_ItemCat_Surgery = "Operazioni mediche",
IGUI_ItemCat_Amputation = "Amputazione"
IGUI_HealthPanel_Cicatrization = "Cicatrizzazione",
IGUI_HealthPanel_Cicatrized = "Cicatrizzata",
IGUI_HealthPanel_Cauterized = "Cauterizzata",
IGUI_HealthPanel_WoundDirtyness = "Sporcizia della ferita",
IGUI_HealthPanel_ProstEquipped = "Protesi installata",
}

View File

@@ -0,0 +1,11 @@
ItemName_IT = {
ItemName_TOC.Surg_Arm_Tourniquet_L = "Laccio emostatico",
ItemName_TOC.Surg_Arm_Tourniquet_R = "Laccio emostatico",
ItemName_TOC.Prost_NormalArm_L = "Braccio Prostetico",
ItemName_TOC.Prost_NormalArm_R = "Braccio Prostetico",
ItemName_TOC.Prost_HookArm_L = "Braccio prostetico - Uncino",
ItemName_TOC.Prost_HookArm_R = "Braccio prostetico - Uncino",
}

View File

@@ -0,0 +1,4 @@
Recipes_IT = {
Recipe_Craft_Prosthetic_Arm = "Costruisci un braccio prostetico",
Recipe_Craft_Prosthetic_Hook = "Costruisci un braccio prostetico con uncino",
}

View File

@@ -0,0 +1,7 @@
Sandbox_IT = {
Sandbox_TOC = "The Only Cure",
Sandbox_TOC_CicatrizationSpeed = "Velocità cicatrizzazione",
Sandbox_TOC_WoundDirtynessMultiplier = "Moltiplicatore sporcizia ferita",
Sandbox_TOC_SurgeonAbilityImportance = "Importanza abilità medico",
}

View File

@@ -0,0 +1,10 @@
Tooltip_IT = {
Tooltip_Surgery_CantCauterize = "Non puoi cauterizzare la ferita",
Tooltip_Surgery_And = " e "
Tooltip_Surgery_TempTooLow = "La temperatura è troppo bassa",
Tooltip_Surgery_Coward = "Non sei abbastanza coraggioso",
Tooltip_Surgery_LimbNotFree = "Devi rimuovere la protesi",
}

View File

@@ -0,0 +1,16 @@
UI_IT = {
UI_trait_Amputee_Hand = "Mano Sinistra Amputata",
UI_trait_Amputee_Hand_desc = "",
UI_trait_Amputee_ForeArm = "Avambraccio Sinistro Amputato",
UI_trait_Amputee_ForeArm_desc = "",
UI_trait_Amputee_UpperArm = "Parte superiore del Braccio Sinistro Amputato",
UI_trait_Amputee_UpperArm_desc = "",
UI_trait_Insensitive = "Insensibile",
UI_trait_Insensitive_desc = "",
UI_Say_CantEquip = "Non posso equipaggiarlo..."
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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