Compare commits
403 Commits
v1.0
...
dev-fix-se
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
839ce002a3 | ||
|
|
49eee7ce84 | ||
|
|
1c72647d40 | ||
|
|
a9a7063287 | ||
|
|
a511ac777a | ||
|
|
04c7172d82 | ||
|
|
522c49b40c | ||
|
|
8c8aa8351b | ||
|
|
3eeb9d1000 | ||
|
|
c60a2c56ec | ||
|
|
69025c8262 | ||
|
|
80e9391db5 | ||
|
|
d7bdee1d26 | ||
|
|
8234abd5e2 | ||
|
|
9011579f08 | ||
|
|
be6466976c | ||
|
|
9ab584d977 | ||
|
|
b0d3520173 | ||
|
|
bbd36f7dc2 | ||
|
|
3fc37f56d6 | ||
|
|
d5fd735de8 | ||
|
|
253c5717a9 | ||
|
|
d850691053 | ||
|
|
f728520f9e | ||
|
|
67f51ca845 | ||
|
|
e07af54d27 | ||
|
|
6e47f945c5 | ||
|
|
644376cea0 | ||
|
|
6a3fa76e00 | ||
|
|
6e674959ab | ||
|
|
d52f3f6bf2 | ||
|
|
5acab111f2 | ||
|
|
61f505fa8e | ||
|
|
6abd89cea7 | ||
|
|
768a0dbdab | ||
|
|
d96c26d099 | ||
|
|
f93c0503f8 | ||
|
|
671bf133e3 | ||
|
|
1e754895a0 | ||
|
|
9a11047e3c | ||
|
|
db6f315f89 | ||
|
|
729e3b62e7 | ||
|
|
7c8cfb0fcc | ||
|
|
bb19da2b4b | ||
|
|
471608f9ba | ||
|
|
71b854efe2 | ||
|
|
e919c8c01b | ||
|
|
b6b61b872f | ||
|
|
58d5c8e13d | ||
|
|
cc5e67aceb | ||
|
|
f77a357dab | ||
|
|
499db8cd78 | ||
|
|
ffce5fac2e | ||
|
|
81842f7020 | ||
|
|
d846b853ff | ||
|
|
0f4117cd34 | ||
|
|
736d527a13 | ||
|
|
1ded7f976c | ||
|
|
6635cc19b2 | ||
|
|
5d8e60a2e4 | ||
|
|
5515b6bd4f | ||
|
|
ba267cb900 | ||
|
|
90ca2edbce | ||
|
|
00d5d83ea2 | ||
|
|
849c17051a | ||
|
|
0071d2ac3f | ||
|
|
68605ddb35 | ||
|
|
371134faa7 | ||
|
|
132ce24328 | ||
|
|
4af127196c | ||
|
|
39bccaf895 | ||
|
|
54c5958715 | ||
|
|
6b79ebf003 | ||
|
|
75d5cb180d | ||
|
|
245f81be26 | ||
|
|
1f950e0663 | ||
|
|
aecb5448ea | ||
|
|
c02ac7dacc | ||
|
|
332e02d9fd | ||
|
|
a2b3db6793 | ||
|
|
6883c81322 | ||
|
|
600c55f5f8 | ||
|
|
bf20aec94f | ||
|
|
26c269e8ba | ||
|
|
db42feed35 | ||
|
|
10f97cffa1 | ||
|
|
22e6621300 | ||
|
|
4d2b62e4a1 | ||
|
|
dd0d48cd08 | ||
|
|
2de512b447 | ||
|
|
1765d82ad9 | ||
|
|
c52cca09f1 | ||
|
|
545d5c7573 | ||
|
|
99d0847caa | ||
|
|
3b7fe2ec58 | ||
|
|
92fc80095d | ||
|
|
751e36cba7 | ||
|
|
487e616801 | ||
|
|
d5915cd287 | ||
|
|
d1f63ab55f | ||
|
|
857515576e | ||
|
|
69c6177be5 | ||
|
|
38fa63324c | ||
|
|
940f5486aa | ||
|
|
11622795e1 | ||
|
|
cb7603feea | ||
|
|
7b5e8e15d5 | ||
|
|
9a74710c1e | ||
|
|
3f7bf56be2 | ||
|
|
501c2fbabd | ||
|
|
3dad820014 | ||
|
|
2f82021b5d | ||
|
|
a3287bb2bc | ||
|
|
6c82f1a96f | ||
|
|
f2446c0781 | ||
|
|
4d6ee68054 | ||
|
|
854238ef98 | ||
|
|
2b41745a05 | ||
|
|
c2e15151a6 | ||
|
|
b25f52a40d | ||
|
|
145ec33620 | ||
|
|
307e19fd52 | ||
|
|
9eb9ea0263 | ||
|
|
f8fc8e7f60 | ||
|
|
3d1cfba85c | ||
|
|
1f6fc63f46 | ||
|
|
3931c0ed4d | ||
|
|
74eb7763cf | ||
|
|
e0e65c022f | ||
|
|
574526aca0 | ||
|
|
29f21decb4 | ||
|
|
b70dd619a4 | ||
|
|
0d5814dad1 | ||
|
|
5f64ead354 | ||
|
|
80c8b22faa | ||
|
|
696edc1f1d | ||
|
|
a1336c8d2e | ||
|
|
1187f1a1b6 | ||
|
|
dc921e4c04 | ||
|
|
bff6a2d4d6 | ||
|
|
36c3418a3e | ||
|
|
a7e32bc69b | ||
|
|
139ad5828a | ||
|
|
1636ca7e13 | ||
|
|
46a8ddc207 | ||
|
|
26e0324f4a | ||
|
|
c1faeeaabf | ||
|
|
a93c8a56bb | ||
|
|
b33cee7271 | ||
|
|
6e68e12b60 | ||
|
|
013f852e7e | ||
|
|
99dbaea143 | ||
|
|
a09a1520a1 | ||
|
|
a7a064119d | ||
|
|
6458988cc8 | ||
|
|
9b1876b235 | ||
|
|
0d9ee4203c | ||
|
|
2ea03601f5 | ||
|
|
3d4a54418c | ||
|
|
ba4f161122 | ||
|
|
17718cbbca | ||
|
|
f9ad597d2b | ||
|
|
c8b6a8c5ed | ||
|
|
df8591b1ef | ||
|
|
9478e0faa1 | ||
|
|
91a6eb7763 | ||
|
|
d512f0ba81 | ||
|
|
31995965f9 | ||
|
|
d35840d825 | ||
|
|
e270b4f73b | ||
|
|
1a754f4012 | ||
|
|
4757d9dfa8 | ||
|
|
74bb34bbc8 | ||
|
|
1b235ebaa4 | ||
|
|
44d486dfeb | ||
|
|
5cc982188a | ||
|
|
aadbe02df4 | ||
|
|
20bed84910 | ||
|
|
fc3113f243 | ||
|
|
be368738ba | ||
|
|
4209f690a8 | ||
|
|
a3a2614124 | ||
|
|
50f6db9344 | ||
|
|
17d554d269 | ||
|
|
1bef713de5 | ||
|
|
beee6f409c | ||
|
|
540f510eb0 | ||
|
|
92334b2f54 | ||
|
|
515f51d84a | ||
|
|
51dbf89ad6 | ||
|
|
631bfea3da | ||
|
|
9d3298ea25 | ||
|
|
d83fd91d3f | ||
|
|
89ff76ac8a | ||
|
|
9a22e816f0 | ||
|
|
5dc328ebc2 | ||
|
|
b1adefe8f3 | ||
|
|
ff8032f26a | ||
|
|
fa1d86ae7e | ||
|
|
c621a56f67 | ||
|
|
5b4fb85db6 | ||
|
|
0113c8272e | ||
|
|
2a2d311ac1 | ||
|
|
a7de7a7315 | ||
|
|
551125bb50 | ||
|
|
abc1da7095 | ||
|
|
37c9415c2a | ||
|
|
42cf719de9 | ||
|
|
7b07b7954a | ||
|
|
074950ebf6 | ||
|
|
7e505285c6 | ||
|
|
d341b4cfbd | ||
|
|
551589e461 | ||
|
|
5144491e81 | ||
|
|
bd8e4d6c86 | ||
|
|
c4529d0890 | ||
|
|
005d9e863e | ||
|
|
5c03cea8c4 | ||
|
|
d73b1a3f43 | ||
|
|
883a84412b | ||
|
|
3a3d58e8a0 | ||
|
|
1e72bac223 | ||
|
|
d771bed2db | ||
|
|
f64a76a128 | ||
|
|
b1f12e55a1 | ||
|
|
a8956df057 | ||
|
|
eb3dac0821 | ||
|
|
f63f0420e2 | ||
|
|
4dd5483f35 | ||
|
|
f72fe35e03 | ||
|
|
e35b91f142 | ||
|
|
43b366b467 | ||
|
|
b6fb136098 | ||
|
|
639841cf20 | ||
|
|
93a00d1b48 | ||
|
|
00b1b78cb3 | ||
|
|
feab41ac5c | ||
|
|
35ef646402 | ||
|
|
cc3ee9495c | ||
|
|
1c990f3b9b | ||
|
|
0e911ec860 | ||
|
|
ec44e4a29e | ||
|
|
7a7e90dd7d | ||
|
|
3fb22a9b1e | ||
|
|
5b524e5a4f | ||
|
|
e055b49c10 | ||
|
|
dd91ca3c7a | ||
|
|
41b6bfd456 | ||
|
|
6e9e522731 | ||
|
|
509f03f2ef | ||
|
|
72f225f154 | ||
|
|
40fe46c1c6 | ||
|
|
6c5276aff2 | ||
|
|
95f1aa304e | ||
|
|
d9c315401c | ||
|
|
afaceac86e | ||
|
|
5180a2cbfb | ||
|
|
f38d68a35a | ||
|
|
3be619c329 | ||
|
|
b9ed37f3ba | ||
|
|
15b1d070bf | ||
|
|
1028cef704 | ||
|
|
f984a834fc | ||
|
|
02c992b12a | ||
|
|
482fffbdd0 | ||
|
|
95bbedd698 | ||
|
|
81f9bbf6ea | ||
|
|
74adac6f9d | ||
|
|
c425203618 | ||
|
|
d3d0db3941 | ||
|
|
a41f976949 | ||
|
|
aeb1bca94d | ||
|
|
06e41790ca | ||
|
|
d3722c1171 | ||
|
|
c35454807d | ||
|
|
cf27a24380 | ||
|
|
1273dc7c7a | ||
|
|
1cb60dd0e8 | ||
|
|
312294edb8 | ||
|
|
6b1205e160 | ||
|
|
2cbf5aea8b | ||
|
|
4cf97a2e79 | ||
|
|
1098333247 | ||
|
|
49832092b4 | ||
|
|
f2007d56a9 | ||
|
|
4e4b771e41 | ||
|
|
2bf1631809 | ||
|
|
ba119de89b | ||
|
|
33140938c9 | ||
|
|
2c5c3885d1 | ||
|
|
96ddd3e030 | ||
|
|
809d5492e8 | ||
|
|
77cf50066a | ||
|
|
2606f664bc | ||
|
|
97b51e1bcf | ||
|
|
ecee8b06e7 | ||
|
|
d6ba1e9068 | ||
|
|
f352b40232 | ||
|
|
74508a9ed1 | ||
|
|
0d3b82aee3 | ||
|
|
8a3b0bb190 | ||
|
|
55a4f9fdb4 | ||
|
|
9a3a57a819 | ||
|
|
53eacd4717 | ||
|
|
877e5a711e | ||
|
|
bd8dae128c | ||
|
|
0d85638464 | ||
|
|
38141bc5db | ||
|
|
ebd128c710 | ||
|
|
18dc61f552 | ||
|
|
3418378139 | ||
|
|
03093f77a7 | ||
|
|
1b248d744e | ||
|
|
fe6d88fa43 | ||
|
|
43cf52cd02 | ||
|
|
18cae1de98 | ||
|
|
e7119cf032 | ||
|
|
34570cd013 | ||
|
|
c09c3ac646 | ||
|
|
16e83955f3 | ||
|
|
cda3a61d2a | ||
|
|
9a4d07900c | ||
|
|
26f7867320 | ||
|
|
f7bd45420b | ||
|
|
c4f6890dd0 | ||
|
|
f7c9047457 | ||
|
|
c9b41838be | ||
|
|
46bc6030fe | ||
|
|
6adb694077 | ||
|
|
0fdc0de27d | ||
|
|
bd9be10e02 | ||
|
|
441fe67890 | ||
|
|
a7d49c7253 | ||
|
|
65795cbe79 | ||
|
|
81be27561d | ||
|
|
150772e252 | ||
|
|
33299b0779 | ||
|
|
46175908b1 | ||
|
|
56da8583b0 | ||
|
|
4fb19ae5a6 | ||
|
|
627e480d07 | ||
|
|
6f17b428bc | ||
|
|
3bc4ce471f | ||
|
|
3d6144cfcd | ||
|
|
8fde83c049 | ||
|
|
328d3c4d3a | ||
|
|
e374601402 | ||
|
|
deb6dcc056 | ||
|
|
980545a07e | ||
|
|
30f343a7de | ||
|
|
d1aef1f67d | ||
|
|
4887699892 | ||
|
|
f973b774a2 | ||
|
|
52a4798377 | ||
|
|
3d963a2ad6 | ||
|
|
3443198edb | ||
|
|
a691a729cf | ||
|
|
5e89a05b26 | ||
|
|
74c7b99bc5 | ||
|
|
35ead001b0 | ||
|
|
a754ae70c4 | ||
|
|
78942d2e78 | ||
|
|
e8269899b0 | ||
|
|
d82fce6f2f | ||
|
|
2cf794552f | ||
|
|
9459360948 | ||
|
|
e8d4a37fdc | ||
|
|
ca42c383d1 | ||
|
|
7813d242e8 | ||
|
|
7424f45ae3 | ||
|
|
7557a13544 | ||
|
|
c8cd318f2d | ||
|
|
99ce9bacda | ||
|
|
9095d11f41 | ||
|
|
60a7f1b996 | ||
|
|
066f4f8049 | ||
|
|
74b625b287 | ||
|
|
6c84ab14b7 | ||
|
|
301603ed68 | ||
|
|
8faa5b9cc5 | ||
|
|
42bc95786b | ||
|
|
01a6d34c97 | ||
|
|
4f6910c806 | ||
|
|
835c57faaa | ||
|
|
81fc637047 | ||
|
|
a002bbbdb0 | ||
|
|
ae51634be4 | ||
|
|
4254d123bf | ||
|
|
8ec28be2b6 | ||
|
|
d0dae7dfe2 | ||
|
|
28ba7d73ab | ||
|
|
0ef3f7284f | ||
|
|
4035423590 | ||
|
|
16c510c79f | ||
|
|
f39bc5c36e | ||
|
|
fcc30e3314 | ||
|
|
807d368df4 | ||
|
|
9fe58dee87 | ||
|
|
d580bad0eb | ||
|
|
875e0fdceb | ||
|
|
d79dfcc509 | ||
|
|
57862163f1 | ||
|
|
de63163e77 |
24
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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.
|
||||||
33
.github/workflows/build_mod.yml
vendored
@@ -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
@@ -0,0 +1,2 @@
|
|||||||
|
dev_stuff/gen_amp_textures/.venv
|
||||||
|
dev_stuff/gen_amp_textures/output
|
||||||
3
.idea/.gitignore
generated
vendored
@@ -1,3 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
11
.idea/The-Only-Cure-But-Better.iml
generated
@@ -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
@@ -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>
|
|
||||||
9
.idea/libraries/zdoc_lua.xml
generated
@@ -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>
|
|
||||||
11
.idea/libraries/zomboid_sources.xml
generated
@@ -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
@@ -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
@@ -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>
|
|
||||||
17
.idea/runConfigurations/Run_Zomboid.xml
generated
@@ -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
@@ -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
@@ -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"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
107
.vscode/tasks.json
vendored
@@ -4,31 +4,112 @@
|
|||||||
"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 - B41"}},
|
||||||
|
"command": "python ${config:zomboid_user_folder}/PaosCrap/make_workshop_pack.py \"41\" \"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 - nosteam-debug 42.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 (41)",
|
||||||
"dependsOn": [
|
|
||||||
"Run Zomboid Debug No Steam",
|
|
||||||
"Run Zomboid Debug No Steam"
|
|
||||||
],
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Run Zomboid Test Server",
|
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "\"E:\\Steam\\steamapps\\common\\Project Zomboid Dedicated Server\\StartServer64_nosteam.bat\"",
|
"presentation": {
|
||||||
|
"group": "groupZomboid"
|
||||||
|
},
|
||||||
|
"command": "\"${config:zomboid_folder_b41}\\ProjectZomboid64 - nosteam-debug.bat\"",
|
||||||
|
"options": {"statusbar": {"label": "$(run) Zomboid client (41)"}},
|
||||||
"problemMatcher": [
|
"problemMatcher": [
|
||||||
"$eslint-stylish"
|
"$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",
|
||||||
|
// "options": {"statusbar": {"label": "$(run) Zomboid Server (TOC)"}},
|
||||||
|
// "type": "shell",
|
||||||
|
// "command":"\"${config:zomboid_server_folder}\\StartServer64_nosteam_custom.bat\" TOC",
|
||||||
|
// "problemMatcher": [
|
||||||
|
// "$eslint-stylish"
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "label": "Run Zomboid Test Server 2",
|
||||||
|
// "options": {"statusbar": {"label": "$(run) Zomboid Server (TOC+FH+BH)"}},
|
||||||
|
// "type": "shell",
|
||||||
|
// "command":"\"${config:zomboid_server_folder}\\StartServer64_nosteam_custom.bat\" TOC_FH_BH",
|
||||||
|
// "problemMatcher": [
|
||||||
|
// "$eslint-stylish"
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "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"
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
local LimitActionsController = require("TOC/Controllers/LimitActionsController") -- declared in common
|
||||||
|
local OverridenMethodsArchive = require("TOC/OverridenMethodsArchive")
|
||||||
|
|
||||||
|
local og_ISClothingExtraAction_isValid = OverridenMethodsArchive.Get("ISClothingExtraAction_isValid")
|
||||||
|
---@diagnostic disable-next-line: duplicate-set-field
|
||||||
|
function ISClothingExtraAction:isValid()
|
||||||
|
-- TOC_DEBUG.print("Inside ISClothingExtraAction:isValid 42")
|
||||||
|
-- TOC_DEBUG.print(OverridenMethodsArchive.Get("ISClothingExtraAction_isValid"))
|
||||||
|
return LimitActionsController.WrapClothingAction(self, og_ISClothingExtraAction_isValid, instanceItem(self.extra))
|
||||||
|
end
|
||||||
40
42/media/lua/client/TOC-42/Handlers/ProsthesisHandler.lua
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
local ProsthesisHandler = require("TOC/Handlers/ProsthesisHandler") -- declared in common
|
||||||
|
local OverridenMethodsArchive = require("TOC/OverridenMethodsArchive")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local og_ISClothingExtraAction_isValid = OverridenMethodsArchive.Get("ISClothingExtraAction_isValid")
|
||||||
|
---@diagnostic disable-next-line: duplicate-set-field
|
||||||
|
function ISClothingExtraAction:isValid()
|
||||||
|
local isEquippable = og_ISClothingExtraAction_isValid(self)
|
||||||
|
-- self.extra is a string, not the item
|
||||||
|
local testItem = instanceItem(self.extra)
|
||||||
|
return ProsthesisHandler.Validate(testItem, isEquippable)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local og_ISClothingExtraAction_perform = OverridenMethodsArchive.Get("ISClothingExtraAction_perform")
|
||||||
|
function ISClothingExtraAction:perform()
|
||||||
|
local extraItem = instanceItem(self.extra)
|
||||||
|
ProsthesisHandler.SearchAndSetupProsthesis(extraItem, true)
|
||||||
|
og_ISClothingExtraAction_perform(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local og_ISUnequipAction_complete = ISUnequipAction.complete
|
||||||
|
function ISUnequipAction:complete()
|
||||||
|
-- Horrendous workaround. For B42, as of now, it will basically happen two times, once with :perform and once with :complete. Shouldn't
|
||||||
|
-- matter for performance but it's really ugly.
|
||||||
|
local isProst = ProsthesisHandler.SearchAndSetupProsthesis(self.item, false)
|
||||||
|
local group
|
||||||
|
if isProst then
|
||||||
|
group = BodyLocations.getGroup("Human")
|
||||||
|
group:setMultiItem("TOC_ArmProst", false)
|
||||||
|
end
|
||||||
|
og_ISUnequipAction_complete(self)
|
||||||
|
|
||||||
|
if isProst then
|
||||||
|
group:setMultiItem("TOC_ArmProst", true)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
BIN
42/media/ui/Female/ForeArm_L.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
42/media/ui/Female/ForeArm_R.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
42/media/ui/Female/Hand_L.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
42/media/ui/Female/Hand_R.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
42/media/ui/Female/UpperArm_L.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
42/media/ui/Female/UpperArm_R.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
42/media/ui/Male/ForeArm_L.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
42/media/ui/Male/ForeArm_R.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
42/media/ui/Male/Hand_L.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
42/media/ui/Male/Hand_R.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
42/media/ui/Male/UpperArm_L.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
42/media/ui/Male/UpperArm_R.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
42/media/ui/test_pattern.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
11
42/mod.info
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
name=The Only Cure
|
||||||
|
poster=poster.png
|
||||||
|
description=You've been bitten. You have only two choices.
|
||||||
|
id=TheOnlyCure
|
||||||
|
icon=icon.png
|
||||||
|
url=https://github.com/ZioPao/The-Only-Cure
|
||||||
|
modversion=2.2
|
||||||
|
versionMin=42.6
|
||||||
|
|
||||||
|
loadModAfter=\FancyHandwork,\BrutalHandwork,\TwoWeaponsOnBackRework
|
||||||
|
incompatible=\BB_FirstAidOverhaul
|
||||||
674
LICENSE
Normal file
@@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
65
README.md
@@ -1,4 +1,61 @@
|
|||||||
# Dev Version
|
<p align='center'>
|
||||||
Version: 1.0
|
<img src="/dev_stuff/logos/title.png" width=50% height=50%>
|
||||||
Workshop ID: 2915572347
|
</p>
|
||||||
Mod ID: Amputation2
|
|
||||||
|
<p align='center'>
|
||||||
|
<a href='https://steamcommunity.com/sharedfiles/filedetails/?id=3236152598'>
|
||||||
|
<img src='https://img.shields.io/badge/Steam-000000?style=for-the-badge&logo=steam&logoColor=white' />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
You're bitten. You have two choices.
|
||||||
|
Wait until you succumb to the virus or take matters into your hands. Cut off that infected part and live to die another day.
|
||||||
|
|
||||||
|
This version of **The Only Cure** has been rebuilt from scratch to support future additions and to feel as close as possible as a vanilla mechanic.
|
||||||
|
|
||||||
|
Supports **Single Player** and **Multiplayer**!
|
||||||
|
|
||||||
|
# Setup
|
||||||
|
Use it with the following mods for the intended experience:
|
||||||
|
- [Fancy Handwork](https://steamcommunity.com/sharedfiles/filedetails/?id=2904920097)
|
||||||
|
- [Brutal Handwork](https://steamcommunity.com/sharedfiles/filedetails/?id=2934621024)
|
||||||
|
|
||||||
|
|
||||||
|
# Quick guide
|
||||||
|
|
||||||
|
## Amputation
|
||||||
|
Get a _Saw_ or a _Garden Saw_, right click on it, and choose which limb to amputate. You can also drag n' drop your Saw item directly into the afflicted area to start cutting it off.
|
||||||
|
|
||||||
|
If you have some _bandages_ and\or _stitches_ in your inventory you will automatically use them, multiplying the chances of your survival.
|
||||||
|
If you have a _tourniquet_, place it on the correct side to dampen the amount of damage you will take after you're done amputating the limb.
|
||||||
|
|
||||||
|
Keep in mind that if you amputate your **upper arm**, you won't be able to equip any prosthesis.
|
||||||
|
|
||||||
|
After you've amputated a limb, you will gain skill points for the amputated side, making timed actions faster in due time.
|
||||||
|
|
||||||
|
## Cicatrization
|
||||||
|
You can check the cicatrization status of an amputated limb from your **Health Panel**.
|
||||||
|
From time to time, you should clean your wounds with a bandage to help the cicatrization process.
|
||||||
|
If your limb isn't completely cicatrized, you can still equip prosthetic limbs, but that can trigger random bleedings from that area.
|
||||||
|
|
||||||
|
## Prosthesis
|
||||||
|
If you're missing a hand, you won't be able to do a lot of things, such as equipping two-handed weapons. With prosthetics limbs, you can fix that.
|
||||||
|
There are two prosthesis type that can be crafted\found.
|
||||||
|
- Hook Prosthesis
|
||||||
|
- Arm Prosthesis
|
||||||
|
|
||||||
|
The main difference between the twos is that your actions will take longer with the Hook Prosthesis.
|
||||||
|
|
||||||
|
When you equip a prosthetic limb, you will slowly gain familiarity with it, making actions more speedy in due time.
|
||||||
|
|
||||||
|
## Admin tools
|
||||||
|
If something strange happened, an admin can reset TOC mechanics on any player by right clicking on them and select _"Reset Amputations"_. They could also do the opposite by clicking on _"Force Amputation"_ for each amputable limb.
|
||||||
|
|
||||||
|
# Credits
|
||||||
|
| | |
|
||||||
|
| ------------- | ------------- |
|
||||||
|
| Pao | Developer |
|
||||||
|
| Mr. Bounty | Original developer |
|
||||||
|
| Chuckleberry Finn | Logo and Icon |
|
||||||
|
| dhert | Compatibility API |
|
||||||
|
| The Zomboid community as a whole | A lot of little things |
|
||||||
|
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
0.9.15
|
|
||||||
- Hotfix to sound not stopping when performing an amputation on another player
|
|
||||||
|
|
||||||
0.9.14
|
|
||||||
- Optimizations
|
|
||||||
- New icon for the mod
|
|
||||||
- Added a dirtyness malus for cicatrization, you'll have to keep your wound clean while it's healing (wash it you animal)
|
|
||||||
- When trying to equip a prosthesis from the inventory you'll get a message to let you know that you're doing it wrong
|
|
||||||
- Added explicit support to Left Is Right, making it work a lot better when you've got an amputated right limb
|
|
||||||
|
|
||||||
|
|
||||||
0.9.13
|
|
||||||
- Some little optimizations
|
|
||||||
- Fixed a bug that made Fancy Handwork compat not work as intended
|
|
||||||
|
|
||||||
0.9.12
|
|
||||||
- Fixed traits icons
|
|
||||||
- Some cleaning to the code
|
|
||||||
|
|
||||||
0.9.11
|
|
||||||
- Hotfix to cicatrization visuals
|
|
||||||
|
|
||||||
0.9.10
|
|
||||||
- Modified textures once again
|
|
||||||
|
|
||||||
|
|
||||||
0.9.9
|
|
||||||
- Modified textures for amputations
|
|
||||||
- Fixed a bug that caused some problems when amputating an already amputated limb
|
|
||||||
|
|
||||||
0.9.8
|
|
||||||
- New textures for amputations
|
|
||||||
- When cicatrized, an amputation will not be bloody anymore (visual thing only)
|
|
||||||
|
|
||||||
0.9.7
|
|
||||||
- Now amputations should spawn in every case, damn hairs. Thanks Dev for the tip!
|
|
||||||
|
|
||||||
|
|
||||||
0.9.6
|
|
||||||
- Fixed a bug when handling zombie hitting amputated limbs
|
|
||||||
- Fixed sound looping incessantly after amputating a limb
|
|
||||||
- Changed how phantom pain occurs
|
|
||||||
- Fixes to new hook models
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
0.9.5
|
|
||||||
- No more Right Click menu. You'll have to use the TOC Menu from the Medical Check, except for Cheats
|
|
||||||
- Fixes to how amputations are handled
|
|
||||||
before and after
|
|
||||||
- Compatibility fixes with older versions
|
|
||||||
- Various fixes to Traits
|
|
||||||
- New heavily WIP models for metal and wooden hooks
|
|
||||||
- Readded sounds during amputations
|
|
||||||
- Fixed banages not getting applied after an amputation when they were in the player's inventory
|
|
||||||
63
common/media/clothing/clothingItems/Amputation_ForeArm_L.xml
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<clothingItem>
|
||||||
|
<m_MaleModel>Amputation\Amputation_FA_LR</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_UnderlayMasksFolder>media/textures/Amputations/Masks/TestDouble</m_UnderlayMasksFolder>
|
||||||
|
|
||||||
|
<m_Masks>4</m_Masks>
|
||||||
|
<m_Masks>6</m_Masks>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- HUMAN -->
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin01_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin02_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin03_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin04_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin05_b</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin01_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin02_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin03_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin04_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin05_hairy_b</textureChoices>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- HUMAN AFTER CICATRIZATION -->
|
||||||
|
|
||||||
|
<textureChoices>Body\MaleBody01</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody02</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody03</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody04</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody05</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Body\MaleBody01a</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody02a</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody03a</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody04</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody05a</textureChoices>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- ZOMBIE -->
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin01_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin01_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin01_l3</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin02_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin02_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin02_l3</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin03_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin03_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin03_l3</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin04_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin04_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin04_l3</textureChoices>
|
||||||
|
|
||||||
|
</clothingItem>
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<clothingItem>
|
||||||
|
<m_MaleModel>Amputation\Amputation_FA_LR</m_MaleModel>
|
||||||
|
<m_FemaleModel>Amputation\Amputation_FA_LR</m_FemaleModel>
|
||||||
|
<m_GUID>8e5bc1b4-d79b-4a17-b86b-71dab6ace816</m_GUID>
|
||||||
|
<m_Static>false</m_Static>
|
||||||
|
<m_AllowRandomHue>false</m_AllowRandomHue>
|
||||||
|
<m_AllowRandomTint>false</m_AllowRandomTint>
|
||||||
|
|
||||||
|
<m_Masks>4</m_Masks>
|
||||||
|
<m_Masks>6</m_Masks>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- HUMAN -->
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin01_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin02_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin03_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin04_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin05_b</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin01_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin02_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin03_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin04_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin05_hairy_b</textureChoices>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- HUMAN AFTER CICATRIZATION -->
|
||||||
|
|
||||||
|
<textureChoices>Body\MaleBody01</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody02</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody03</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody04</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody05</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Body\MaleBody01a</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody02a</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody03a</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody04</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody05a</textureChoices>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- ZOMBIE -->
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin01_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin01_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin01_l3</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin02_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin02_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin02_l3</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin03_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin03_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin03_l3</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin04_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin04_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin04_l3</textureChoices>
|
||||||
|
|
||||||
|
</clothingItem>
|
||||||
58
common/media/clothing/clothingItems/Amputation_ForeArm_R.xml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<clothingItem>
|
||||||
|
<m_MaleModel>Amputation\Amputation_Right_LowerArm_Male</m_MaleModel>
|
||||||
|
<m_FemaleModel>Amputation\Amputation_Right_LowerArm_Female</m_FemaleModel>
|
||||||
|
<m_GUID>e6f80efd-22e5-49e0-8b24-537519d42b37</m_GUID>
|
||||||
|
<m_Static>false</m_Static>
|
||||||
|
<m_AllowRandomHue>false</m_AllowRandomHue>
|
||||||
|
<m_AllowRandomTint>false</m_AllowRandomTint>
|
||||||
|
|
||||||
|
<m_Masks>5</m_Masks>
|
||||||
|
<m_Masks>6</m_Masks>
|
||||||
|
|
||||||
|
<!-- HUMAN -->
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin01_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin02_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin03_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin04_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin05_b</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin01_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin02_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin03_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin04_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin05_hairy_b</textureChoices>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- HUMAN AFTER CICATRIZATION -->
|
||||||
|
|
||||||
|
<textureChoices>Body\MaleBody01</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody02</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody03</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody04</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody05</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Body\MaleBody01a</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody02a</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody03a</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody04</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody05a</textureChoices>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- ZOMBIE -->
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin01_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin01_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin01_l3</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin02_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin02_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin02_l3</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin03_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin03_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin03_l3</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin04_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin04_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Forearm\z_skin04_l3</textureChoices>
|
||||||
|
</clothingItem>
|
||||||
24
common/media/clothing/clothingItems/Amputation_Hand_L.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<clothingItem>
|
||||||
|
<m_MaleModel>Amputation\Amputation_Left_Hand_Male</m_MaleModel>
|
||||||
|
<m_FemaleModel>Amputation\Amputation_Left_Hand_Female</m_FemaleModel>
|
||||||
|
<m_GUID>2de93af2-b7a8-4c04-84d1-28d92cce8a0f</m_GUID>
|
||||||
|
<m_Static>false</m_Static>
|
||||||
|
<m_AllowRandomHue>false</m_AllowRandomHue>
|
||||||
|
<m_AllowRandomTint>false</m_AllowRandomTint>
|
||||||
|
<m_Masks>4</m_Masks>
|
||||||
|
<m_MasksFolder>none</m_MasksFolder>
|
||||||
|
|
||||||
|
<!-- HUMAN -->
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin01_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin02_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin03_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin04_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin05_b</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin01_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin02_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin03_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin04_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin05_hairy_b</textureChoices>
|
||||||
|
</clothingItem>
|
||||||
23
common/media/clothing/clothingItems/Amputation_Hand_R.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<clothingItem>
|
||||||
|
<m_MaleModel>Amputation\Amputation_Right_Hand_Male</m_MaleModel>
|
||||||
|
<m_FemaleModel>Amputation\Amputation_Right_Hand_Female</m_FemaleModel>
|
||||||
|
<m_GUID>f114e53a-b92e-4639-8d8c-2b43ab981885</m_GUID>
|
||||||
|
<m_Static>false</m_Static>
|
||||||
|
<m_AllowRandomHue>false</m_AllowRandomHue>
|
||||||
|
<m_AllowRandomTint>false</m_AllowRandomTint>
|
||||||
|
<m_Masks>6</m_Masks>
|
||||||
|
<m_MasksFolder>none</m_MasksFolder>
|
||||||
|
|
||||||
|
<!-- HUMAN -->
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin01_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin02_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin03_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin04_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin05_b</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin01_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin02_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin03_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin04_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Forearm\skin05_hairy_b</textureChoices>
|
||||||
|
</clothingItem>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<clothingItem>
|
||||||
|
<m_MaleModel>Amputation\Amputation_Left_UpperArm_Male</m_MaleModel>
|
||||||
|
<m_FemaleModel>Amputation\Amputation_Left_UpperArm_Female</m_FemaleModel>
|
||||||
|
<m_GUID>646cafa5-3fa1-41af-9ca0-aa57cca3b36d</m_GUID>
|
||||||
|
<m_Static>false</m_Static>
|
||||||
|
<m_AllowRandomHue>false</m_AllowRandomHue>
|
||||||
|
<m_AllowRandomTint>false</m_AllowRandomTint>
|
||||||
|
<m_Masks>3</m_Masks>
|
||||||
|
<m_Masks>4</m_Masks>
|
||||||
|
|
||||||
|
<!-- HUMAN -->
|
||||||
|
<textureChoices>Amputations\Human\Upperarm\skin01_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Upperarm\skin02_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Upperarm\skin03_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Upperarm\skin04_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Upperarm\skin05_b</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Human\Upperarm\skin01_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Upperarm\skin02_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Upperarm\skin03_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Upperarm\skin04_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Upperarm\skin05_hairy_b</textureChoices>
|
||||||
|
|
||||||
|
<!-- HUMAN AFTER CICATRIZATION -->
|
||||||
|
|
||||||
|
<textureChoices>Body\MaleBody01</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody02</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody03</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody04</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody05</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Body\MaleBody01a</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody02a</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody03a</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody04</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody05a</textureChoices>
|
||||||
|
|
||||||
|
<!-- ZOMBIE -->
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin01_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin01_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin01_l3</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin02_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin02_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin02_l3</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin03_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin03_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin03_l3</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin04_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin04_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin04_l3</textureChoices>
|
||||||
|
|
||||||
|
</clothingItem>
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<clothingItem>
|
||||||
|
<m_MaleModel>Amputation\Amputation_Right_UpperArm_Male</m_MaleModel>
|
||||||
|
<m_FemaleModel>Amputation\Amputation_Right_UpperArm_Female</m_FemaleModel>
|
||||||
|
<m_GUID>db8ccad2-b76f-44bd-93ab-1eefa25beade</m_GUID>
|
||||||
|
<m_Static>false</m_Static>
|
||||||
|
<m_AllowRandomHue>false</m_AllowRandomHue>
|
||||||
|
<m_AllowRandomTint>false</m_AllowRandomTint>
|
||||||
|
<m_Masks>5</m_Masks>
|
||||||
|
<m_Masks>6</m_Masks>
|
||||||
|
|
||||||
|
<!-- HUMAN -->
|
||||||
|
<textureChoices>Amputations\Human\Upperarm\skin01_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Upperarm\skin02_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Upperarm\skin03_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Upperarm\skin04_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Upperarm\skin05_b</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Human\Upperarm1\skin01_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Upperarm1\skin02_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Upperarm1\skin03_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Upperarm1\skin04_hairy_b</textureChoices>
|
||||||
|
<textureChoices>Amputations\Human\Upperarm1\skin05_hairy_b</textureChoices>
|
||||||
|
|
||||||
|
<!-- HUMAN AFTER CICATRIZATION -->
|
||||||
|
|
||||||
|
<textureChoices>Body\MaleBody01</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody02</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody03</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody04</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody05</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Body\MaleBody01a</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody02a</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody03a</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody04</textureChoices>
|
||||||
|
<textureChoices>Body\MaleBody05a</textureChoices>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- ZOMBIE -->
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin01_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin01_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin01_l3</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin02_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin02_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin02_l3</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin03_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin03_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin03_l3</textureChoices>
|
||||||
|
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin04_l1</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin04_l2</textureChoices>
|
||||||
|
<textureChoices>Amputations\Zombie\Upperarm\z_skin04_l3</textureChoices>
|
||||||
|
|
||||||
|
</clothingItem>
|
||||||
10
common/media/clothing/clothingItems/Prost_HookArm_L.xml
Normal 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>
|
||||||
10
common/media/clothing/clothingItems/Prost_HookArm_R.xml
Normal 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>
|
||||||
10
common/media/clothing/clothingItems/Prost_NormalArm_L.xml
Normal 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>
|
||||||
10
common/media/clothing/clothingItems/Prost_NormalArm_R.xml
Normal 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>
|
||||||
67
common/media/fileGuidTable.xml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?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>
|
||||||
|
|
||||||
|
<!-- TEST -->
|
||||||
|
<files>
|
||||||
|
<path>media/clothing/clothingItems/Amputation_ForeArm_LR.xml</path>
|
||||||
|
<guid>8e5bc1b4-d79b-4a17-b86b-71dab6ace816</guid>
|
||||||
|
</files>
|
||||||
|
|
||||||
|
|
||||||
|
</fileGuidTable>
|
||||||
50
common/media/lua/client/TOC/API.lua
Normal 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
|
||||||
114
common/media/lua/client/TOC/Admin.lua
Normal 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
|
||||||
77
common/media/lua/client/TOC/ClientRelayCommands.lua
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
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)
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
--* 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
|
||||||
48
common/media/lua/client/TOC/CommonMethods.lua
Normal 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
|
||||||
79
common/media/lua/client/TOC/Compat.lua
Normal 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
|
||||||
480
common/media/lua/client/TOC/Controllers/DataController.lua
Normal 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
|
||||||
182
common/media/lua/client/TOC/Controllers/ItemsController.lua
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
local StaticData = require("TOC/StaticData")
|
||||||
|
local CommonMethods = require("TOC/CommonMethods")
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
--- Submodule to handle spawning the correct items after certain actions (ie: cutting a hand). LOCAL ONLY!
|
||||||
|
---@class ItemsController
|
||||||
|
local ItemsController = {}
|
||||||
|
|
||||||
|
|
||||||
|
--* Player Methods *--
|
||||||
|
---@class ItemsController.Player
|
||||||
|
ItemsController.Player = {}
|
||||||
|
|
||||||
|
---Returns the correct index for the textures of the amputation
|
||||||
|
---@param playerObj IsoPlayer
|
||||||
|
---@param isCicatrized boolean
|
||||||
|
---@return number
|
||||||
|
---@private
|
||||||
|
function ItemsController.Player.GetAmputationTexturesIndex(playerObj, isCicatrized)
|
||||||
|
-- todo
|
||||||
|
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
|
||||||
|
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
|
||||||
|
-- Reset model just in case
|
||||||
|
playerObj:resetModel()
|
||||||
|
|
||||||
|
-- group:setMultiItem("TOC_Arm", true)
|
||||||
|
-- group:setMultiItem("TOC_ArmProst", true)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Spawns and equips the correct amputation item to the player.
|
||||||
|
---@param playerObj IsoPlayer
|
||||||
|
---@param limbName string
|
||||||
|
function ItemsController.Player.SpawnAmputationItem(playerObj, limbName)
|
||||||
|
TOC_DEBUG.print("clothing name " .. StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limbName)
|
||||||
|
local clothingItem = playerObj:getInventory():AddItem(StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limbName)
|
||||||
|
local texId = ItemsController.Player.GetAmputationTexturesIndex(playerObj, false)
|
||||||
|
|
||||||
|
---@cast clothingItem InventoryItem
|
||||||
|
clothingItem:getVisual():setTextureChoice(texId) -- it counts from 0, so we have to subtract 1
|
||||||
|
playerObj:setWornItem(clothingItem:getBodyLocation(), clothingItem)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Search through worn items and modifies a specific amputation item
|
||||||
|
---@param playerObj IsoPlayer
|
||||||
|
---@param limbName string
|
||||||
|
---@param isCicatrized boolean
|
||||||
|
function ItemsController.Player.OverrideAmputationItemVisuals(playerObj, limbName, isCicatrized)
|
||||||
|
local wornItems = playerObj:getWornItems()
|
||||||
|
local fullType = StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limbName
|
||||||
|
|
||||||
|
for i = 1, wornItems:size() do
|
||||||
|
local it = wornItems:get(i - 1)
|
||||||
|
if it then
|
||||||
|
local wornItem = wornItems:get(i - 1):getItem()
|
||||||
|
TOC_DEBUG.print(wornItem:getFullType())
|
||||||
|
if wornItem:getFullType() == fullType then
|
||||||
|
TOC_DEBUG.print("Found amputation item for " .. limbName)
|
||||||
|
|
||||||
|
-- change it here
|
||||||
|
local texId = ItemsController.Player.GetAmputationTexturesIndex(playerObj, isCicatrized)
|
||||||
|
wornItem:getVisual():setTextureChoice(texId)
|
||||||
|
playerObj:resetModelNextFrame() -- necessary to update the model
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--* Zombie Methods *--
|
||||||
|
---@class ItemsController.Zombie
|
||||||
|
ItemsController.Zombie = {}
|
||||||
|
|
||||||
|
---Set an amputation to a zombie
|
||||||
|
---@param zombie IsoZombie
|
||||||
|
---@param amputationFullType string Full Type
|
||||||
|
function ItemsController.Zombie.SpawnAmputationItem(zombie, amputationFullType)
|
||||||
|
local texId = ItemsController.Zombie.GetAmputationTexturesIndex(zombie)
|
||||||
|
local zombieVisuals = zombie:getItemVisuals()
|
||||||
|
local itemVisual = ItemVisual:new()
|
||||||
|
itemVisual:setItemType(amputationFullType)
|
||||||
|
itemVisual:setTextureChoice(texId)
|
||||||
|
if zombieVisuals then zombieVisuals:add(itemVisual) end
|
||||||
|
zombie:resetModelNextFrame()
|
||||||
|
|
||||||
|
-- Spawn the item too in the inventory to keep track of stuff this way. It's gonna get deleted when we reload the game
|
||||||
|
local zombieInv = zombie:getInventory()
|
||||||
|
zombieInv:AddItem(amputationFullType)
|
||||||
|
|
||||||
|
|
||||||
|
-- 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
|
||||||
@@ -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, InventoryItemFactory.CreateItem(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
|
||||||
@@ -0,0 +1,391 @@
|
|||||||
|
local DataController = require("TOC/Controllers/DataController")
|
||||||
|
local CommonMethods = require("TOC/CommonMethods")
|
||||||
|
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
|
||||||
|
local StaticData = require("TOC/StaticData")
|
||||||
|
require("TOC/Events")
|
||||||
|
-----------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- Handle ONLY stuff for the local client
|
||||||
|
|
||||||
|
---@class LocalPlayerController
|
||||||
|
---@field playerObj IsoPlayer
|
||||||
|
---@field username string
|
||||||
|
---@field hasBeenDamaged boolean
|
||||||
|
local LocalPlayerController = {}
|
||||||
|
|
||||||
|
|
||||||
|
--* Initialization
|
||||||
|
|
||||||
|
---Setup the Player Handler and modData, only for local client
|
||||||
|
---@param isForced boolean?
|
||||||
|
function LocalPlayerController.InitializePlayer(isForced)
|
||||||
|
local playerObj = getPlayer()
|
||||||
|
local username = playerObj:getUsername()
|
||||||
|
|
||||||
|
TOC_DEBUG.print("Initializing local player: " .. username)
|
||||||
|
|
||||||
|
DataController:new(username, isForced)
|
||||||
|
LocalPlayerController.playerObj = playerObj
|
||||||
|
LocalPlayerController.username = username
|
||||||
|
|
||||||
|
--Setup the CicatrizationUpdate event and triggers it once
|
||||||
|
Events.OnAmputatedLimb.Add(LocalPlayerController.ToggleUpdateAmputations)
|
||||||
|
LocalPlayerController.ToggleUpdateAmputations()
|
||||||
|
|
||||||
|
-- Since isForced is used to reset an existing player data, we're gonna clean their ISHealthPanel table too
|
||||||
|
if isForced then
|
||||||
|
local ItemsController = require("TOC/Controllers/ItemsController")
|
||||||
|
ItemsController.Player.DeleteAllOldAmputationItems(playerObj)
|
||||||
|
CachedDataHandler.Setup(username)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set a bool to use an overriding GetDamagedParts
|
||||||
|
SetHealthPanelTOC()
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---Handles the traits
|
||||||
|
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(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:setCut(false)
|
||||||
|
bodyPart:setCutTime(0)
|
||||||
|
|
||||||
|
bodyPart:setDeepWounded(false)
|
||||||
|
bodyPart:setDeepWoundTime(0)
|
||||||
|
|
||||||
|
bodyPart:setHaveBullet(false, 0)
|
||||||
|
bodyPart:setHaveGlass(false)
|
||||||
|
bodyPart:setSplint(false, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param bodyDamage BodyDamage
|
||||||
|
---@param bodyPart BodyPart
|
||||||
|
---@param limbName string
|
||||||
|
---@param dcInst DataController
|
||||||
|
function LocalPlayerController.HealZombieInfection(bodyDamage, bodyPart, limbName, dcInst)
|
||||||
|
if bodyDamage:isInfected() == false then return end
|
||||||
|
|
||||||
|
bodyDamage:setInfected(false)
|
||||||
|
bodyDamage:setInfectionMortalityDuration(-1)
|
||||||
|
bodyDamage:setInfectionTime(-1)
|
||||||
|
bodyDamage:setInfectionLevel(-1)
|
||||||
|
bodyPart:SetInfected(false)
|
||||||
|
|
||||||
|
dcInst:setIsInfected(limbName, false)
|
||||||
|
dcInst:apply()
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param character IsoPlayer
|
||||||
|
---@param limbName string
|
||||||
|
function LocalPlayerController.TryRandomBleed(character, limbName)
|
||||||
|
-- Chance should be determined by the cicatrization time
|
||||||
|
local cicTime = DataController.GetInstance():getCicatrizationTime(limbName)
|
||||||
|
if cicTime == 0 then return end
|
||||||
|
|
||||||
|
-- TODO This is just a placeholder, we need to figure out a better way to calculate this chance
|
||||||
|
local normCicTime = CommonMethods.Normalize(cicTime, 0, StaticData.LIMBS_CICATRIZATION_TIME_IND_NUM[limbName]) / 2
|
||||||
|
TOC_DEBUG.print("OG cicTime: " .. tostring(cicTime))
|
||||||
|
TOC_DEBUG.print("Normalized cic time : " .. tostring(normCicTime))
|
||||||
|
|
||||||
|
local chance = ZombRandFloat(0.0, 1.0)
|
||||||
|
if chance > normCicTime then
|
||||||
|
TOC_DEBUG.print("Triggered bleeding from non cicatrized wound")
|
||||||
|
local adjacentBodyPartType = BodyPartType[StaticData.LIMBS_ADJACENT_IND_STR[limbName]]
|
||||||
|
|
||||||
|
-- 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, bodyPart, limbName, dcInst)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if (bodyPart:bitten() or bodyPart:IsInfected()) and not dcInst:getIsInfected(limbName) then
|
||||||
|
dcInst:setIsInfected(limbName, true)
|
||||||
|
modDataNeedsUpdate = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check other body parts that are not included in the mod, if there's a bite there then the player is fucked
|
||||||
|
-- We can skip this loop if the player has been infected. The one before we kinda need it to handle correctly the bites in case the player wanna cut stuff off anyway
|
||||||
|
if 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
|
||||||
|
local ItemsController = require("TOC/Controllers/ItemsController")
|
||||||
|
ItemsController.Player.OverrideAmputationItemVisuals(playerObj, limbName, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
--* Object drop handling when amputation occurs
|
||||||
|
|
||||||
|
|
||||||
|
function LocalPlayerController.CanItemBeEquipped(itemObj, limbName)
|
||||||
|
local bl = itemObj:getBodyLocation()
|
||||||
|
local side = CommonMethods.GetSide(limbName)
|
||||||
|
local sideStr = CommonMethods.GetSideFull(side)
|
||||||
|
|
||||||
|
-- TODO Check from DataController
|
||||||
|
|
||||||
|
if string.contains(limbName, "Hand_") and (bl == sideStr .. "_MiddleFinger" or bl == sideStr .. "_RingFinger") then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
if string.contains(limbName, "ForeArm_") and (bl == sideStr .. "Wrist") then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Drop all items from the affected limb
|
||||||
|
---@param limbName string
|
||||||
|
function LocalPlayerController.DropItemsAfterAmputation(limbName)
|
||||||
|
TOC_DEBUG.print("Triggered DropItemsAfterAmputation")
|
||||||
|
local side = CommonMethods.GetSide(limbName)
|
||||||
|
local sideStr = CommonMethods.GetSideFull(side)
|
||||||
|
|
||||||
|
local pl = getPlayer()
|
||||||
|
local wornItems = pl:getWornItems()
|
||||||
|
|
||||||
|
for i = 1, wornItems:size() do
|
||||||
|
local it = wornItems:get(i - 1)
|
||||||
|
if it then
|
||||||
|
local wornItem = wornItems:get(i - 1):getItem()
|
||||||
|
TOC_DEBUG.print(wornItem:getBodyLocation())
|
||||||
|
|
||||||
|
local bl = wornItem:getBodyLocation()
|
||||||
|
if string.contains(limbName, "Hand_") and (bl == sideStr .. "_MiddleFinger" or bl == sideStr .. "_RingFinger") then
|
||||||
|
pl:removeWornItem(wornItem)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
if string.contains(limbName, "ForeArm_") and (bl == sideStr .. "Wrist") then
|
||||||
|
pl:removeWornItem(wornItem)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO Consider 2 handed weapons too
|
||||||
|
|
||||||
|
-- equipped items too
|
||||||
|
if side == "R" then
|
||||||
|
pl:setPrimaryHandItem(nil)
|
||||||
|
elseif side == "L" then
|
||||||
|
pl:setSecondaryHandItem(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
Events.OnAmputatedLimb.Add(LocalPlayerController.DropItemsAfterAmputation)
|
||||||
|
Events.OnProsthesisUnequipped.Add(LocalPlayerController.DropItemsAfterAmputation)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return LocalPlayerController
|
||||||
115
common/media/lua/client/TOC/Controllers/TourniquetController.lua
Normal 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
|
||||||
5
common/media/lua/client/TOC/Events.lua
Normal 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
|
||||||
211
common/media/lua/client/TOC/Handlers/AmputationHandler.lua
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
local DataController = require("TOC/Controllers/DataController")
|
||||||
|
local ItemsController = require("TOC/Controllers/ItemsController")
|
||||||
|
local CachedDataHandler = require("TOC/Handlers/CachedDataHandler")
|
||||||
|
local LocalPlayerController = require("TOC/Controllers/LocalPlayerController")
|
||||||
|
local StaticData = require("TOC/StaticData")
|
||||||
|
local TourniquetController = require("TOC/Controllers/TourniquetController")
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
--- Manages an amputation. Will be run on the patient client
|
||||||
|
---@class AmputationHandler
|
||||||
|
---@field patientPl IsoPlayer
|
||||||
|
---@field limbName string
|
||||||
|
---@field bodyPartType BodyPartType
|
||||||
|
---@field surgeonPl IsoPlayer?
|
||||||
|
local AmputationHandler = {}
|
||||||
|
|
||||||
|
---@param limbName string
|
||||||
|
---@param surgeonPl IsoPlayer?
|
||||||
|
---@return AmputationHandler
|
||||||
|
function AmputationHandler:new(limbName, surgeonPl)
|
||||||
|
local o = {}
|
||||||
|
setmetatable(o, self)
|
||||||
|
self.__index = self
|
||||||
|
|
||||||
|
o.patientPl = getPlayer()
|
||||||
|
o.limbName = limbName
|
||||||
|
o.bodyPartType = BodyPartType[limbName]
|
||||||
|
|
||||||
|
-- TOC_DEBUG.print("limbName = " .. o.limbName)
|
||||||
|
-- TOC_DEBUG.print("bodyPartType = " .. tostring(o.bodyPartType))
|
||||||
|
|
||||||
|
if surgeonPl then
|
||||||
|
o.surgeonPl = surgeonPl
|
||||||
|
else
|
||||||
|
o.surgeonPl = o.patientPl
|
||||||
|
end
|
||||||
|
|
||||||
|
AmputationHandler.instance = o
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--* Static methods *--
|
||||||
|
|
||||||
|
---@param player IsoPlayer
|
||||||
|
---@param limbName string
|
||||||
|
function AmputationHandler.ApplyDamageDuringAmputation(player, limbName)
|
||||||
|
|
||||||
|
|
||||||
|
local ampGroup = StaticData.LIMBS_TO_AMP_GROUPS_MATCH_IND_STR[limbName]
|
||||||
|
local isTourniquetEquipped = false
|
||||||
|
|
||||||
|
-- Check if tourniquet is applied on the zone
|
||||||
|
for bl, tournAmpGroup in pairs(StaticData.TOURNIQUET_BODYLOCS_TO_GROUPS_IND_STR) do
|
||||||
|
local item = player:getWornItem(bl)
|
||||||
|
|
||||||
|
-- LimbName -> Group -> BodyLoc
|
||||||
|
if item and tournAmpGroup == ampGroup then
|
||||||
|
TOC_DEBUG.print("tourniquet is equipped")
|
||||||
|
isTourniquetEquipped = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local bodyDamage = player:getBodyDamage()
|
||||||
|
local bodyPartType = BodyPartType[limbName]
|
||||||
|
local bodyDamagePart = bodyDamage:getBodyPart(bodyPartType)
|
||||||
|
TOC_DEBUG.print("damage patient - " .. tostring(bodyPartType))
|
||||||
|
|
||||||
|
bodyDamagePart:setBleeding(true)
|
||||||
|
bodyDamagePart:setCut(true)
|
||||||
|
|
||||||
|
local bleedingTime
|
||||||
|
if isTourniquetEquipped then
|
||||||
|
bleedingTime = ZombRand(1,5)
|
||||||
|
else
|
||||||
|
bleedingTime = ZombRand(10, 20)
|
||||||
|
end
|
||||||
|
|
||||||
|
bodyDamagePart:setBleedingTime(bleedingTime)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---@param prevAction ISBaseTimedAction
|
||||||
|
---@param limbName string
|
||||||
|
---@param surgeonPl IsoPlayer
|
||||||
|
---@param patientPl IsoPlayer
|
||||||
|
---@param stitchesItem InventoryItem
|
||||||
|
---@return ISStitch
|
||||||
|
function AmputationHandler.PrepareStitchesAction(prevAction, limbName, surgeonPl, patientPl, stitchesItem)
|
||||||
|
local 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:setEndurance(surgeonFactor)
|
||||||
|
patientStats:setStress(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
|
||||||
|
ItemsController.Player.DeleteOldAmputationItem(self.patientPl, self.limbName)
|
||||||
|
ItemsController.Player.SpawnAmputationItem(self.patientPl, self.limbName)
|
||||||
|
|
||||||
|
-- Add it to the list of cut limbs on this local client
|
||||||
|
local username = self.patientPl:getUsername()
|
||||||
|
CachedDataHandler.AddAmputatedLimb(username, self.limbName)
|
||||||
|
|
||||||
|
-- TODO Not optimal, we're already cycling through this when using setCutLimb
|
||||||
|
for i=1, #StaticData.LIMBS_DEPENDENCIES_IND_STR[self.limbName] do
|
||||||
|
local dependedLimbName = StaticData.LIMBS_DEPENDENCIES_IND_STR[self.limbName][i]
|
||||||
|
CachedDataHandler.AddAmputatedLimb(username, dependedLimbName)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Cache highest amputation and hand feasibility
|
||||||
|
CachedDataHandler.CalculateCacheableValues(username)
|
||||||
|
|
||||||
|
-- If the part was actually infected, heal the player, if they were in time (infectionLevel < 20)
|
||||||
|
if bd:getInfectionLevel() < 20 and bodyPart:IsInfected() and not dcInst:getIsIgnoredPartInfected() then
|
||||||
|
LocalPlayerController.HealZombieInfection(bd, bodyPart, self.limbName, dcInst)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- The last part is to handle the damage that the player will receive after the amputation
|
||||||
|
if not damagePlayer then return end
|
||||||
|
self:damageAfterAmputation(surgeonFactor)
|
||||||
|
|
||||||
|
-- Trigger this event
|
||||||
|
triggerEvent("OnAmputatedLimb", self.limbName)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Deletes the instance
|
||||||
|
function AmputationHandler:close()
|
||||||
|
AmputationHandler.instance = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return AmputationHandler
|
||||||
177
common/media/lua/client/TOC/Handlers/CachedDataHandler.lua
Normal 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
|
||||||
|
|
||||||
193
common/media/lua/client/TOC/Handlers/ProsthesisHandler.lua
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
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 bodyLocArmProst = StaticData.MOD_BODYLOCS_BASE_IND_STR.TOC_ArmProst
|
||||||
|
local bodyLocLegProst = StaticData.MOD_BODYLOCS_BASE_IND_STR.TOC_LegProst
|
||||||
|
|
||||||
|
---Check if the following item is a prosthesis or not
|
||||||
|
---@param item InventoryItem?
|
||||||
|
---@return boolean
|
||||||
|
function ProsthesisHandler.CheckIfProst(item)
|
||||||
|
-- TODO Won't be correct when prost for legs are gonna be in
|
||||||
|
--TOC_DEBUG.print("Checking if item is prost")
|
||||||
|
if item == nil then
|
||||||
|
--TOC_DEBUG.print("Not prost")
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return item:getBodyLocation():contains(bodyLocArmProst)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Get the grouping for the prosthesis
|
||||||
|
---@param item InventoryItem
|
||||||
|
---@return string
|
||||||
|
function ProsthesisHandler.GetGroup(item)
|
||||||
|
local fullType = item:getFullType()
|
||||||
|
local side = CommonMethods.GetSide(fullType)
|
||||||
|
|
||||||
|
|
||||||
|
local bodyLocation = item:getBodyLocation()
|
||||||
|
local position
|
||||||
|
if bodyLocation == bodyLocArmProst then
|
||||||
|
position = "Top_"
|
||||||
|
elseif bodyLocation == bodyLocLegProst then
|
||||||
|
position = "Bottom_"
|
||||||
|
else
|
||||||
|
TOC_DEBUG.print("Something is wrong, no position in this item")
|
||||||
|
position = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local index = position .. side
|
||||||
|
local group = StaticData.AMP_GROUPS_IND_STR[index]
|
||||||
|
return group
|
||||||
|
end
|
||||||
|
|
||||||
|
---Check if a prosthesis is equippable. It depends whether the player has a cut limb or not on that specific side. There's an exception for Upper arm, obviously
|
||||||
|
---@param fullType string
|
||||||
|
---@return boolean
|
||||||
|
function ProsthesisHandler.CheckIfEquippable(fullType)
|
||||||
|
--TOC_DEBUG.print("Current item is a prosthesis")
|
||||||
|
local side = CommonMethods.GetSide(fullType)
|
||||||
|
--TOC_DEBUG.print("Checking side: " .. tostring(side))
|
||||||
|
|
||||||
|
local highestAmputatedLimbs = CachedDataHandler.GetHighestAmputatedLimbs(getPlayer():getUsername())
|
||||||
|
|
||||||
|
if highestAmputatedLimbs then
|
||||||
|
local hal = highestAmputatedLimbs[side]
|
||||||
|
if hal and not string.contains(hal, "UpperArm") then
|
||||||
|
--TOC_DEBUG.print("Found acceptable limb to use prosthesis => " .. tostring(hal))
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- No acceptable cut limbs
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
---Handle equipping or unequipping prosthetics
|
||||||
|
---@param item InventoryItem
|
||||||
|
---@param isEquipping boolean
|
||||||
|
---@return boolean
|
||||||
|
function ProsthesisHandler.SearchAndSetupProsthesis(item, isEquipping)
|
||||||
|
if not ProsthesisHandler.CheckIfProst(item) then return false end
|
||||||
|
|
||||||
|
local group = ProsthesisHandler.GetGroup(item)
|
||||||
|
TOC_DEBUG.print("Setup Prosthesis => " .. group .. " - is equipping? " .. tostring(isEquipping))
|
||||||
|
local dcInst = DataController.GetInstance()
|
||||||
|
dcInst:setIsProstEquipped(group, isEquipping)
|
||||||
|
dcInst:apply()
|
||||||
|
|
||||||
|
-- Calculates hands feasibility once again
|
||||||
|
CachedDataHandler.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
|
||||||
71
common/media/lua/client/TOC/Main.lua
Normal 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.1.6"
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
206
common/media/lua/client/TOC/Tests.lua
Normal 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()
|
||||||
89
common/media/lua/client/TOC/TimedActions/CauterizeAction.lua
Normal 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
|
||||||
126
common/media/lua/client/TOC/TimedActions/CleanWoundAction.lua
Normal 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
|
||||||
138
common/media/lua/client/TOC/TimedActions/CutLimbAction.lua
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
require "TimedActions/ISBaseTimedAction"
|
||||||
|
local AmputationHandler = require("TOC/Handlers/AmputationHandler")
|
||||||
|
local CommandsData = require("TOC/CommandsData")
|
||||||
|
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
---@class CutLimbAction : ISBaseTimedAction
|
||||||
|
---@field patient IsoPlayer
|
||||||
|
---@field character IsoPlayer
|
||||||
|
---@field patientX number
|
||||||
|
---@field patientY number
|
||||||
|
---@field limbName string
|
||||||
|
---@field item InventoryItem
|
||||||
|
---@field stitchesItem InventoryItem?
|
||||||
|
---@field bandageItem InventoryItem?
|
||||||
|
local CutLimbAction = ISBaseTimedAction:derive("CutLimbAction")
|
||||||
|
|
||||||
|
---Starts CutLimbAction
|
||||||
|
---@param surgeon IsoPlayer This is gonna be self.character to have working animations
|
||||||
|
---@param patient IsoPlayer
|
||||||
|
---@param limbName string
|
||||||
|
---@param item InventoryItem This is gonna be the saw, following ISBaseTimedAction
|
||||||
|
---@param stitchesItem InventoryItem?
|
||||||
|
---@param bandageItem InventoryItem?
|
||||||
|
---@return CutLimbAction
|
||||||
|
function CutLimbAction:new(surgeon, patient, limbName, item, stitchesItem, bandageItem)
|
||||||
|
local o = {}
|
||||||
|
setmetatable(o, self)
|
||||||
|
self.__index = self
|
||||||
|
|
||||||
|
-- We need to follow ISBaseTimedAction. self.character is gonna be the surgeon
|
||||||
|
o.character = surgeon
|
||||||
|
o.patient = patient
|
||||||
|
o.limbName = limbName
|
||||||
|
o.item = item
|
||||||
|
|
||||||
|
o.patientX = patient:getX()
|
||||||
|
o.patientY = patient:getY()
|
||||||
|
|
||||||
|
o.stitchesItem = stitchesItem or nil
|
||||||
|
o.bandageItem = bandageItem or nil
|
||||||
|
|
||||||
|
o.stopOnWalk = true
|
||||||
|
o.stopOnRun = true
|
||||||
|
|
||||||
|
o.maxTime = 1000 - (surgeon:getPerkLevel(Perks.Doctor) * 50)
|
||||||
|
if o.character:isTimedActionInstant() then o.maxTime = 1 end
|
||||||
|
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
function CutLimbAction:isValid()
|
||||||
|
return not ISHealthPanel.DidPatientMove(self.character,self.patient, self.patientX, self.patientY)
|
||||||
|
end
|
||||||
|
|
||||||
|
function CutLimbAction:start()
|
||||||
|
if self.patient == self.character then
|
||||||
|
-- Self
|
||||||
|
AmputationHandler.ApplyDamageDuringAmputation(self.patient, self.limbName)
|
||||||
|
else
|
||||||
|
-- Another player
|
||||||
|
---@type relayDamageDuringAmputationParams
|
||||||
|
local params = {patientNum = self.patient:getOnlineID(), limbName = self.limbName}
|
||||||
|
sendClientCommand(CommandsData.modules.TOC_RELAY, CommandsData.server.Relay.RelayDamageDuringAmputation, params )
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type ISBaseTimedAction
|
||||||
|
local prevAction = self
|
||||||
|
|
||||||
|
-- Handle stitching
|
||||||
|
if self.stitchesItem then
|
||||||
|
TOC_DEBUG.print("Stitches...")
|
||||||
|
prevAction = AmputationHandler.PrepareStitchesAction(prevAction, self.limbName, self.character, self.patient, self.stitchesItem)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Handle bandages
|
||||||
|
if self.bandageItem then
|
||||||
|
prevAction = AmputationHandler.PrepareBandagesAction(prevAction, self.limbName, self.character, self.patient, self.bandageItem)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Setup cosmetic stuff
|
||||||
|
self:setActionAnim("SawLog")
|
||||||
|
self:setOverrideHandModels(self.item:getStaticModel())
|
||||||
|
|
||||||
|
-- Setup audio
|
||||||
|
self.sound = self.character:getEmitter():playSound("Amputation")
|
||||||
|
local radius = 5
|
||||||
|
addSound(self.character, self.character:getX(), self.character:getY(), self.character:getZ(), radius, radius)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function CutLimbAction:waitToStart()
|
||||||
|
if self.character == self.patient then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
self.character:faceThisObject(self.patient)
|
||||||
|
return self.character:shouldBeTurning()
|
||||||
|
end
|
||||||
|
|
||||||
|
function CutLimbAction:update()
|
||||||
|
self.character:setMetabolicTarget(Metabolics.HeavyWork)
|
||||||
|
if self.character ~= self.patient then
|
||||||
|
self.patient:setMetabolicTarget(Metabolics.HeavyWork)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function CutLimbAction:stopSound()
|
||||||
|
if self.sound then
|
||||||
|
self.character:getEmitter():stopSound(self.sound)
|
||||||
|
self.sound = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function CutLimbAction:stop()
|
||||||
|
self:stopSound()
|
||||||
|
ISBaseTimedAction.stop(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function CutLimbAction:perform()
|
||||||
|
-- Stop the sound
|
||||||
|
self:stopSound()
|
||||||
|
|
||||||
|
if self.patient == self.character then
|
||||||
|
TOC_DEBUG.print("patient and surgeon are the same, executing on the client")
|
||||||
|
local handler = AmputationHandler:new(self.limbName)
|
||||||
|
handler:execute(true)
|
||||||
|
else
|
||||||
|
TOC_DEBUG.print("patient and surgeon not the same, sending relay to server")
|
||||||
|
-- Other player
|
||||||
|
---@type relayExecuteAmputationActionParams
|
||||||
|
local params = {patientNum = self.patient:getOnlineID(), limbName = self.limbName}
|
||||||
|
sendClientCommand(CommandsData.modules.TOC_RELAY, CommandsData.server.Relay.RelayExecuteAmputationAction, params )
|
||||||
|
end
|
||||||
|
|
||||||
|
ISBaseTimedAction.perform(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
return CutLimbAction
|
||||||
139
common/media/lua/client/TOC/TimedActions/IgnoredActions.lua
Normal 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
|
||||||
@@ -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
|
||||||
116
common/media/lua/client/TOC/UI/ConfirmationPanel.lua
Normal 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
|
||||||
293
common/media/lua/client/TOC/UI/HealthPanel.lua
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
-- local og_ISHealthPanel_update = ISHealthPanel.update
|
||||||
|
-- function ISHealthPanel:update()
|
||||||
|
-- og_ISHealthPanel_update(self)
|
||||||
|
-- -- TODO Listen for changes on other player side instead of looping this
|
||||||
|
|
||||||
|
|
||||||
|
-- -- FIX Re-enable it, just for test
|
||||||
|
-- if self.character then
|
||||||
|
-- local locPlUsername = getPlayer():getUsername()
|
||||||
|
-- local remPlUsername = self.character:getUsername()
|
||||||
|
-- if locPlUsername ~= remPlUsername and self:isReallyVisible() then
|
||||||
|
-- -- Request update for TOC DATA
|
||||||
|
-- local key = CommandsData.GetKey(remPlUsername)
|
||||||
|
-- --ModData.request(key)
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- We need to override this to force the alpha to 1
|
||||||
|
local og_ISCharacterInfoWindow_render = ISCharacterInfoWindow.prerender
|
||||||
|
function ISCharacterInfoWindow:prerender()
|
||||||
|
og_ISCharacterInfoWindow_render(self)
|
||||||
|
self.backgroundColor.a = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- We need to override this to force the alpha to 1 for the Medical Check in particular
|
||||||
|
local og_ISHealthPanel_prerender = ISHealthPanel.prerender
|
||||||
|
function ISHealthPanel:prerender()
|
||||||
|
og_ISHealthPanel_prerender(self)
|
||||||
|
self.backgroundColor.a = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
--- The medical check wrap the health panel into this. We need to override its color
|
||||||
|
local overrideBackgroundColor = true
|
||||||
|
local og_ISUIElement_wrapInCollapsableWindow = ISUIElement.wrapInCollapsableWindow
|
||||||
|
---@param title string
|
||||||
|
---@param resizable any
|
||||||
|
---@param subClass any
|
||||||
|
---@return any
|
||||||
|
function ISUIElement:wrapInCollapsableWindow(title, resizable, subClass)
|
||||||
|
local panel = og_ISUIElement_wrapInCollapsableWindow(self, title, resizable, subClass)
|
||||||
|
|
||||||
|
if overrideBackgroundColor then
|
||||||
|
TOC_DEBUG.print("Overriding color for panel - " .. title)
|
||||||
|
self.backgroundColor.a = 1
|
||||||
|
panel.backgroundColor.a = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return panel
|
||||||
|
end
|
||||||
|
|
||||||
|
-- This is run when a player is trying the Medical Check action on another player
|
||||||
|
local og_ISMedicalCheckAction_perform = ISMedicalCheckAction.perform
|
||||||
|
function ISMedicalCheckAction:perform()
|
||||||
|
local username = self.otherPlayer:getUsername()
|
||||||
|
TOC_DEBUG.print("Medical Action on " .. username )
|
||||||
|
|
||||||
|
-- We need to recalculate them here before we can create the highest amputations point
|
||||||
|
CachedDataHandler.CalculateAmputatedLimbs(username)
|
||||||
|
og_ISMedicalCheckAction_perform(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
local og_ISHealthBodyPartListBox_doDrawItem = ISHealthBodyPartListBox.doDrawItem
|
||||||
|
function ISHealthBodyPartListBox:doDrawItem(y, item, alt)
|
||||||
|
y = og_ISHealthBodyPartListBox_doDrawItem(self, y, item, alt)
|
||||||
|
y = y - 5
|
||||||
|
local x = 15
|
||||||
|
local fontHgt = getTextManager():getFontHeight(UIFont.Small)
|
||||||
|
|
||||||
|
local username = self.parent.character:getUsername()
|
||||||
|
--local amputatedLimbs = CachedDataHandler.GetIndexedAmputatedLimbs(username)
|
||||||
|
|
||||||
|
---@type BodyPart
|
||||||
|
local bodyPart = item.item.bodyPart
|
||||||
|
|
||||||
|
local bodyPartTypeStr = BodyPartType.ToString(bodyPart:getType())
|
||||||
|
local limbName = StaticData.LIMBS_IND_STR[bodyPartTypeStr]
|
||||||
|
|
||||||
|
|
||||||
|
-- We should cache a lot of other stuff to have this working with CacheDataHandler :(
|
||||||
|
if limbName then
|
||||||
|
local dcInst = DataController.GetInstance(username)
|
||||||
|
if dcInst:getIsCut(limbName) and dcInst:getIsVisible(limbName) then
|
||||||
|
if dcInst:getIsCicatrized(limbName) then
|
||||||
|
if dcInst:getIsCauterized(limbName) then
|
||||||
|
self:drawText("- " .. getText("IGUI_HealthPanel_Cauterized"), x, y, 0.58, 0.75, 0.28, 1, UIFont.Small)
|
||||||
|
else
|
||||||
|
self:drawText("- " .. getText("IGUI_HealthPanel_Cicatrized"), x, y, 0.28, 0.89, 0.28, 1, UIFont.Small)
|
||||||
|
end
|
||||||
|
|
||||||
|
y = y + fontHgt
|
||||||
|
else
|
||||||
|
local cicaTime = dcInst:getCicatrizationTime(limbName)
|
||||||
|
|
||||||
|
-- Show it in percentage
|
||||||
|
local maxCicaTime = StaticData.LIMBS_CICATRIZATION_TIME_IND_NUM[limbName]
|
||||||
|
local percentage = (1 - cicaTime/maxCicaTime) * 100
|
||||||
|
self:drawText("- " .. getText("IGUI_HealthPanel_Cicatrization") .. string.format(" %.2f", percentage) .. "%", x, y, 0.89, 0.28, 0.28, 1, UIFont.Small)
|
||||||
|
y = y + fontHgt
|
||||||
|
|
||||||
|
local scaledDirtyness = math.floor(dcInst:getWoundDirtyness(limbName) * 100)
|
||||||
|
self:drawText("- " .. getText("IGUI_HealthPanel_WoundDirtyness") .. string.format(" %d", scaledDirtyness) .. "%", x, y, 0.89, 0.28, 0.28, 1, UIFont.Small)
|
||||||
|
y = y + fontHgt
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
if dcInst:getIsProstEquipped(limbName) then
|
||||||
|
self:drawText("- " .. getText("IGUI_HealthPanel_ProstEquipped"), x, y, 0.28, 0.89, 0.28, 1, UIFont.Small)
|
||||||
|
y = y + fontHgt
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
y = y + 5
|
||||||
|
return y
|
||||||
|
end
|
||||||
|
|
||||||
|
local og_ISHealthPanel_getDamagedParts = ISHealthPanel.getDamagedParts
|
||||||
|
function ISHealthPanel:getDamagedParts()
|
||||||
|
-- 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
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
-- Had to copy and paste this stuff from the base game since this is a local only class. Kinda shit, but eh
|
||||||
|
|
||||||
|
---@class BaseHandler : ISBaseObject
|
||||||
|
---@field panel ISUIElement
|
||||||
|
---@field bodyPart BodyPart
|
||||||
|
---@field items table
|
||||||
|
local BaseHandler = ISBaseObject:derive("BaseHandler")
|
||||||
|
|
||||||
|
---@param panel ISUIElement
|
||||||
|
---@param bodyPart BodyPart
|
||||||
|
---@return table
|
||||||
|
function BaseHandler:new(panel, bodyPart)
|
||||||
|
local o = {}
|
||||||
|
setmetatable(o, self)
|
||||||
|
self.__index = self
|
||||||
|
o.panel = panel
|
||||||
|
o.bodyPart = bodyPart
|
||||||
|
o.items = {}
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
function BaseHandler:isInjured()
|
||||||
|
TOC_DEBUG.print("Running isInjured")
|
||||||
|
local bodyPart = self.bodyPart
|
||||||
|
return (bodyPart:HasInjury() or bodyPart:stitched() or bodyPart:getSplintFactor() > 0) and not bodyPart:bandaged()
|
||||||
|
end
|
||||||
|
|
||||||
|
function BaseHandler:checkItems()
|
||||||
|
for k,v in pairs(self.items) do
|
||||||
|
table.wipe(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
local containers = ISInventoryPaneContextMenu.getContainers(self:getDoctor())
|
||||||
|
local done = {}
|
||||||
|
local childContainers = {}
|
||||||
|
for i=1,containers:size() do
|
||||||
|
local container = containers:get(i-1)
|
||||||
|
done[container] = true
|
||||||
|
table.wipe(childContainers)
|
||||||
|
self:checkContainerItems(container, childContainers)
|
||||||
|
for _,container2 in ipairs(childContainers) do
|
||||||
|
if not done[container2] then
|
||||||
|
done[container2] = true
|
||||||
|
self:checkContainerItems(container2, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function BaseHandler:checkContainerItems(container, childContainers)
|
||||||
|
local containerItems = container:getItems()
|
||||||
|
for i=1,containerItems:size() do
|
||||||
|
local item = containerItems:get(i-1)
|
||||||
|
if item:IsInventoryContainer() then
|
||||||
|
if childContainers then
|
||||||
|
table.insert(childContainers, item:getInventory())
|
||||||
|
end
|
||||||
|
else
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
self:checkItem(item) -- This is in inherited classes, we never use this class by itself
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function BaseHandler:dropItems(items)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function BaseHandler:addItem(items, item)
|
||||||
|
table.insert(items, item)
|
||||||
|
end
|
||||||
|
|
||||||
|
function BaseHandler:getAllItemTypes(items)
|
||||||
|
local done = {}
|
||||||
|
local types = {}
|
||||||
|
for _,item in ipairs(items) do
|
||||||
|
if not done[item:getFullType()] then
|
||||||
|
table.insert(types, item:getFullType())
|
||||||
|
done[item:getFullType()] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return types
|
||||||
|
end
|
||||||
|
|
||||||
|
function BaseHandler:getItemOfType(items, type)
|
||||||
|
for _,item in ipairs(items) do
|
||||||
|
if item:getFullType() == type then
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function BaseHandler:getItemOfTag(items, type)
|
||||||
|
for _,item in ipairs(items) do
|
||||||
|
if item:hasTag(type) then
|
||||||
|
return item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function BaseHandler:getAllItemsOfType(items, type)
|
||||||
|
local items = {}
|
||||||
|
for _,item in ipairs(items) do
|
||||||
|
if item:getFullType() == type then
|
||||||
|
table.insert(items, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return items
|
||||||
|
end
|
||||||
|
|
||||||
|
function BaseHandler:onMenuOptionSelected(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)
|
||||||
|
ISTimedActionQueue.add(HealthPanelAction:new(self:getDoctor(), self, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8))
|
||||||
|
end
|
||||||
|
|
||||||
|
function BaseHandler:toPlayerInventory(item, previousAction)
|
||||||
|
if item:getContainer() ~= self:getDoctor():getInventory() then
|
||||||
|
local action = ISInventoryTransferAction:new(self:getDoctor(), item, item:getContainer(), self:getDoctor():getInventory())
|
||||||
|
ISTimedActionQueue.addAfter(previousAction, action)
|
||||||
|
-- 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
|
||||||
@@ -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
|
||||||
98
common/media/lua/client/TOC/UI/SurgeryInteractions.lua
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
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("Brave") or pl:HasTrait("Desensitized") or pl:getPerkLevel(Perks.Strength) > 5
|
||||||
|
local isTempHighEnough = stoveObj:getCurrentTemperature() >= 250
|
||||||
|
local isLimbFree = not dcInst:getIsProstEquipped(limbName)
|
||||||
|
|
||||||
|
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)
|
||||||
157
common/media/lua/client/TOC/Zombies/ZombiesAmputation.lua
Normal 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)
|
||||||
171
common/media/lua/client/lua_timers.lua
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
-- Made by Vyshnia
|
||||||
|
-- Workshop ID: 2875394066
|
||||||
|
-- Mod ID: LuaTimers
|
||||||
|
|
||||||
|
local os_time = os.time
|
||||||
|
local table_insert = table.insert
|
||||||
|
local table_remove = table.remove
|
||||||
|
local assert = assert
|
||||||
|
local type = type
|
||||||
|
local pairs = pairs
|
||||||
|
|
||||||
|
timer = {
|
||||||
|
Timers = {},
|
||||||
|
SimpleTimers = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function timer:Simple(delay, func)
|
||||||
|
assert(type(delay) == "number", "Delay of timer should be a number type")
|
||||||
|
assert(type(func) == "function", "Func of timer should be a function type (lol)")
|
||||||
|
|
||||||
|
table_insert(self.SimpleTimers, {
|
||||||
|
EndTime = os_time() + delay,
|
||||||
|
Func = func
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function timer:Create(name, delay, repetitions, func)
|
||||||
|
assert(type(name) == "string", "ID of timer should be a string type")
|
||||||
|
assert(type(delay) == "number", "Delay of timer should be a number type")
|
||||||
|
assert(type(repetitions) == "number", "Repetitions of timer should be a number type")
|
||||||
|
assert(type(func) == "function", "Func of timer should be a function type (lol)")
|
||||||
|
|
||||||
|
self.Timers[name] = {
|
||||||
|
Delay = delay,
|
||||||
|
StartRepetitions = repetitions,
|
||||||
|
Repetitions = repetitions,
|
||||||
|
Infinity = repetitions == 0,
|
||||||
|
LastFuncTime = os_time(),
|
||||||
|
Func = func,
|
||||||
|
Paused = false,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function timerUpdate()
|
||||||
|
local cur_time = os_time()
|
||||||
|
|
||||||
|
for k, v in pairs(timer.Timers) do
|
||||||
|
if not v.Paused then
|
||||||
|
if cur_time >= v.LastFuncTime + v.Delay then
|
||||||
|
v.Func()
|
||||||
|
|
||||||
|
v.LastFuncTime = cur_time
|
||||||
|
|
||||||
|
if not v.Infinity then
|
||||||
|
v.Repetitions = v.Repetitions - 1
|
||||||
|
|
||||||
|
if v.Repetitions <= 0 then
|
||||||
|
timer.Timers[k] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local simple_timers = timer.SimpleTimers
|
||||||
|
|
||||||
|
for i = #simple_timers, 1, -1 do
|
||||||
|
local t = simple_timers[i]
|
||||||
|
|
||||||
|
if t.EndTime <= cur_time then
|
||||||
|
t.Func()
|
||||||
|
|
||||||
|
table_remove(simple_timers, i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Events.OnTickEvenPaused.Add(timerUpdate)
|
||||||
|
|
||||||
|
function timer:Remove(name)
|
||||||
|
local t = self.Timers[name]
|
||||||
|
|
||||||
|
if not t then return false end
|
||||||
|
|
||||||
|
self.Timers[name] = nil
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function timer:Exists(name)
|
||||||
|
return self.Timers[name] and true or false
|
||||||
|
end
|
||||||
|
|
||||||
|
function timer:Start(name)
|
||||||
|
local t = self.Timers[name]
|
||||||
|
|
||||||
|
if not t then return false end
|
||||||
|
|
||||||
|
t.Repetitions = t.StartRepetitions
|
||||||
|
t.LastFuncTime = os_time()
|
||||||
|
t.Paused = false
|
||||||
|
t.PausedTime = nil
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function timer:Pause(name)
|
||||||
|
local t = self.Timers[name]
|
||||||
|
|
||||||
|
if not t then return false end
|
||||||
|
|
||||||
|
if t.Paused then return false end
|
||||||
|
|
||||||
|
t.Paused = true
|
||||||
|
t.PausedTime = os_time()
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function timer:UnPause(name)
|
||||||
|
local t = self.Timers[name]
|
||||||
|
|
||||||
|
if not t then return false end
|
||||||
|
|
||||||
|
if not t.Paused then return false end
|
||||||
|
|
||||||
|
t.Paused = false
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
timer.Resume = timer.UnPause
|
||||||
|
|
||||||
|
function timer:Toggle(name)
|
||||||
|
local t = self.Timers[name]
|
||||||
|
|
||||||
|
if not t then return false end
|
||||||
|
|
||||||
|
t.Paused = not t.Paused
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function timer:TimeLeft(name)
|
||||||
|
local t = self.Timers[name]
|
||||||
|
|
||||||
|
if not t then return end
|
||||||
|
|
||||||
|
if t.Paused then
|
||||||
|
return (t.Repetitions - 1) * t.Delay + (t.LastFuncTime + t.Delay - t.PausedTime)
|
||||||
|
else
|
||||||
|
return (t.Repetitions - 1) * t.Delay + (t.LastFuncTime + t.Delay - os_time())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function timer:NextTimeLeft(name)
|
||||||
|
local t = self.Timers[name]
|
||||||
|
|
||||||
|
if not t then return end
|
||||||
|
|
||||||
|
if t.Paused then
|
||||||
|
return t.LastFuncTime + t.Delay - t.PausedTime
|
||||||
|
else
|
||||||
|
return t.LastFuncTime + t.Delay - os_time()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function timer:RepsLeft(name)
|
||||||
|
local t = self.Timers[name]
|
||||||
|
|
||||||
|
return t and t.Repetitions
|
||||||
|
end
|
||||||
32
common/media/lua/server/TOC/DebugCommands.lua
Normal 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)
|
||||||
|
|
||||||
31
common/media/lua/server/TOC/Distributions.lua
Normal 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
15
common/media/lua/server/TOC/LimitActionsController.lua
Normal 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
|
||||||
60
common/media/lua/server/TOC/ServerDataHandler.lua
Normal 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
|
||||||
67
common/media/lua/server/TOC/ServerRelayCommands.lua
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
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:getOnlineID()
|
||||||
|
|
||||||
|
---@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:getOnlineID()
|
||||||
|
|
||||||
|
---@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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
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)
|
||||||
71
common/media/lua/shared/TOC/BodyLocations.lua
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
require("TOC/Debug")
|
||||||
|
require("NPCs/BodyLocations")
|
||||||
|
local StaticData = require("TOC/StaticData")
|
||||||
|
|
||||||
|
local BodyLocationsAPI = {}
|
||||||
|
local function customGetVal(obj, int) return getClassFieldVal(obj, getClassField(obj, int)) end
|
||||||
|
local group = BodyLocations.getGroup("Human")
|
||||||
|
|
||||||
|
---@type ArrayList
|
||||||
|
local list = customGetVal(group, 1)
|
||||||
|
|
||||||
|
-- TODO Not sure if this method actually works as intende with b42, but for our use case it's fine...
|
||||||
|
|
||||||
|
---@param toRelocateOrCreate string
|
||||||
|
---@param locationElement string
|
||||||
|
---@param afterBoolean boolean
|
||||||
|
---@return BodyLocation
|
||||||
|
function BodyLocationsAPI.MoveOrCreateBeforeOrAfter(toRelocateOrCreate, locationElement, afterBoolean)
|
||||||
|
-- Check type of arg 2 == string - if not error out.
|
||||||
|
if type(locationElement) ~= "string" then error("Argument 2 is not of type string. Please re-check!", 2) end
|
||||||
|
local itemToMoveTo = group:getLocation(locationElement) -- get location to move to
|
||||||
|
if itemToMoveTo ~= nil then
|
||||||
|
-- Check type of arg 1 == string - if not, error out.
|
||||||
|
if type(toRelocateOrCreate) ~= "string" then error("Argument 1 is not of type string. Please re-check!", 2) end
|
||||||
|
|
||||||
|
local curItem
|
||||||
|
if StaticData.COMPAT_42 then
|
||||||
|
curItem = BodyLocation.new(group, toRelocateOrCreate) -- create new item
|
||||||
|
group:getAllLocations():add(curItem) -- add to the list
|
||||||
|
else
|
||||||
|
curItem = group:getOrCreateLocation(toRelocateOrCreate) -- get current item - or create
|
||||||
|
|
||||||
|
end
|
||||||
|
list:remove(curItem) -- remove from the list
|
||||||
|
local index = group:indexOf(locationElement) -- get current index after removal of the location to move to
|
||||||
|
if afterBoolean then index = index + 1 end -- if we want it after it, we increase the index to move to by one
|
||||||
|
list:add(index, curItem) -- we add the item again
|
||||||
|
|
||||||
|
|
||||||
|
return curItem
|
||||||
|
else -- we did not find the location to move to, so we throw an error.
|
||||||
|
error("Could not find the BodyLocation [".. tostring(locationElement) .."] - please check the passed arguments!", 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function TestBodyLocations()
|
||||||
|
local group = BodyLocations.getGroup("Human")
|
||||||
|
local x = group:getAllLocations()
|
||||||
|
|
||||||
|
for i=0, x:size() -1 do
|
||||||
|
|
||||||
|
---@type BodyLocation
|
||||||
|
local bl = x:get(i)
|
||||||
|
|
||||||
|
print(bl:getId())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- MultiItem causes a ton of issues... fucking hell
|
||||||
|
|
||||||
|
-- BodyLocationsAPI.MoveOrCreateBeforeOrAfter("TOC_Arm", "Shirt", true)
|
||||||
|
-- group:setMultiItem("TOC_Arm", true)
|
||||||
|
|
||||||
|
local curItem = BodyLocation.new(group, "TOC_Arm") -- create new item
|
||||||
|
group:getAllLocations():add(curItem) -- add to the list
|
||||||
|
|
||||||
|
BodyLocationsAPI.MoveOrCreateBeforeOrAfter("TOC_ArmProst", "TOC_Arm", true)
|
||||||
|
group:setMultiItem("TOC_ArmProst", true)
|
||||||
|
|
||||||
|
BodyLocationsAPI.MoveOrCreateBeforeOrAfter("TOC_ArmAccessory", "TOC_ArmProst", true)
|
||||||
|
group:setMultiItem("TOC_ArmAccessory", true)
|
||||||
57
common/media/lua/shared/TOC/CommandsData.lua
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
local StaticData = require("TOC/StaticData")
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
local CommandsData = {}
|
||||||
|
|
||||||
|
CommandsData.modules = {
|
||||||
|
TOC_DEBUG = "TOC_DEBUG",
|
||||||
|
TOC_RELAY = "TOC_RELAY"
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandsData.client = {
|
||||||
|
Relay = {
|
||||||
|
ReceiveDamageDuringAmputation = "ReceiveDamageDuringAmputation", ---@alias receiveDamageDuringAmputationParams { limbName : string}
|
||||||
|
ReceiveExecuteAmputationAction = "ReceiveExecuteAmputationAction", ---@alias receiveExecuteAmputationActionParams {surgeonNum : number, limbName : string, damagePlayer : boolean}
|
||||||
|
|
||||||
|
--* APPLY *--
|
||||||
|
ReceiveApplyFromServer = "ReceiveApplyFromServer",
|
||||||
|
|
||||||
|
--* 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
|
||||||
110
common/media/lua/shared/TOC/Debug.lua
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
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))
|
||||||
|
--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
|
||||||
31
common/media/lua/shared/TOC/OverridenMethodsArchive.lua
Normal 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
|
||||||
306
common/media/lua/shared/TOC/StaticData.lua
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
---@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"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
StaticData.MOD_BODYLOCS_BASE_IND_STR = {
|
||||||
|
TOC_ArmProst = "TOC_ArmProst",
|
||||||
|
TOC_LegProst = "TOC_LegProst",
|
||||||
|
TOC_Arm = "TOC_Arm",
|
||||||
|
}
|
||||||
|
|
||||||
|
-- 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"
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = {
|
||||||
|
["HandsLeft"] = StaticData.AMP_GROUPS_IND_STR.Top_L,
|
||||||
|
["HandsRight"] = 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
|
||||||
47
common/media/lua/shared/TOC/Traits.lua
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
|
||||||
|
---Setups the custom TOC traits
|
||||||
|
|
||||||
|
local TRAITS = {
|
||||||
|
Amputee_Hand = "Amputee_Hand",
|
||||||
|
Amputee_ForeArm = "Amputee_ForeArm",
|
||||||
|
Amputee_UpperArm = "Amputee_UpperArm",
|
||||||
|
--Insensitive = "Insensitive" -- TODO Disabled for now, until we reintroduce it
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
local function GetTraitText(trait)
|
||||||
|
return getText("UI_trait_" .. trait)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function GetTraitDesc(trait)
|
||||||
|
return getText("UI_trait_" .. trait .. "_desc")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function SetupTraits()
|
||||||
|
local traitsTable = {
|
||||||
|
[1] = TraitFactory.addTrait(TRAITS.Amputee_Hand, GetTraitText(TRAITS.Amputee_Hand), -8, GetTraitDesc(TRAITS.Amputee_Hand), false, false),
|
||||||
|
[2] = TraitFactory.addTrait(TRAITS.Amputee_ForeArm, GetTraitText(TRAITS.Amputee_ForeArm), -10, GetTraitDesc(TRAITS.Amputee_ForeArm), false, false),
|
||||||
|
[3] = TraitFactory.addTrait(TRAITS.Amputee_UpperArm, GetTraitText(TRAITS.Amputee_UpperArm), -20, GetTraitDesc(TRAITS.Amputee_UpperArm), false, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i=1, #traitsTable do
|
||||||
|
|
||||||
|
---@type Trait
|
||||||
|
local t = traitsTable[i]
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
t:addXPBoost(Perks.Side_L, 4)
|
||||||
|
t:addXPBoost(Perks.Fitness, -1)
|
||||||
|
t:addXPBoost(Perks.Strength, -1)
|
||||||
|
end
|
||||||
|
|
||||||
|
--TraitFactory.addTrait(TRAITS.Insensitive, GetTraitText(TRAITS.Insensitive), 6, GetTraitDesc(TRAITS.Insensitive), false, false)
|
||||||
|
|
||||||
|
TraitFactory.setMutualExclusive(TRAITS.Amputee_Hand, TRAITS.Amputee_ForeArm)
|
||||||
|
TraitFactory.setMutualExclusive(TRAITS.Amputee_Hand, TRAITS.Amputee_UpperArm)
|
||||||
|
TraitFactory.setMutualExclusive(TRAITS.Amputee_ForeArm, TRAITS.Amputee_UpperArm)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Events.OnGameBoot.Add(SetupTraits)
|
||||||
22
common/media/lua/shared/Translate/DE/ContextMenu_DE.txt
Normal 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",
|
||||||
|
|
||||||
|
}
|
||||||
18
common/media/lua/shared/Translate/DE/IG_UI_DE.txt
Normal 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",
|
||||||
|
}
|
||||||
9
common/media/lua/shared/Translate/DE/ItemName_DE.txt
Normal 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",
|
||||||
|
}
|
||||||
4
common/media/lua/shared/Translate/DE/Recipes_DE.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Recipes_DE = {
|
||||||
|
Recipe_Craft_Prosthetic_Arm = "Baue Armprothese",
|
||||||
|
Recipe_Craft_Prosthetic_Hook = "Baue Hakenprothese",
|
||||||
|
}
|
||||||
6
common/media/lua/shared/Translate/DE/Sandbox_DE.txt
Normal 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",
|
||||||
|
}
|
||||||
8
common/media/lua/shared/Translate/DE/Tooltip_DE.txt
Normal 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",
|
||||||
|
|
||||||
|
}
|
||||||
11
common/media/lua/shared/Translate/DE/UI_DE.txt
Normal 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..."
|
||||||
|
}
|
||||||
33
common/media/lua/shared/Translate/EN/ContextMenu_EN.txt
Normal 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",
|
||||||
|
|
||||||
|
}
|
||||||
25
common/media/lua/shared/Translate/EN/IG_UI_EN.txt
Normal 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?"
|
||||||
|
|
||||||
|
}
|
||||||
11
common/media/lua/shared/Translate/EN/ItemName_EN.txt
Normal 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",
|
||||||
|
}
|
||||||
4
common/media/lua/shared/Translate/EN/Recipes_EN.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Recipes_EN = {
|
||||||
|
Recipe_Craft_Prosthetic_Arm = "Craft Prosthetic Arm",
|
||||||
|
Recipe_Craft_Prosthetic_Hook = "Craft Prosthetic Hook",
|
||||||
|
}
|
||||||
10
common/media/lua/shared/Translate/EN/Sandbox_EN.txt
Normal 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",
|
||||||
|
|
||||||
|
}
|
||||||
10
common/media/lua/shared/Translate/EN/Tooltip_EN.txt
Normal 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",
|
||||||
|
|
||||||
|
}
|
||||||