Track LocalMods as part of monolith

This commit is contained in:
2026-06-08 18:50:16 +03:00
parent 143f2fed7c
commit 1b214b44c2
1287 changed files with 139255 additions and 1 deletions
+98
View File
@@ -0,0 +1,98 @@
--if Game.IsMultiplayer and CLIENT then return end
NT = {} -- Neurotrauma
NT.Name = "Neurotrauma"
NT.Version = "A1.17.4"
NT.VersionNum = 01170400
NT.Path = table.pack(...)[1]
dofile(NT.Path .. "/Lua/Scripts/helperfunctions.lua")
-- all things config
dofile(NT.Path .. "/Lua/Scripts/configdata.lua")
-- server-side code (also run in singleplayer)
if (Game.IsMultiplayer and SERVER) or not Game.IsMultiplayer then
-- Version and expansion display
Timer.Wait(function()
Timer.Wait(function()
local runstring = "\n/// Running Neurotrauma V " .. NT.Version .. " ///\n"
-- add dashes
local linelength = string.len(runstring) + 4
local i = 0
while i < linelength do
runstring = runstring .. "-"
i = i + 1
end
local hasAddons = #NTC.RegisteredExpansions > 0
-- add expansions
for val in NTC.RegisteredExpansions do
runstring = runstring .. "\n+ " .. (val.Name or "Unnamed expansion") .. " V " .. (val.Version or "???")
if val.MinNTVersion ~= nil and NT.VersionNum < (val.MinNTVersionNum or 1) then
runstring = runstring
.. "\n-- WARNING! Neurotrauma version "
.. val.MinNTVersion
.. " or higher required!"
end
end
-- No expansions
runstring = runstring .. "\n"
if not hasAddons then runstring = runstring .. "- Not running any expansions\n" end
print(runstring)
end, 1)
end, 1)
dofile(NT.Path .. "/Lua/Scripts/Server/characterpatches.lua")
dofile(NT.Path .. "/Lua/Scripts/Server/ntcompat.lua")
dofile(NT.Path .. "/Lua/Scripts/Server/dynamicitems.lua")
dofile(NT.Path .. "/Lua/Scripts/Server/blood.lua")
dofile(NT.Path .. "/Lua/Scripts/Server/humanupdate.lua")
dofile(NT.Path .. "/Lua/Scripts/Server/ondamaged.lua")
dofile(NT.Path .. "/Lua/Scripts/Server/items.lua")
--dofile(NT.Path .. "/Lua/Scripts/Server/onfire.lua") -- we got onfire in vanilla, plus it has severe perf issues
dofile(NT.Path .. "/Lua/Scripts/Server/cpr.lua")
--dofile(NT.Path.."/Lua/Scripts/Server/surgerytable.lua")
dofile(NT.Path .. "/Lua/Scripts/Server/fuckbots.lua")
dofile(NT.Path .. "/Lua/Scripts/Server/lootcrates.lua")
dofile(NT.Path .. "/Lua/Scripts/Server/multiscalpel.lua") -- its important for this to run after items.lua
dofile(NT.Path .. "/Lua/Scripts/Server/falldamage.lua")
dofile(NT.Path .. "/Lua/Scripts/Server/screams.lua")
dofile(NT.Path .. "/Lua/Scripts/Server/modconflict.lua")
dofile(NT.Path .. "/Lua/Scripts/testing.lua")
end
-- server-side code only
if SERVER then
Networking.Receive("NT.ConfigUpdate", function(msg, sender)
if not sender.HasPermission(ClientPermissions.ManageSettings) then return end
NTConfig.ReceiveConfig(msg)
NTConfig.SaveConfig()
end)
Networking.Receive("NT.ConfigRequest", function(msg, sender)
if not sender then return end
NTConfig.SendConfig(sender)
end)
end
-- client-side code
if CLIENT then
dofile(NT.Path .. "/Lua/Scripts/Client/configgui.lua")
dofile(NT.Path .. "/Lua/Scripts/Client/dynamicitems.lua")
end
-- Shared and singleplayer code
dofile(NT.Path .. "/Lua/Scripts/Shared/surgerytable.lua") -- Heelge: fix for https://github.com/OlegBSTU/Neurotrauma/issues/15
--dofile(NT.Path .. "/Lua/Scripts/Shared/pronecolliderfix.lua") -- Heelge: fix collider on prone (thx Lua man), but it has 2 ms perf drop so commented out
-- Consent Required Extended with adjustments
-- mod page: https://steamcommunity.com/sharedfiles/filedetails/?id=2892602084
dofile(NT.Path .. "/Lua/ConsentRequiredExtended/init.lua")
dofile(NT.Path .. "/Lua/Scripts/testing.lua")
+154
View File
@@ -0,0 +1,154 @@
-- Consent Required API
-- Any Lua script can access this API by adding this line:
-- local Api = require "com.github.cintique.ConsentRequired.Api"
local Environment = require("ConsentRequiredExtended.Util.Environment")
local Barotrauma = require("ConsentRequiredExtended.Util.Barotrauma")
local _ENV = Environment.PrepareEnvironment(_ENV)
-- Table of identifiers (strings) of items that when used
-- as a treatment on an NPC from a different team,
-- causes that NPC (and their allies) to become hostile
-- towards the player.
local affectedItems = {}
---Adds an item (by identifier string) to `affectedItems`.
---@param identifier string
function AddAffectedItem(identifier)
table.insert(affectedItems, identifier)
end
LuaUserData.MakeFieldAccessible(Descriptors["Barotrauma.AbandonedOutpostMission"], "requireRescue")
-- Character type doesn't have tags we can assign a custom "rescuetarget" tag to
-- So instead we just hold characters which need rescue in a table and compare their entity IDs
-- This table is only resfreshed on roundstart
local rescuetargets = {}
---Returns a boolean indicating whether a given item is affected or not.
---@param identifier string The identifier of the item that we are testing.
---@return boolean isAffected True if the item is affected, false otherwise.
function IsItemAffected(identifier)
for _, item in pairs(affectedItems) do
if item == identifier or HF.StartsWith(identifier, item) then
return true
end
end
return false
end
LuaUserData.MakeMethodAccessible(Descriptors["Barotrauma.HumanAIController"], "RespondToAttack")
local ADD_ATTACKER_DAMAGE = 130 -- Heelge: this used to max out negative rep gain, now only around 4 negative rep, any less negative rep is too forgiving.
---@param aiChar Barotrauma_Character The AI character to be made hostile.
---@param instigator Barotrauma_Character The character to be the target of the AI's wrath.
function makeHostile(aiChar, instigator)
--There is a bit of code which causes attackresults without afflictions to get discarded if some other affliction happens whichin a second
--That one was "fun" to debug
--aiChar.AIController.OnAttacked(instigator, Barotrauma.AttackResult.NewAttackResultFromDamage(ADD_ATTACKER_DAMAGE))
Timer.Wait(function()
aiChar.AIController.RespondToAttack(instigator, Barotrauma.AttackResult.NewAttackResultFromDamage(ADD_ATTACKER_DAMAGE))
aiChar.AddAttacker(instigator, ADD_ATTACKER_DAMAGE)
end, 500)
-- as this bypasses usual npc reaction timer add a bit of delay to make it not instant
end
---@param char1 Barotrauma_Character Character one.
---@param char2 Barotrauma_Character Character two.
---@return boolean charactersAreOnSameTeam True if characters one & two are on the same team, false otherwise.
function isOnSameTeam(char1, char2)
local team1 = char1.TeamID
local team2 = char2.TeamID
return team1 == team2
end
---Updates current rescue targets list, separate so we dont cycle thru all missions every time we apply item to chacter. Use IsRescueTarget(target) after this.
function UpdateRescueTargets()
rescuetargets = {}
for mission in Game.GameSession.Missions do
if LuaUserData.IsTargetType(mission.Prefab.MissionClass, "Barotrauma.AbandonedOutpostMission") then
for character in mission.requireRescue do
rescuetargets[character.ID] = character
--table.insert(rescuetargets, character)
end
end
end
-- print('rescue targets =')
-- for char in rescuetargets do print(char.Name) end
end
---@param target Barotrauma_Character The character we want to confirm as being rescued
---@return boolean consent True if target is rescue mission target, false otherwise
function IsRescueTarget(target)
-- for char in rescuetargets do
-- if target.ID == char.ID then return true end
-- end
if rescuetargets[target.ID] ~= nil then
return true
end
return false
end
---@param user Barotrauma_Character The character who desires consent.
---@param target Barotrauma_Character The character who gives consent
---@return boolean consent True if consent is given, false otherwise.
function hasConsent(user, target)
return isOnSameTeam(user, target) or target.IsEscorted or IsRescueTarget(target) -- No longer needs to be shared.
end
---@param aiChar Barotrauma_Character The (AI but not necessarily) character whose sight is being tested.
---@param target Barotrauma_Character The character to be seen.
---@return boolean aiCanSeeTarget True if the AI can see the target character.
function canAiSeeTarget(aiChar, target)
-- I'll just use what Barotrauma uses for witness line of sight
local aiVisibleHulls = aiChar.GetVisibleHulls()
local targetCurrentHull = target.CurrentHull
for _, visibleHull in pairs(aiVisibleHulls) do
if targetCurrentHull == visibleHull then
return true
end
end
return false
end
---@param user Barotrauma_Character The character of the instigator being witnessed.
---@param victim Barotrauma_Character The character of the victim of the crime being witnessed.
---@return Barotrauma_Character[] Characters that have witnessed the crime.
function getWitnessesToCrime(user, victim)
local witnesses = {}
for _, char in pairs(Character.CharacterList) do
if
not char.Removed
and not char.IsUnconscious
and char.IsBot
and char.IsHuman
and isOnSameTeam(char, victim)
then
local isWitnessingUser = canAiSeeTarget(char, user)
if isWitnessingUser then
table.insert(witnesses, char)
end
end
end
return witnesses
end
---@param user Barotrauma_Character The character that is applying the affected item.
---@param target Barotrauma_Character The character of the target of the affected item's application.
function onAffectedItemApplied(user, target)
if not hasConsent(user, target) and target.IsBot and target.IsHuman then
if not target.IsIncapacitated and target.Stun <= 10 then
makeHostile(target, user)
else
-- Vanilla Barotrauma Human AI doesn't care what you do to their unconscious teammates, even shooting them in the head
-- Let's fix that for this particular case of mistreatment
local witnesses = getWitnessesToCrime(user, target)
for _, witness in pairs(witnesses) do
makeHostile(witness, user)
end
end
end
end
return Environment.Export(_ENV)
@@ -0,0 +1,67 @@
-- User edited configuration file.
local Environment = require("ConsentRequiredExtended.Util.Environment")
local _ENV = Environment.PrepareEnvironment(_ENV)
--------- Start editing here ---------
AffectedItems = {
-- Neurotrauma
-- "healthscanner", --健康扫描仪 -- whats a tiny bit of radiation damage between friends?
"bloodanalyzer", --血液分析仪
"opium", --药用鸦片
"antidama1", --吗啡
"antidama2", --芬太尼
"antibleeding3", --抗生素凝膠
"propofol", -- 异丙酚
"mannitol", -- 甘露醇
"pressuremeds", -- 压力药物
"meth",
"needle",
"adrenaline",
"multiscalpel", -- 多功能手术刀
"advscalpel", -- 手术刀
"advhemostat", -- 止血钳
"advretractors", -- 皮肤牵引器
"tweezers", -- 镊子
"surgicaldrill", -- 骨钻
"surgerysaw", -- 手术锯
"organscalpel_liver", -- 器官切割刀:肝脏
"organscalpel_lungs", -- 器官切割刀:肺
"organscalpel_kidneys", -- 器官切割刀:肾脏
"organscalpel_heart", -- 器官切割刀:心脏
"organscalpel_brain", -- 器官切割刀:大脑
"emptybloodpack", -- 空血袋
"bloodpack",
"alienblood", -- 异星血浆
"tourniquet", -- 止血带
"defibrillator", -- 手动除颤器
"aed", -- 智能除颤器
"bvm", -- 人工呼吸器
"antibiotics", -- 广谱抗生素
"sulphuricacid", -- 硫酸
"divingknife", -- 潜水刀
"divingknifedementonite", -- 攝魂潛水刀
"divingknifehardened", -- 硬化潛水刀
"crowbar", -- 潜水刀
"crowbardementonite", -- 攝魂撬棍
"crowbarhardened", -- 硬化撬棍
"stasisbag", -- 冷藏袋
"autocpr", -- 全自动CPR
-- NeuroEyes
"organscalpel_eyes", -- 器官切割刀:眼睛
-- blahaj 布罗艾鲨鱼
-- "blahaj", -- 布罗艾鲨鱼 -- Blahaj never hurt anyone
-- "blahajplus", -- 大鲨鲨
"blahajplusplus", -- 超大鲨鲨
-- Pharmacy 制药大师
"custompill", -- 自制药丸
"custompill_horsepill", -- 大药丸
"custompill_tablets", -- 药片
-- vanilla 原版
"toyhammer", -- 玩具锤子
}
--------- Stop editing here ---------
return Environment.Export(_ENV)
+40
View File
@@ -0,0 +1,40 @@
-- Do not take my blood or organs without my consent, thanks.
-- Causes AI to get angry at you if you use certain medical items on them.
-- These items are those related to organ and blood removal.
-- This mod is meant to be accompanied by Neurotrauma, and aims to
-- resolve the issue of being freely able to steal blood/organs from
-- neutral NPCs (e.g. outposts, VIPs) without them getting mad at you.
local Api = require("ConsentRequiredExtended.Api")
local OnItemApplied = require("ConsentRequiredExtended.OnItemApplied")
local onMeleeWeaponHandleImpact = require("ConsentRequiredExtended.onMeleeWeaponHandleImpact")
local onHandleProjectileCollision = require("ConsentRequiredExtended.onHandleProjectileCollision")
local Config = require("ConsentRequiredExtended.Config")
local LUA_EVENT_ITEM_APPLYTREATMENT = "item.ApplyTreatment"
local HOOK_NAME_ITEM_APPLYTREATMENT = "ConsentRequiredExtended.onItemApplyTreatment"
local LUA_EVENT_MELEEWEAPON_HANDLEIMPACT = "meleeWeapon.handleImpact"
local HOOK_NAME_MELEEWEAPON_HANDLEIMPACT = "ConsentRequiredExtended.onMeleeWeaponHandleImpact"
local LUA_EVENT_ROUNDSTART = "roundStart"
local HOOK_NAME_UPDATE_RESCUETARGETS = "ConsentRequiredExtended.onUpdateRescueTargets"
-- Set up affected items from config.
for _, affectedItem in pairs(Config.AffectedItems) do
Api.AddAffectedItem(affectedItem)
end
Hook.Add(LUA_EVENT_ITEM_APPLYTREATMENT, HOOK_NAME_ITEM_APPLYTREATMENT, OnItemApplied)
-- damn meleeWeapon
Hook.Add(LUA_EVENT_MELEEWEAPON_HANDLEIMPACT, HOOK_NAME_MELEEWEAPON_HANDLEIMPACT, onMeleeWeaponHandleImpact)
Hook.Add(LUA_EVENT_ROUNDSTART, HOOK_NAME_UPDATE_RESCUETARGETS, Api.UpdateRescueTargets)
Hook.Patch(
"ConsentRequiredExtended.onHandleProjectileCollision",
"Barotrauma.Items.Components.Projectile",
"HandleProjectileCollision",
onHandleProjectileCollision,
Hook.HookMethodType.After
)
@@ -0,0 +1,20 @@
local Api = require("ConsentRequiredExtended.Api")
local function isItemAffected(identifier)
return Api.IsItemAffected(identifier)
end
---@param item Barotrauma_Item Item being applied.
---@param user Barotrauma_Character The character that is applying the item.
---@param target Barotrauma_Character The character of the target of the item's application.
local function OnItemApplied(item, user, target)
if not NTConfig.Get("NTCRE_ConsentRequiredExtra", true) then
return
end
local itemIdentifier = item.Prefab.Identifier.Value
if isItemAffected(itemIdentifier) then
Api.onAffectedItemApplied(user, target)
end
end
return OnItemApplied
@@ -0,0 +1,90 @@
-- Functions for interfacing with Barotrauma.
local Environment = require 'ConsentRequiredExtended.Util.Environment'
local _ENV = Environment.PrepareEnvironment(_ENV)
-- local Clr = require 'ConsentRequiredExtended.Util.Clr'
-- local UserData = require 'ConsentRequiredExtended.Util.UserData'
---Functions related to working with Barotrauma.AttackResult.
AttackResult = {}
---Initialise AttackResults.
-- local function Init_AttackResult()
-- -- Registrations.
-- UserData.RegisterStandardType("System.Reflection.FieldInfo")
-- -- Construct a List<Affliction> generic type.
-- local afflictionsListClrType = Clr.CreateConstructedGenericType("System.Collections.Generic.List`1", "Barotrauma.Affliction")
-- local attackResultAfflictionsField = Clr.GetRawClrType("Barotrauma.AttackResult").GetField("Afflictions")
-- ---Instantiates a new AttackResult with damage and empty afflictions.
-- ---@param damage number An amount of damage.
-- function AttackResult.NewAttackResultFromDamage(damage)
-- -- Instantiate a new AttackResult.
-- local attackResult = _G.AttackResult(damage, nil)
-- -- Instantiate an empty List<Afflictions> (this is to prevent NREs),
-- -- and set it to attackResult.Afflictions. This is a readonly field,
-- -- hence the use of reflection.
-- local afflictionsList = UserData.FromClrType({}, afflictionsListClrType)
-- attackResultAfflictionsField.SetValue(attackResult, afflictionsList)
-- return attackResult
-- end
-- end
---Initialise AttackResults without needing to register system.type and reflections
local function Init_AttackResult()
-- Registrations.
LuaUserData.MakePropertyAccessible(Descriptors['Barotrauma.AttackResult'], 'Damage')
---Instantiates a new AttackResult with damage and empty afflictions.
---@param damage number An amount of damage.
function AttackResult.NewAttackResultFromDamage(damage)
-- I have not noticed any NREs from affliction list being null
-- but just in case here is version which intializes with empty list
-- Also uncomment MakePropertyAccessible Damage above
local attackResult = _G.AttackResult({}, nil, {})
attackResult.Damage = damage
--local attackResult = _G.AttackResult(damage)
return attackResult
end
end
---Runs at start-up, handles registrations, etc.
function Init()
Init_AttackResult()
end
function Test()
local errors = {}
local function AssertEquals(testDescription, expected, got)
if expected ~= got then
local errorString = string.format(
"Test Error: %s\n\texpected = %s\n\tgot = %s",
testDescription,
tostring(expected),
tostring(got)
)
table.insert(errors, errorString)
end
end
local atkRes = AttackResult.NewAttackResultFromDamage(10)
AssertEquals("atkRes.Damage", 10, atkRes.Damage)
AssertEquals("atkRes.Afflictions is null", true, atkRes.Afflictions ~= nil)
AssertEquals("#atkRes.Afflictions is non-zero", 0, #atkRes.Afflictions)
if #errors == 0 then
print("Tests successful")
else
for _, err in pairs(errors) do
print(err)
end
end
end
Init()
return Environment.Export(_ENV)
@@ -0,0 +1,74 @@
-- Functions for working with CLR types.
local Environment = require 'ConsentRequiredExtended.Util.Environment'
local _ENV = Environment.PrepareEnvironment(_ENV)
local UserData = require 'ConsentRequiredExtended.Util.UserData'
---Construct ClrType wrapper table for CLR types.
---@param underlyingType userdata The underlying type (CLR type: System.Type).
---@return ClrType Wrapper table for working with CLR types.
local function New(underlyingType)
---@class ClrType
local clrType = {}
---Instantiate an object for the given CLR type.
---@return any An instance of the CLR type. Actual Lua type depends on MoonSharp conversions.
function clrType:Instantiate()
-- TODO: Implement args.
return underlyingType.Assembly.CreateInstance(underlyingType.FullName)
end
---Get the underlying type as a raw CLR type object.
---@return userdata The raw underlying type (CLR type: System.Type).
function clrType:GetUnderlyingType()
return underlyingType
end
---Get the full name of the CLR type.
---@return string The full name of the type.
function clrType:GetFullName()
return underlyingType.FullName
end
return clrType
end
---Create a constructed generic type.
---@param genericTypeName string The name of the generic type to construct and register.
---@vararg string
---@return ClrType The constructed generic type.
function CreateConstructedGenericType(genericTypeName, ...)
local genericTypeArgumentsString = table.pack(...)
local genericTypeArgumentsType = {}
for _, typeString in pairs(genericTypeArgumentsString) do
table.insert(genericTypeArgumentsType, GetRawClrType(typeString))
end
local genericTypeDefinition = GetRawClrType(genericTypeName)
local constructedGenericType = genericTypeDefinition.MakeGenericType(table.unpack(genericTypeArgumentsType))
return New(constructedGenericType)
end
---Get a System.Type object from a type name.
---@param typeName string Name of the type.
---@return userdata The type object (CLR type: System.Type).
function GetRawClrType(typeName)
return LuaUserData.GetType(typeName)
end
---Get a ClrType instance wrapping a Type object that matches the type name.
---@param typeName string Name of the type.
---@return ClrType The CLR type.
function GetClrType(typeName)
return New(GetRawClrType(typeName))
end
local function Init()
UserData.RegisterStandardType("System.Type")
UserData.RegisterStandardType("System.Reflection.RuntimeAssembly")
end
Init()
return Environment.Export(_ENV)
@@ -0,0 +1,32 @@
-- Functions for managing the mod's environment.
---Isolate a function or module's environment from Global.
---@param env table The _ENV table.
local function prepareEnvironment(env)
return setmetatable(
{},
{
__index = _G,
}
)
end
local _ENV = prepareEnvironment(_ENV)
PrepareEnvironment = prepareEnvironment
---Create an empty table whose metatable indexes non-local variables declared within
---a function/module's environment, and is immutable to any changes.
---@param env table The _ENV table.
---@return table Empty table that interfaces with _ENV.
function Export(env)
return setmetatable(
{},
{
__index = function(t, k) return env[k] end,
__newindex = function() error("Attempted to modify a protected table.") end
}
)
end
return Export(_ENV)
@@ -0,0 +1,35 @@
-- Functions for creating userdata.
local Environment = require 'ConsentRequiredExtended.Util.Environment'
local _ENV = Environment.PrepareEnvironment(_ENV)
---Create a userdata that references the type in a static context.
---@param clrTypeName string The name of the type to point to.
---@return userdata A userdata that references the type in a static context.
function FromStringStatic(clrTypeName)
LuaUserData.CreateStatic(clrTypeName)
end
---Create a userdata that references the type described by a ClrType in a static context.
---@param clrType ClrType ClrType that describes the type being referenced.
---@return userdata A userdata that references the type in a static context.
function FromClrTypeStatic(clrType)
return FromStringStatic(clrType:GetFullName())
end
---Create a userdata that references an instance of a CLR type with conversion.
---@param value any A Lua value to convert to a CLR object of the given type and wrap up in a userdata.
---@param clrType ClrType The CLR type to instantiate and wrap in the userdata.
---@return userdata A userdata that references the an instance of the CLR type.
function FromClrType(value, clrType)
return LuaUserData.CreateUserDataFromType(value, clrType:GetUnderlyingType())
end
---Register standard types with MoonSharp. For generics use RegisterClrType.ConstructedGenericType.
---@param typeName string Name of the type to register.
function RegisterStandardType(typeName)
local desc = LuaUserData.RegisterType(typeName)
_G.Descriptors[typeName] = desc
end
return Environment.Export(_ENV)
+18
View File
@@ -0,0 +1,18 @@
local SRC_NAMESPACE = "ConsentRequiredExtended."
local MAIN = "Main"
local LUA_EVENT_LOADED = "loaded"
local HOOK_NAME_ON_LOADED = "ConsentRequiredExtended.onLoaded"
if Game.IsMultiplayer and CLIENT then return end
local function onLoaded()
-- Only run client side if not multiplayer
---@diagnostic disable-next-line: undefined-global
-- if Game.IsMultiplayer and CLIENT then return end
local requireStr = SRC_NAMESPACE .. MAIN
require(requireStr)
end
Hook.Add(LUA_EVENT_LOADED, HOOK_NAME_ON_LOADED, onLoaded)
@@ -0,0 +1,39 @@
local Api = require("ConsentRequiredExtended.Api")
local function isItemAffected(identifier)
return Api.IsItemAffected(identifier)
end
local function onHandleProjectileCollision(projectile, ptable)
if not ptable.ReturnValue then
return
end
if not NTConfig.Get("NTCRE_ConsentRequiredExtra", true) then
return
end
if not isItemAffected(projectile.Item.Prefab.Identifier.Value) then
return
end
if projectile.User == nil then
return
end
local target = ptable["target"]
if target.Body == nil or target.Body.UserData == nil then
return
end
local targetUserData = target.Body.UserData
local targetUser = nil
if LuaUserData.IsTargetType(targetUserData, "Barotrauma.Limb") then
targetUser = targetUserData.character
elseif LuaUserData.IsTargetType(targetUserData, "Barotrauma.Character") then
targetUser = targetUserData
end
if targetUser ~= nil then
Api.onAffectedItemApplied(projectile.User, targetUser)
end
end
return onHandleProjectileCollision
@@ -0,0 +1,38 @@
local Api = require("ConsentRequiredExtended.Api")
local function isItemAffected(identifier)
return Api.IsItemAffected(identifier)
end
---@param meleeweapon Weapon target
---@param target The target of the hit could be a limb or just a character.
local function onMeleeWeaponHandleImpact(meleeweapon, target)
if not NTConfig.Get("NTCRE_ConsentRequiredExtra", true) then
return
end
if meleeweapon == nil or target == nil then
return
end
local itemIdentifier = meleeweapon.item.Prefab.Identifier.Value
if isItemAffected(itemIdentifier) then
local user = meleeweapon.picker
if user == nil then
return
end
local targetUserData = target.UserData
if targetUserData == nil then
return
end
local targetUser = nil
if LuaUserData.IsTargetType(targetUserData, "Barotrauma.Limb") then
targetUser = targetUserData.character
elseif LuaUserData.IsTargetType(targetUserData, "Barotrauma.Character") then
targetUser = targetUserData
end
if targetUser ~= nil then
Api.onAffectedItemApplied(user, targetUser)
end
end
end
return onMeleeWeaponHandleImpact
@@ -0,0 +1,56 @@
-- why barotrauma's GUI libraries don't have this implemented by default? this is stupid
local function updateServerMessageScrollBasedOnCaret(textBox, listBox)
local caretY = textBox.CaretScreenPos.Y
local bottomCaretExtent = textBox.Font.LineHeight * 1.5
local topCaretExtent = -textBox.Font.LineHeight * 0.5
if caretY + bottomCaretExtent > listBox.Rect.Bottom then
listBox.ScrollBar.BarScroll = (caretY - textBox.Rect.Top - listBox.Rect.Height + bottomCaretExtent)
/ (textBox.Rect.Height - listBox.Rect.Height)
elseif caretY + topCaretExtent < listBox.Rect.Top then
listBox.ScrollBar.BarScroll = (caretY - textBox.Rect.Top + topCaretExtent)
/ (textBox.Rect.Height - listBox.Rect.Height)
end
end
local function CreateMultiLineTextBox(rectransform, text, size)
local multineListBox = GUI.ListBox(GUI.RectTransform(Vector2(1, size or 0.2), rectransform))
local textBox = GUI.TextBox(
GUI.RectTransform(Vector2(1, 1), multineListBox.Content.RectTransform),
text,
nil,
nil,
nil,
true,
"GUITextBoxNoBorder"
)
textBox.add_OnSelected(function()
updateServerMessageScrollBasedOnCaret(textBox, multineListBox)
end)
textBox.OnTextChangedDelegate = function()
local textSize = textBox.Font.MeasureString(textBox.WrappedText)
textBox.RectTransform.NonScaledSize =
Point(textBox.RectTransform.NonScaledSize.X, math.max(multineListBox.Content.Rect.Height, textSize.Y + 10))
multineListBox.UpdateScrollBarSize()
return true
end
textBox.OnEnterPressed = function()
local str = textBox.Text
local caretIndex = textBox.CaretIndex
textBox.Text = str:sub(1, caretIndex) .. "\n" .. str:sub(caretIndex + 1)
textBox.CaretIndex = caretIndex + 1
return true
end
return textBox
end
return CreateMultiLineTextBox
+580
View File
@@ -0,0 +1,580 @@
--easysettings by Evil Factory
local easySettings = dofile(NT.Path .. "/Lua/Scripts/Client/easysettings.lua")
local MultiLineTextBox = dofile(NT.Path .. "/Lua/Scripts/Client/MultiLineTextBox.lua")
local GUIComponent = LuaUserData.CreateStatic("Barotrauma.GUIComponent")
local configUI
local BaseConfigPages = {
{ name = "Item Prices", key = "prices" },
{ name = "Item Availability", key = "availability" },
}
-- Did you know the default background colour is not true black?
local Transparent = Color(2, 2, 2)
-- Barotrauma beige
local DefaultTextColour = Color(210, 200, 154)
local ExpansionNameForUI = {}
local function CommaStringToTable(str)
local tbl = {}
for word in string.gmatch(str, "([^,]+)") do
table.insert(tbl, word)
end
return tbl
end
-- Function that autofills the addon dropdown
-- We also automatically make the UI display the names as "NT [mod]" to match their Content Package / Steam Workhop name to minimise confusion.
local function PopulateDropdown(dropdown)
ExpansionNameForUI = {}
-- Goofy but it works!
-- Ensure Neurotrauma is on top by just loading it first
for _, expansion in ipairs(NTConfig.Expansions) do
if expansion.Name == "Neurotrauma" then
local name = tostring(expansion.Name)
local uiName = name
table.insert(ExpansionNameForUI, { uiName, name, type = "expansion" })
dropdown.AddItem(uiName)
end
end
-- Then get all the base pages (expansion proof!)
for _, page in ipairs(BaseConfigPages) do
table.insert(ExpansionNameForUI, { page.name, page.key, type = "page" })
dropdown.AddItem(page.name)
end
-- Then dump all other expansions in there
for _, expansion in ipairs(NTConfig.Expansions) do
if expansion.Name ~= "Neurotrauma" then
local name = tostring(expansion.Name)
-- First, we make the UI Name the same as their expansion name
local uiName = name
-- Add NT at the front if it does not already have that
if not string.find(uiName, "^NT%s") then uiName = "NT " .. uiName end
-- Add them to a lookup table for later
table.insert(ExpansionNameForUI, { uiName, name, type = "expansion" })
-- Finally, add it to the dropdown menu
dropdown.AddItem(uiName)
end
end
end
-- TODO: Readd the difficulty calculation I kinda dont care about it right now so it can fuck off
-- Function to determine how the layout should be structured
local function PrebuildConfigLayout(entries, selectedExpansion)
-- This table will contain subtables that determine how the ConstructUI function actually 'constructs' the UI.
-- These chunks can be gone over using Ipairs to ensure the order is correct (a previous test made unrelated settings render together)
local LayoutChunks = {}
local CurrentGroup = nil
local lastType = nil
local selectedKey = nil
local selectedType = nil
-- Grab actual expansion name based on their UIName
for _, uiEntry in ipairs(ExpansionNameForUI) do
if uiEntry[1] == selectedExpansion then
selectedKey = uiEntry[2]
selectedType = uiEntry.type
break
end
end
for key, entry in pairs(entries) do
if selectedType == "page" then
-- Get entries with this page set
if not entry.page or entry.page ~= selectedKey then goto continue end
elseif selectedType == "expansion" then
-- Get expansion entries that do NOT have a page set (so other expansion can force their price changes into the combined page, for example)
if entry.expansion ~= selectedKey or entry.page ~= nil then goto continue end
end
-- Automatically add some white space between unique item types to prevent bleeding
if entry.type ~= lastType and lastType ~= nil then table.insert(LayoutChunks, { type = "spacer" }) end
lastType = entry.type
-- Categories are always standalone and should be put above the settings they govern
-- Interrupt a group if it exists
if entry.type == "category" then
CurrentGroup = nil
table.insert(LayoutChunks, { type = "category", entry = entry })
-- Floats / Strings until now were just using up way too much screen space. We want to put these into a LayoutGroup together to maximise screen usage.
-- If entries have 'group=true', then all entries of the same type that follow one another will be grouped together. If there are other entry types (like categories) in-between, they will not.
-- This prevents unrelated settings from merging together + adds reverse-compat
elseif entry.type == "float" and entry.group then
-- Make a group and keep adding config entries that come after this as long as the criteria stands
if not CurrentGroup or CurrentGroup.type ~= "float_group" then
CurrentGroup = { type = "float_group", items = {} }
table.insert(LayoutChunks, CurrentGroup)
end
table.insert(CurrentGroup.items, { key = key, entry = entry })
elseif entry.type == "string" and entry.group then
if not CurrentGroup or CurrentGroup.type ~= "string_group" then
CurrentGroup = { type = "string_group", items = {} }
table.insert(LayoutChunks, CurrentGroup)
end
table.insert(CurrentGroup.items, { key = key, entry = entry })
-- Anything else is anything that shouldn't be grouped. Interrupt a group if it exists and add the setting as standalone.
else
CurrentGroup = nil
table.insert(LayoutChunks, { type = "standalone", key = key, entry = entry })
end
::continue::
end
return LayoutChunks
end
local function PopulateSettingsIntoUI(list, selectedExpansion)
list.Content:ClearChildren()
-- Infotext (should always be shown)
local tb_BasicConfigInfo = GUI.TextBlock(
GUI.RectTransform(Vector2(1, 0.1), list.Content.RectTransform),
"Server config can be changed by owner or a client with manage settings permission. If the server doesn't allow writing into the config folder, then it must be edited manually.",
DefaultTextColour,
nil,
GUI.Alignment.Center,
true,
nil,
Transparent
)
-- Prevent textblocks from lighting up the background when clicked / changing the cursor on hover.
tb_BasicConfigInfo.CanBeFocused = false
local LayoutChunks = PrebuildConfigLayout(NTConfig.Entries, selectedExpansion)
-- Go over the pre-generated Layout table and construct settings procedurally
for _, chunk in ipairs(LayoutChunks) do
-- Categories
if chunk.type == "category" then
local tb_ProceduralCategoryHeader = GUI.TextBlock(
GUI.RectTransform(Vector2(1, 0.09), list.Content.RectTransform),
chunk.entry.name,
DefaultTextColour,
GUI.GUIStyle.LargeFont,
GUI.Alignment.BottomCenter,
true,
nil,
Transparent
)
tb_ProceduralCategoryHeader.CanBeFocused = false
-- Standalone items
elseif chunk.type == "standalone" then
local key = chunk.key
local entry = chunk.entry
-- Ungrouped float
if entry.type == "float" then
local minrange = (entry.range and entry.range[1]) or ""
-- This is fucking stupid. If the default value is the same as the minimum value of a scalar, it bugs and displays 0.
-- So, we set the minimum to 0.99 and just render it like it were 1
if minrange == 0.99 then minrange = 1 end
local maxrange = (entry.range and entry.range[2]) or ""
local rect = GUI.RectTransform(Vector2(1, 0.04), list.Content.RectTransform)
local tb_EntryInformation = GUI.TextBlock(
rect,
entry.name .. " (" .. minrange .. "-" .. maxrange .. ")",
DefaultTextColour,
nil,
GUI.Alignment.Center,
true,
nil,
Transparent
)
tb_EntryInformation.CanBeFocused = false
if entry.description then tb_EntryInformation.ToolTip = entry.description end
local scalar =
GUI.NumberInput(GUI.RectTransform(Vector2(1, 0.08), list.Content.RectTransform), NumberType.Float)
scalar.valueStep = 0.1
scalar.MinValueFloat = entry.range and entry.range[1] or 0
scalar.MaxValueFloat = entry.range and entry.range[2] or 100
scalar.FloatValue = NTConfig.Get(key, 1)
scalar.OnValueChanged = function()
NTConfig.Set(key, scalar.FloatValue)
end
-- Bool
elseif entry.type == "bool" then
local rect = GUI.RectTransform(Vector2(0.5, 1), list.Content.RectTransform)
local toggle = GUI.TickBox(rect, entry.name)
if entry.description then toggle.ToolTip = entry.description end
toggle.Selected = NTConfig.Get(key, false)
toggle.OnSelected = function()
NTConfig.Set(key, toggle.State == GUIComponent.ComponentState.Selected)
end
-- String (a textblock to input)
elseif entry.type == "string" then
local style = ""
if entry.style ~= nil then style = " (" .. entry.style .. ")" end
local rect = GUI.RectTransform(Vector2(1, 0.05), list.Content.RectTransform)
local tb_StringInformation = GUI.TextBlock(
rect,
entry.name .. style,
DefaultTextColour,
nil,
GUI.Alignment.Center,
true,
nil,
Transparent
)
-- By default, don't change cursor on hovering
tb_StringInformation.CanBeFocused = false
-- If there's a tooltip, set it and re-enable hovering
if entry.description then
tb_StringInformation.ToolTip = entry.description
tb_StringInformation.CanBeFocused = true
end
local stringinput
-- Make MultiLineTextBox the default, but now allow normal ones too. A single line entry does not need a multi-line textblock.
if entry.noMLTB == true then
stringinput = GUI.TextBox(
GUI.RectTransform(Vector2(1, entry.boxsize), list.Content.RectTransform),
"",
nil,
nil,
nil,
true
)
else
stringinput = MultiLineTextBox(list.Content.RectTransform, "", entry.boxsize)
end
stringinput.Text = table.concat(entry.value, ",")
stringinput.OnTextChangedDelegate = function(textBox)
entry.value = CommaStringToTable(textBox.Text)
end
end
-- Auto-added empty space
elseif chunk.type == "spacer" then
GUI.LayoutGroup(GUI.RectTransform(Vector2(1, 0.02), list.Content.RectTransform), false)
-- Grouped Floats
elseif chunk.type == "float_group" then
local MaxPerRow = 2
local row = nil
local count = 0
for _, item in ipairs(chunk.items) do
-- Make a new LayoutGroup to add entries into everytime the MaxPerRow is hit (default of 2)
if not row or (count % MaxPerRow == 0) then
row = GUI.LayoutGroup(GUI.RectTransform(Vector2(1, 0.09), list.Content.RectTransform), true)
end
-- Safety check!
if not row then return end
-- This determines how the space in the UI is used, tied together to make fucking around a bit easier
row.RelativeSpacing = 0.01
local Text_space = 0.53
local Scalar_space = 0.30
local Reset_space = 0.07
local baseWidth = 1 / MaxPerRow
local textcellwidth = baseWidth * Text_space
local scalarcellwidth = baseWidth * Scalar_space
local resetcellwidth = baseWidth * Reset_space
local key = item.key
local entry = item.entry
local resetButton
-- Make each subdivided part of the group their own
-- Part of the group that holds text
local textcell = GUI.LayoutGroup(GUI.RectTransform(Vector2(textcellwidth, 1), row.RectTransform), false)
-- Part of the group that holds the numberinput box
local scalarcell =
GUI.LayoutGroup(GUI.RectTransform(Vector2(scalarcellwidth, 1), row.RectTransform), false)
-- In case a reset button is set
local resetbuttoncell =
GUI.LayoutGroup(GUI.RectTransform(Vector2(resetcellwidth, 0.59), row.RectTransform), false)
-- Relativespacing but larger space for the center instead of everywhere
local additionalspacecell =
GUI.LayoutGroup(GUI.RectTransform(Vector2(resetcellwidth, 0.59), row.RectTransform), false)
local minrange = entry.range and entry.range[1] or ""
if minrange == 0.99 then minrange = 1 end
local maxrange = entry.range and entry.range[2] or ""
local tb_EntryInformation = GUI.TextBlock(
GUI.RectTransform(Vector2(1, 0.7), textcell.RectTransform),
entry.name .. " (" .. minrange .. "-" .. maxrange .. ")",
DefaultTextColour,
nil,
GUI.Alignment.Center,
true,
nil,
Transparent
)
tb_EntryInformation.CanBeFocused = false
local scalar =
GUI.NumberInput(GUI.RectTransform(Vector2(1, 0.6), scalarcell.RectTransform), NumberType.Float)
scalar.PlusButton.RectTransform.RelativeSize = Vector2(1, 0.5)
scalar.MinusButton.RectTransform.RelativeSize = Vector2(1, 0.5)
scalar.valueStep = 0.1
scalar.MinValueFloat = entry.range and entry.range[1] or 0
scalar.MaxValueFloat = entry.range and entry.range[2] or 100
scalar.FloatValue = NTConfig.Get(key, 1)
scalar.OnValueChanged = function()
NTConfig.Set(key, scalar.FloatValue)
end
-- Leftover space in the Row goes to the reset button if enabled
if entry.resettable then
resetButton = GUI.Button(
GUI.RectTransform(Vector2(1, 1), resetbuttoncell.RectTransform, GUI.Anchor.BottomLeft),
GUI.Alignment.BottomLeft,
nil,
Transparent
)
-- Give the reset button a sprite that matches its function (yoinked from basegame)
local resetButtonStyle =
GUI.Image(GUI.RectTransform(Vector2(1, 1), resetButton.RectTransform), "GUIButtonRefresh")
resetButtonStyle.ToolTip = "Reset to default"
-- On button press, fetch default value.
resetButton.OnClicked = function()
local defaultValue = entry.default
scalar.FloatValue = defaultValue
NTConfig.Set(key, defaultValue)
end
end
count = count + 1
end
-- Grouped Strings
elseif chunk.type == "string_group" then
local MaxPerRow = 2
local row = nil
local count = 0
for _, item in ipairs(chunk.items) do
-- Make a new LayoutGroup to add entries into everytime the MaxPerRow is hit (default of 2)
if not row or (count % MaxPerRow == 0) then
row = GUI.LayoutGroup(GUI.RectTransform(Vector2(1, 0.09), list.Content.RectTransform), true)
end
-- Safety check!
if not row then return end
row.RelativeSpacing = 0.01
local Text_space = 0.50
-- Any less than 0.33 per string and the max value (255,255,255) or 3 digits per value will bleed (at 1920 * 1080 default)
local String_space = 0.33
local Reset_space = 0.07
local baseWidth = 1 / MaxPerRow
local textcellwidth = baseWidth * Text_space
local scalarcellwidth = baseWidth * String_space
local resetcellwidth = baseWidth * Reset_space
local key = item.key
local entry = item.entry
local resetButton
-- Make each subdivided part of the group their own
-- Part of the group that holds text
local textcell = GUI.LayoutGroup(GUI.RectTransform(Vector2(textcellwidth, 1), row.RectTransform), false)
-- Part of the group that holds the input box
local stringinputcell =
GUI.LayoutGroup(GUI.RectTransform(Vector2(scalarcellwidth, 1), row.RectTransform), false)
-- In case a reset button is set
local resetbuttoncell =
GUI.LayoutGroup(GUI.RectTransform(Vector2(resetcellwidth, 0.45), row.RectTransform), false)
-- Relativespacing but larger space for the center instead of everywhere
local additionalspacecell =
GUI.LayoutGroup(GUI.RectTransform(Vector2(resetcellwidth, 0.59), row.RectTransform), false)
local style = ""
if entry.style ~= nil then style = " (" .. entry.style .. ")" end
local tb_StringInformation = GUI.TextBlock(
GUI.RectTransform(Vector2(1, 0.4), textcell.RectTransform),
entry.name .. style,
DefaultTextColour,
nil,
GUI.Alignment.Center,
true,
nil,
Transparent
)
tb_StringInformation.CanBeFocused = false
if entry.description then
tb_StringInformation.ToolTip = entry.description
tb_StringInformation.CanBeFocused = true
end
local stringinput
-- Not all strings need a MultiLineTextBox, especially since they can just yoink the mouse cursor while scrolling.
-- Make MLTB's a toggleable option
if entry.noMLTB == true then
stringinput = GUI.TextBox(
GUI.RectTransform(
Vector2(1, entry.boxsize),
stringinputcell.RectTransform,
GUI.Anchor.CenterLeft
),
"",
nil,
nil,
nil,
true
)
else
stringinput = MultiLineTextBox(stringinputcell.RectTransform, "", entry.boxsize)
end
stringinput.Text = table.concat(entry.value, ",")
stringinput.OnTextChangedDelegate = function(textBox)
entry.value = CommaStringToTable(textBox.Text)
end
-- Leftover space in the Row goes to the reset button if enabled
if entry.resettable then
resetButton = GUI.Button(
GUI.RectTransform(Vector2(1, 1), resetbuttoncell.RectTransform),
GUI.Alignment.CenterRight,
nil,
Transparent
)
-- Give the reset button a sprite that matches its function (yoinked from basegame)
local resetButtonStyle =
GUI.Image(GUI.RectTransform(Vector2(1, 1), resetButton.RectTransform), "GUIButtonRefresh")
resetButtonStyle.ToolTip = "Reset to default"
-- On button press, fetch default value.
resetButton.OnClicked = function()
entry.value = entry.default
stringinput.Text = table.concat(entry.value, ",")
end
end
count = count + 1
end
end
end
if Game.IsMultiplayer and not Game.Client.HasPermission(ClientPermissions.ManageSettings) then
for guicomponent in list.GetAllChildren() do
guicomponent.enabled = false
end
end
return list
end
-- Base UI construction
local function ConstructUI(parent)
-- Set the default to display (Neurotrauma, duh)
local selectedExpansion = BaseConfigPages[1].name
local list = easySettings.BasicList(parent)
-- Get the Title block to put the drop down menu into (could be moved tbh)
local innerLayout = list.Parent
local children = innerLayout.RectTransform.Children
local title = children[1].GUIComponent
-- Get the amount of loaded expansions + seperate pages
local dropdownheight = #NTConfig.Expansions + #BaseConfigPages
-- Don't show the dropdown if we only have Neurotrauma or no other addons that have settings to show
if dropdownheight > 1 then
local dropdown_AddonSelection = GUI.DropDown(
GUI.RectTransform(Vector2(0.18, 1), title.RectTransform),
"",
dropdownheight - 2,
nil,
false,
false,
GUI.Alignment.CenterLeft
)
dropdown_AddonSelection.ListBox.RectTransform.RelativeOffset = (Vector2(0, 0.5))
PopulateDropdown(dropdown_AddonSelection)
dropdown_AddonSelection.Select(0)
selectedExpansion = dropdown_AddonSelection.Text
dropdown_AddonSelection.ToolTip = "Choose which mod's settings to display."
-- Using the dropdown changes a variable so we can change the page accordingly; only do so if we're not already on that page
dropdown_AddonSelection.OnSelected = function(guiComponent)
local newSelection = tostring(guiComponent.Text)
-- Check if changed
if newSelection == selectedExpansion then return end
selectedExpansion = newSelection
-- Redo content based on new selection
PopulateSettingsIntoUI(list, selectedExpansion)
end
end
-- Default
PopulateSettingsIntoUI(list, selectedExpansion)
end
Networking.Receive("NT.ConfigUpdate", function(msg)
NTConfig.ReceiveConfig(msg)
if configUI == nil then return end
if configUI.RectTransform == nil then return end
local parent = configUI.RectTransform.Parent
configUI = nil
configUI = ConstructUI(parent)
end)
easySettings.AddMenu("Neurotrauma", function(parent)
if Game.IsMultiplayer then
local msg = Networking.Start("NT.ConfigRequest")
Networking.Send(msg)
end
configUI = ConstructUI(parent)
end)
+114
View File
@@ -0,0 +1,114 @@
local _Int32 = LuaUserData.RegisterType("System.Int32")
-- Stack specific items together; if you want only the left leg to be cheaper than a right one go fuck yourself
local ItemVariants = {
antibloodloss2 = "NT_ItemPrice_bloodpacks",
bloodpackoplus = "NT_ItemPrice_bloodpacks",
bloodpackaminus = "NT_ItemPrice_bloodpacks",
bloodpackaplus = "NT_ItemPrice_bloodpacks",
bloodpackbminus = "NT_ItemPrice_bloodpacks",
bloodpackbplus = "NT_ItemPrice_bloodpacks",
bloodpackabminus = "NT_ItemPrice_bloodpacks",
bloodpackabplus = "NT_ItemPrice_bloodpacks",
rarm = "NT_ItemPrice_arms",
larm = "NT_ItemPrice_arms",
rleg = "NT_ItemPrice_legs",
lleg = "NT_ItemPrice_legs",
rarmp = "NT_ItemPrice_bionicarms",
larmp = "NT_ItemPrice_bionicarms",
rlegp = "NT_ItemPrice_bioniclegs",
llegp = "NT_ItemPrice_bioniclegs",
}
-- Fetch config multipliers
local function GetItemMultiplier(identifier)
if identifier == nil then return 1.0 end
-- Add grouping so blood bags don't fucking kill me
local configKey = ItemVariants[identifier] or ("NT_ItemPrice_" .. identifier)
if NTConfig.Entries[configKey] == nil then return 1.0 end
local value = NTConfig.Get(configKey, 1.0)
return tonumber(value) or 1.0
end
-- PRICE CHANGING
-- Hook into the price-determining function and add a multiplier
Hook.Patch("Barotrauma.Location+StoreInfo", "GetAdjustedItemBuyPrice", function(instance, ptable)
local item = ptable["item"]
if item == nil or item.Identifier == nil then return end
local id = item.Identifier.Value
local mult = GetItemMultiplier(id)
-- Don't do extra math if the item value is unchanged
if mult == 1.0 then return end
-- Get the 'actual price' after the game is done with it's calculations
local base = ptable.OriginalReturnValue
if base == nil then return end
-- Apply config-determined multiplier
local result = math.floor(base * mult + 0.5)
ptable.ReturnValue = LuaUserData.CreateUserDataFromDescriptor(result, _Int32)
end, Hook.HookMethodType.After)
-- FABRICATOR CHANGES
-- You cannot fabricate the item; this is accomplished by taking the Filter that gets made whenever you open a fabricator, and hiding the specific item IDs we want to hide.
local fabricatorType = LuaUserData.RegisterType("Barotrauma.Items.Components.Fabricator")
LuaUserData.MakeFieldAccessible(fabricatorType, "itemList")
Hook.Patch("Barotrauma.Items.Components.Fabricator", "FilterEntities", function(instance, ptable)
Timer.Wait(function()
local blockedItems = HF.DynamicUnavailableItems()
for child in instance.itemList.Content.Children do
local recipe = child.UserData
if recipe and LuaUserData.IsTargetType(recipe, "Barotrauma.FabricationRecipe") then
local id = recipe.TargetItem.Identifier.Value
if blockedItems[id] then child.Visible = false end
end
end
end, 1)
end, Hook.HookMethodType.After)
-- STORE CHANGES
-- You cannot buy the item; we simply hook into store availability and hide the item.
-- Ensure the items CANNOT be specials.
local storeType = LuaUserData.RegisterType("Barotrauma.Store")
LuaUserData.MakeFieldAccessible(storeType, "storeBuyList")
LuaUserData.MakeFieldAccessible(storeType, "storeDailySpecialsGroup")
Hook.Patch("Barotrauma.Store", "FilterStoreItems", {
"Barotrauma.MapEntityCategory",
"System.String",
}, function(instance, ptable)
Timer.Wait(function()
-- Fetch items to hide
local blockedItems = HF.DynamicUnavailableItems()
local storeList = instance.storeBuyList
if storeList then
for child in storeList.Content.Children do
local item = child.UserData
if item and item.ItemPrefab then
local id = item.ItemPrefab.Identifier.Value
-- Hide items within the table every refresh
if blockedItems[id] then child.Visible = false end
end
end
end
end, 10)
end, Hook.HookMethodType.After)
-- Force config sync on level swap to ensure things go properly
Hook.Add("roundStart", "forcesyncconfig", function()
if Game.IsMultiplayer then
local msg = Networking.Start("NT.ConfigRequest")
Networking.Send(msg)
end
end)
+209
View File
@@ -0,0 +1,209 @@
-- Original code by Evil Factory,
-- Adapted for Neurotrauma
local easySettings = {}
easySettings.Settings = {}
local GUIComponent = LuaUserData.CreateStatic("Barotrauma.GUIComponent")
local function GetChildren(comp)
local tbl = {}
for value in comp.GetAllChildren() do
table.insert(tbl, value)
end
return tbl
end
easySettings.SaveTable = function(path, tbl)
File.Write(path, json.serialize(tbl))
end
easySettings.LoadTable = function(path)
if not File.Exists(path) then return {} end
return json.parse(File.Read(path))
end
easySettings.AddMenu = function(name, onOpen)
table.insert(easySettings.Settings, { Name = name, OnOpen = onOpen })
end
-- Overhauled Config GUI
easySettings.BasicList = function(parent, size)
-- Menu Frame
local menuContent =
GUI.Frame(GUI.RectTransform(size or Vector2(0.5, 0.8), parent.RectTransform, GUI.Anchor.Center), "GUIFrame")
-- Main Layout
local mainLayout = GUI.LayoutGroup(
GUI.RectTransform(Vector2(0.95, 0.95), menuContent.RectTransform, GUI.Anchor.Center, GUI.Pivot.Center),
false
)
-- Background
local configBackground = GUI.Frame(GUI.RectTransform(Vector2(1, 0.95), mainLayout.RectTransform), "InnerFrame")
-- Shrink Inner layout
local innerLayout = GUI.LayoutGroup(
GUI.RectTransform(Vector2(0.95, 0.95), configBackground.RectTransform, GUI.Anchor.TopCenter),
false
)
-- Title block
local title = GUI.TextBlock(
GUI.RectTransform(Vector2(1, 0.07), innerLayout.RectTransform),
"Neurotrauma Config Settings",
nil,
GUI.GUIStyle.LargeFont
)
title.TextAlignment = GUI.Alignment.TopCenter
-- Setting list
local menuList = GUI.ListBox(GUI.RectTransform(Vector2(1, 0.97), innerLayout.RectTransform))
menuList.Padding = Vector4(10, 15, 10, 10)
menuList.UpdateDimensions()
-- Button row
local buttonRow = GUI.LayoutGroup(GUI.RectTransform(Vector2(1, 0.1), mainLayout.RectTransform), true)
buttonRow.RelativeSpacing = 0.02
easySettings.SaveButton(buttonRow)
easySettings.CloseButton(buttonRow)
easySettings.ResetButton(buttonRow)
return menuList
end
-- Function for a Tickbox
easySettings.TickBox = function(parent, text, onSelected, state)
if state == nil then state = true end
local tickBox = GUI.TickBox(GUI.RectTransform(Vector2(1, 0.2), parent.RectTransform), text)
tickBox.Selected = state
tickBox.OnSelected = function()
onSelected(tickBox.State == GUIComponent.ComponentState.Selected)
end
return tickBox
end
-- Function for a Slider
easySettings.Slider = function(parent, min, max, onSelected, value)
local scrollBar = GUI.ScrollBar(GUI.RectTransform(Vector2(1, 0.1), parent.RectTransform), 0.1, nil, "GUISlider")
scrollBar.Range = Vector2(min, max)
scrollBar.BarScrollValue = value or max / 2
scrollBar.OnMoved = function()
onSelected(scrollBar.BarScrollValue)
end
return scrollBar
end
-- Function for the Save and Exit button
easySettings.SaveButton = function(parent)
local button = GUI.Button(
GUI.RectTransform(Vector2(0.32, 0.05), parent.RectTransform, GUI.Anchor.BottomLeft),
"Save and Exit",
GUI.Alignment.Center,
"GUIButton"
)
button.OnClicked = function()
if Game.IsMultiplayer and Game.Client.HasPermission(ClientPermissions.ManageSettings) then
NTConfig.SendConfig()
elseif Game.IsSingleplayer then
NTConfig.SaveConfig()
end
GUI.GUI.TogglePauseMenu()
end
return button
end
-- Function for the Discard and Exit button
easySettings.CloseButton = function(parent)
local button = GUI.Button(
GUI.RectTransform(Vector2(0.32, 0.05), parent.RectTransform, GUI.Anchor.BottomCenter),
"Discard and Exit",
GUI.Alignment.Center,
"GUIButton"
)
button.OnClicked = function()
GUI.GUI.TogglePauseMenu()
NTConfig.LoadConfig()
end
return button
end
-- Function for the Reset and Exit button
easySettings.ResetButton = function(parent)
local button = GUI.Button(
GUI.RectTransform(Vector2(0.32, 0.05), parent.RectTransform, GUI.Anchor.BottomRight),
"Reset Config",
GUI.Alignment.Center,
"GUIButton"
)
button.OnClicked = function()
if
Game.IsSingleplayer or (Game.IsMultiplayer and Game.Client.HasPermission(ClientPermissions.ManageSettings))
then
easySettings.ResetMessage(parent)
end
end
return button
end
-- Confirmation popup for config reset
easySettings.ResetMessage = function(parent)
local ResetMessage = GUI.MessageBox(
"Reset neurotrauma settings",
"Are you sure you want to reset neurotrauma settings to default values?",
{ "Yes", "No" }
)
ResetMessage.DrawOnTop = true
ResetMessage.Text.TextAlignment = GUI.Alignment.Center
ResetMessage.Buttons[1].OnClicked = function()
NTConfig.ResetConfig()
if Game.IsMultiplayer and Game.Client.HasPermission(ClientPermissions.ManageSettings) then
NTConfig.SendConfig()
elseif Game.IsSingleplayer then
NTConfig.SaveConfig()
end
GUI.GUI.TogglePauseMenu()
ResetMessage.Close()
end
ResetMessage.Buttons[2].OnClicked = function()
ResetMessage.Close()
end
return ResetMessage
end
-- Add button on pause menu for NT Settings
Hook.Patch("Barotrauma.GUI", "TogglePauseMenu", {}, function()
if GUI.GUI.PauseMenuOpen then
local frame = GUI.GUI.PauseMenu
local list = GetChildren(GetChildren(frame)[2])[1]
for key, value in pairs(easySettings.Settings) do
local PauseMenuButton = GUI.Button(
GUI.RectTransform(Vector2(1, 0.1), list.RectTransform),
value.Name,
GUI.Alignment.Center,
"GUIButtonSmall"
)
PauseMenuButton.OnClicked = function()
value.OnOpen(frame)
end
end
end
end, Hook.HookMethodType.After)
return easySettings
+174
View File
@@ -0,0 +1,174 @@
-- Neurotrauma blood types functions
-- Hooks Lua event "characterCreated" to create a randomized blood type for spawned character and sets their immunity to 100
---@diagnostic disable: lowercase-global, undefined-global
NT.BLOODTYPE = { -- blood types and chance in percent
{ "ominus", 7 },
{ "oplus", 37 },
{ "aminus", 6 },
{ "aplus", 36 },
{ "bminus", 2 },
{ "bplus", 8 },
{ "abminus", 1 },
{ "abplus", 3 },
}
NT.setBlood = {}
NT.foundAny = false
-- Insert all blood types in one table for RandomizeBlood()
for index, value in ipairs(NT.BLOODTYPE) do
-- print(index," : ",value[1],", ",value[2],"%")
table.insert(NT.setBlood, index, { value[2], value[1] })
end
-- Applies math.random() blood type.
-- returns the applied bloodtype as an affliction identifier
function NT.RandomizeBlood(character)
rand = math.random(0, 99)
local i = 0
for index, value in ipairs(NT.setBlood) do
i = i + value[1]
if i > rand then
HF.SetAffliction(character, value[2], 100)
return value[2]
end
end
end
Hook.Add("characterCreated", "NT.BloodAndImmunity", function(createdCharacter)
Timer.Wait(function()
if createdCharacter.IsHuman and not createdCharacter.IsDead then
NT.TryRandomizeBlood(createdCharacter)
-- add immunity
local conditional2 = createdCharacter.CharacterHealth.GetAffliction("immunity")
if conditional2 == nil then HF.SetAffliction(createdCharacter, "immunity", 100) end
end
end, 1000)
end)
-- applies a new bloodtype only if the character doesnt already have one
function NT.TryRandomizeBlood(character)
NT.GetBloodtype(character)
end
-- returns the bloodtype of the character as an affliction identifier string
-- generates blood type if none present
function NT.GetBloodtype(character)
for index, affliction in ipairs(NT.BLOODTYPE) do
local conditional = character.CharacterHealth.GetAffliction(affliction[1])
if conditional ~= nil and conditional.Strength > 0 then
return affliction[1] -- TODO: give out abplus (AB+) to enemy team for blood infusions
end
end
return NT.RandomizeBlood(character)
end
function NT.HasBloodtype(character)
for index, affliction in ipairs(NT.BLOODTYPE) do
local conditional = character.CharacterHealth.GetAffliction(affliction[1])
if conditional ~= nil and conditional.Strength > 0 then return true end
end
return false
end
Hook.Add("OnInsertedIntoBloodAnalyzer", "NT.BloodAnalyzer", function(effect, deltaTime, item, targets, position)
-- Hematology Analyzer (bloodanalyzer) can scan inserted blood bags
local owner = item.GetRootInventoryOwner()
if owner == nil then return end
if not LuaUserData.IsTargetType(owner, "Barotrauma.Character") then return end
if not owner.IsPlayer then return end
local character = owner
local contained = item.OwnInventory.GetItemAt(0)
local BaseColor = "127,255,255"
local NameColor = "127,255,255"
local LowColor = "127,255,255"
local HighColor = "127,255,255"
local VitalColor = "127,255,255"
if NTConfig.Get("NTSCAN_enablecoloredscanner", 1) then
BaseColor = table.concat(NTConfig.Get("NTSCAN_basecolor", 1), ",")
NameColor = table.concat(NTConfig.Get("NTSCAN_namecolor", 1), ",")
LowColor = table.concat(NTConfig.Get("NTSCAN_lowcolor", 1), ",")
HighColor = table.concat(NTConfig.Get("NTSCAN_highcolor", 1), ",")
VitalColor = table.concat(NTConfig.Get("NTSCAN_vitalcolor", 1), ",")
end
-- NT adds bloodbag; NT Blood Work or 'Real Sonar Medical Item Recipes Patch for Neurotrauma' add allblood, lets check for either
if contained ~= nil and (contained.HasTag("bloodbag") or contained.HasTag("allblood")) then
HF.GiveItem(character, "ntsfx_syringe")
Timer.Wait(function()
if item == nil or character == nil or item.OwnInventory.GetItemAt(0) ~= contained then return end
local identifier = contained.Prefab.Identifier.Value
local packtype = "o-"
if identifier ~= "antibloodloss2" then packtype = string.sub(identifier, string.len("bloodpack") + 1) end
local bloodTypeDisplay = string.gsub(packtype, "abc", "c")
bloodTypeDisplay = string.gsub(bloodTypeDisplay, "plus", "+")
bloodTypeDisplay = string.gsub(bloodTypeDisplay, "minus", "-")
bloodTypeDisplay = string.upper(bloodTypeDisplay)
local readoutString = "‖color:"
.. BaseColor
.. ""
.. "Bloodpack: "
.. "‖color:end‖"
.. "‖color:"
.. NameColor
.. ""
.. bloodTypeDisplay
.. "‖color:end‖"
-- check if acidosis, alkalosis or sepsis
local tags = HF.SplitString(contained.Tags, ",")
local defects = ""
for tag in tags do
if tag == "sepsis" then
defects = defects .. "‖color:" .. VitalColor .. "" .. "\nSepsis detected" .. "‖color:end‖"
end
if HF.StartsWith(tag, "acid") then
local split = HF.SplitString(tag, ":")
if split[2] ~= nil then
defects = defects
.. "‖color:"
.. HighColor
.. ""
.. "\nAcidosis: "
.. tonumber(split[2])
.. "%"
.. "‖color:end‖"
end
elseif HF.StartsWith(tag, "alkal") then
local split = HF.SplitString(tag, ":")
if split[2] ~= nil then
defects = defects
.. "‖color:"
.. HighColor
.. ""
.. "\nAlkalosis: "
.. tonumber(split[2])
.. "%"
.. "‖color:end‖"
end
end
end
if defects ~= "" then
readoutString = readoutString .. defects
else
readoutString = readoutString
.. "‖color:"
.. LowColor
.. ""
.. "\nNo blood defects"
.. "‖color:end‖"
end
HF.DMClient(HF.CharacterToClient(character), readoutString, Color(127, 255, 255, 255))
end, 1500)
end
end)
@@ -0,0 +1,32 @@
-- Hooks CalculateMovementPenalty method of Barotrauma.Character
-- when painless enough, disable weapon sway / movement hindrance limb penalties
-- !!! Lags the game, though GetAimWobble seems to be ok to patch
-- Disable movement penalties for painless characters
-- Has about 2 ms performance drop on a many character save
--Hook.Patch("Barotrauma.Character", "CalculateMovementPenalty", function(instance, ptable)
-- if HF.HasAffliction(instance, "analgesia", 20) then
-- ptable.PreventExecution = true
-- return 0
-- end
--end, Hook.HookMethodType.Before)
-- Disable aim penalties for painless characters
Hook.Patch("Barotrauma.AnimController", "GetAimWobble", function(instance, ptable)
if HF.HasAffliction(instance.Character, "analgesia", 20) then
ptable.PreventExecution = true
return 0
end
end, Hook.HookMethodType.Before)
-- Patch to cause unconscious from the game rather than stun
-- Lags the game by 6 times (on the same save)
--Hook.Patch("Barotrauma.CharacterHealth", "get_IsUnconscious", function(instance, ptable)
-- local isUnconscious = HF.HasAffliction(instance.Character, "sym_unconsciousness")
-- ptable.PreventExecution = true
-- return instance.Character.IsDead
-- or (
-- (instance.Character.Vitality <= 0.0 or isUnconscious)
-- and not instance.Character.HasAbilityFlag(AbilityFlags.AlwaysStayConscious)
-- )
--end, Hook.HookMethodType.After)
@@ -0,0 +1,108 @@
-- THIS FILE IS NO LONGER IN USE
-- the defunct item in question has been removed from the mod
-- i'm keeping it here for...safekeeping i guess
LuaUserData.RegisterTypeBarotrauma("PurchasedItem")
LuaUserData.RegisterType("System.Xml.Linq.XElement")
local VANILLA_PREFAB_ID = "antibloodloss2"
local DEFUNCT_PREFAB_ID = "bloodpackominus"
-- Removes vanilla bloodpacks from cached stores in case the user installed
-- this mod mid-campaign.
-- NOTE: stores that had their stocks generated before installing this mod
-- won't have any new medical items added.
Hook.HookMethod("Barotrauma.Location", "LoadStores", function(instance, ptable)
if instance.Stores == nil then return end
for storeId, store in pairs(instance.Stores) do
for _, purchasedItem in pairs(store.Stock) do
local itemId = purchasedItem.ItemPrefabIdentifier
if itemId == DEFUNCT_PREFAB_ID then
-- print("Removing defunct bloodpack (qty " .. purchasedItem.Quantity .. ") from " .. tostring(storeId))
store.RemoveStock({ purchasedItem })
end
end
end
end, Hook.HookMethodType.After)
-- Replaces all vanilla bloodpack items with O- blood
local function replaceItems()
local ntBloodPrefab = ItemPrefab.Prefabs[DEFUNCT_PREFAB_ID]
local vanillaBloodPrefab = ItemPrefab.Prefabs[VANILLA_PREFAB_ID]
if ntBloodPrefab == nil then
print("ERROR: couldn't find " .. DEFUNCT_PREFAB_ID)
return
end
for _, item in pairs(Item.ItemList) do
local id = tostring(item.Prefab.Identifier)
if id == DEFUNCT_PREFAB_ID then
-- Don't replace decorative blood packs
if item.NonInteractable then return end
local pos = item.WorldPosition
local inv = item.ParentInventory
local condition = item.ConditionPercentage
local quality = item.Quality
-- print("replacing blood pack (pos=" .. tostring(pos) .. ", inv=" .. tostring(inv) .. ")")
local slotIdx = -1
if inv ~= nil then
slotIdx = inv.FindIndex(item)
if slotIdx < 0 then
print(
"ERROR: couldn't find item ("
.. tostring(item)
.. ", pos "
.. tostring(pos)
.. ") in inventory ("
.. tostring(inv)
.. ")"
)
return
end
end
-- We call `Drop()` first in case the inventory is full because
-- `AddEntityToRemoveQueue` may not remove the item before we
-- insert the new one, causing the inventory to overflow.
item.Drop()
Entity.Spawner.AddEntityToRemoveQueue(item)
Entity.Spawner.AddItemToSpawnQueue(vanillaBloodPrefab, pos, condition, quality, function(newItem)
newItem.Rotation = item.Rotation
-- Stolen items stay stolen
newItem.AllowStealing = item.AllowStealing
newItem.OriginalOutpost = item.OriginalOutpost
if inv ~= nil then
if not inv.TryPutItem(newItem, slotIdx, false, true, nil) then
print(
"ERROR: failed to replace neurotrauma bloodpack ("
.. tostring(item)
.. ", pos "
.. tostring(pos)
.. ", slotIdx "
.. tostring(slotIdx)
.. ", inv "
.. tostring(inv)
.. ") with new item: "
.. tostring(newItem)
)
end
end
end)
end
end
end
Hook.Add("roundStart", "NT.ConvertBloodPacks", function()
replaceItems()
end)
-- Hook.Add("chatMessage", "NT.BloodPackTesting", function(msg, client)
-- if (msg == "convertblood") then
-- replaceItems()
-- end
-- end)
+49
View File
@@ -0,0 +1,49 @@
-- Hooks Lua event "human.CPRSuccess" to prevent fractures from ragdoll jank, and
-- apply NT affliction cpr_buff or cause rib fractures in Hooked Lua event "human.CPRFailed"
-- human.CPRSuccess was changed to character.CPRSuccess? Way above my paygrade - Lukako
Hook.Add("character.CPRSuccess", "NT.CPRSuccess", function(animcontroller)
if
animcontroller == nil
or animcontroller.Character == nil
or animcontroller.Character.SelectedCharacter == nil
then
return
end
local character = animcontroller.Character.SelectedCharacter
if not HF.HasAffliction(character, "luabotomy") then HF.SetAffliction(character, "luabotomy", 1) end
if not HF.HasAffliction(character, "cpr_buff_auto") then HF.AddAffliction(character, "cpr_buff", 2) end
HF.AddAffliction(character, "cpr_fracturebuff", 2) -- prevent fractures during CPR (fuck baro physics)
end)
Hook.Add("character.CPRFailed", "NT.CPRFailed", function(animcontroller)
if
animcontroller == nil
or animcontroller.Character == nil
or animcontroller.Character.SelectedCharacter == nil
then
return
end
local character = animcontroller.Character.SelectedCharacter
if not HF.HasAffliction(character, "luabotomy") then HF.SetAffliction(character, "luabotomy", 1) end
HF.AddAffliction(character, "cpr_fracturebuff", 2) -- prevent fractures during CPR (fuck baro physics)
HF.AddAfflictionLimb(character, "blunttrauma", LimbType.Torso, 0.3)
if
HF.Chance(
NTConfig.Get("NT_fractureChance", 1)
* NTConfig.Get("NT_CPRFractureChance", 1)
* 0.2
/ HF.GetSkillLevel(animcontroller.Character, "medical")
)
then
HF.AddAffliction(character, "t_fracture", 1)
end
end)
+25
View File
@@ -0,0 +1,25 @@
-- Based on config settings, which items ought to be destroyed
local function DynamicRemoveItems()
local blockedItems = HF.DynamicUnavailableItems()
for _, item in pairs(Item.ItemList) do
local id = item.Prefab.Identifier.Value
if blockedItems[id] then item.Remove() end
end
end
-- On level swap, remove any items that shouldn't be there
Hook.Add("roundEnd", "nt_dynamicremoveitems", function()
DynamicRemoveItems()
end)
-- Recreate stores command to make sure newly added items are actually in stores
Game.AddCommand("nt_recreatestores", "Recreate all stores.", function()
if Game.GameSession.Map ~= nil then
for location in Game.GameSession.Map.Locations do
location.CreateStores(true)
end
else
print("nt_recreatestores: Tried to recreate stores in the campaign map, but there is none!")
end
end)
+300
View File
@@ -0,0 +1,300 @@
-- Hooks Lua event "changeFallDamage" to cause more damage and NT afflictions like fractures and artery cuts on extremities depending on severity
local limbtypes = {
LimbType.Torso,
LimbType.Head,
LimbType.LeftArm,
LimbType.RightArm,
LimbType.LeftLeg,
LimbType.RightLeg,
}
local function HasLungs(c)
return not HF.HasAffliction(c, "lungremoved")
end
local function getCalculatedReductionSuit(armor, strength, limbtype)
if armor == nil then return 0 end
local reduction = 0
if armor.HasTag("deepdivinglarge") or armor.HasTag("deepdiving") then
local modifiers = armor.GetComponentString("Wearable").DamageModifiers
for modifier in modifiers do
if string.find(modifier.AfflictionIdentifiers, "blunttrauma") ~= nil then
reduction = strength - strength * modifier.DamageMultiplier
end
end
elseif armor.HasTag("clothing") and armor.HasTag("smallitem") and limbtype == LimbType.Torso then
local modifiers = armor.GetComponentString("Wearable").DamageModifiers
for modifier in modifiers do
if string.find(modifier.AfflictionIdentifiers, "blunttrauma") ~= nil then
reduction = strength - strength * modifier.DamageMultiplier
end
end
end
return reduction
end
local function getCalculatedReductionClothes(armor, strength, limbtype)
if armor == nil then return 0 end
local reduction = 0
if armor.HasTag("deepdiving") or armor.HasTag("diving") then
local modifiers = armor.GetComponentString("Wearable").DamageModifiers
for modifier in modifiers do
if string.find(modifier.AfflictionIdentifiers, "blunttrauma") ~= nil then
reduction = strength - strength * modifier.DamageMultiplier
end
end
elseif armor.HasTag("clothing") and armor.HasTag("smallitem") then
local modifiers = armor.GetComponentString("Wearable").DamageModifiers
for modifier in modifiers do
if string.find(modifier.AfflictionIdentifiers, "blunttrauma") ~= nil then
reduction = strength - strength * modifier.DamageMultiplier
end
end
end
return reduction
end
local function getCalculatedReductionHelmet(armor, strength)
if armor == nil then return 0 end
local reduction = 0
if armor.HasTag("smallitem") then
local modifiers = armor.GetComponentString("Wearable").DamageModifiers
for modifier in modifiers do
if string.find(modifier.AfflictionIdentifiers, "blunttrauma") ~= nil then
reduction = strength - strength * modifier.DamageMultiplier
end
end
end
return reduction
end
local function getCalculatedConcussionReduction(armor, strength)
if armor == nil then return 0 end
local reduction = 0
if armor.HasTag("deepdiving") or armor.HasTag("deepdivinglarge") then
local modifiers = armor.GetComponentString("Wearable").DamageModifiers
for modifier in modifiers do
if string.find(modifier.AfflictionIdentifiers, "concussion") ~= nil then
reduction = strength - strength * modifier.DamageMultiplier
end
end
elseif armor.HasTag("smallitem") then
local modifiers = armor.GetComponentString("Wearable").DamageModifiers
for modifier in modifiers do
if string.find(modifier.AfflictionIdentifiers, "concussion") ~= nil then
reduction = strength - strength * modifier.DamageMultiplier
end
end
end
return reduction
end
Hook.Add("changeFallDamage", "NT.falldamage", function(impactDamage, character, impactPos, velocity)
-- don't run the code if we ignore the code
if not NTConfig.Get("NT_Calculations", true) then return 0 end
-- dont bother with creatures
if not character.IsHuman then return 0 end
-- dont apply fall damage in water
if character.InWater then return 0 end
-- dont apply fall damage when dragged by someone
if character.SelectedBy ~= nil then return 0 end
-- don't apply fall damage if were specifically immune to it
if HF.HasAffliction(character, "cpr_fracturebuff") or HF.HasAffliction(character, "stopcreatureabuse") then
return 0
end
if not HF.HasAffliction(character, "luabotomy") then HF.SetAffliction(character, "luabotomy", 1) end
local velocityMagnitude = HF.Magnitude(velocity)
velocityMagnitude = velocityMagnitude ^ 1.3
-- apply fall damage to all limbs based on fall direction
local mainlimbPos = character.AnimController.MainLimb.WorldPosition
local limbDotResults = {}
local minDotRes = 1000
for limb in character.AnimController.Limbs do
for type in limbtypes do
if limb.type == type then
-- fetch the direction of each limb relative to the torso
local limbPosition = limb.WorldPosition
local posDif = limbPosition - mainlimbPos
posDif.X = posDif.X / 100
posDif.Y = posDif.Y / 100
local posDifMagnitude = HF.Magnitude(posDif)
if posDifMagnitude > 1 then posDif.Normalize() end
local normalizedVelocity = Vector2(velocity.X, velocity.Y)
normalizedVelocity.Normalize()
-- compare those directions to the direction we're moving
-- this will later be used to hurt the limbs facing impact more than the others
local limbDot = Vector2.Dot(posDif, normalizedVelocity)
limbDotResults[type] = limbDot
if minDotRes > limbDot then minDotRes = limbDot end
break
end
end
end
-- shift all weights out of the negatives
-- increase the weight of all limbs if speed is high
-- the effect of this is that, at higher speeds, all limbs take damage instead of mainly the ones facing the impact site
for type, dotResult in pairs(limbDotResults) do
limbDotResults[type] = dotResult - minDotRes + math.max(0, (velocityMagnitude - 30) / 10)
end
-- count weight so we're able to distribute the damage fractionally
local weightsum = 0
for dotResult in limbDotResults do
weightsum = weightsum + dotResult
end
for type, dotResult in pairs(limbDotResults) do
local relativeWeight = dotResult / weightsum
-- lets limit the numbers to the max value of blunttrauma so that resistances make sense
local damageInflictedToThisLimb = math.min(
relativeWeight * math.max(0, velocityMagnitude - 10) ^ 1.5 * NTConfig.Get("NT_falldamage", 1) * 0.5,
NTConfig.Get("NT_falldamageCeiling", 1) * 60
)
NT.CauseFallDamage(character, type, damageInflictedToThisLimb)
end
-- make the normal damage not run
return 0
end)
NT.CauseFallDamage = function(character, limbtype, strength)
local armor1 = character.Inventory.GetItemInLimbSlot(InvSlotType.OuterClothes)
local armor2 = character.Inventory.GetItemInLimbSlot(InvSlotType.InnerClothes)
if limbtype ~= LimbType.Head then
strength = math.max(
strength
- getCalculatedReductionSuit(armor1, strength, limbtype)
- getCalculatedReductionClothes(armor2, strength, limbtype),
0
)
else
armor2 = character.Inventory.GetItemInLimbSlot(InvSlotType.Head)
strength = math.max(
strength
- getCalculatedReductionSuit(armor1, strength, limbtype)
- getCalculatedReductionHelmet(armor2, strength, limbtype),
0
)
end
-- additionally calculate the affliction reduced damage
local prefab = AfflictionPrefab.Prefabs["blunttrauma"]
local resistance = character.CharacterHealth.GetResistance(prefab, limbtype)
if resistance >= 1 then return end
strength = strength * (1 - resistance)
HF.AddAfflictionLimb(character, "blunttrauma", limbtype, strength)
-- return earlier if the strength value is not high enough for damage checks
if strength < 1 then return end
local fractureImmune = false
local injuryChanceMultiplier = NTConfig.Get("NT_falldamageSeriousInjuryChance", 1)
-- torso
if not fractureImmune and strength >= 1 and limbtype == LimbType.Torso then
if
HF.Chance(
(strength - 15)
/ 100
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
* injuryChanceMultiplier
)
then
NT.BreakLimb(character, limbtype)
if
HasLungs(character)
and strength >= 5
and HF.Chance(
strength
/ 70
* NTC.GetMultiplier(character, "pneumothoraxchance")
* NTConfig.Get("NT_pneumothoraxChance", 1)
)
then
HF.AddAffliction(character, "pneumothorax", 5)
end
end
end
-- head
if not fractureImmune and strength >= 1 and limbtype == LimbType.Head then
if strength >= 15 and HF.Chance(math.min(strength / 100, 0.7)) then
HF.AddAfflictionResisted(
character,
"concussion",
math.max(
10
- getCalculatedConcussionReduction(armor1, 10, limbtype)
- getCalculatedConcussionReduction(armor2, 10, limbtype),
0
)
)
end
if
strength >= 15
and HF.Chance(
math.min((strength - 15) / 100, 0.7)
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
* injuryChanceMultiplier
)
then
NT.BreakLimb(character, limbtype)
end
if
strength >= 55
and HF.Chance(
math.min((strength - 15) / 100, 0.7)
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
* injuryChanceMultiplier
)
then
HF.AddAffliction(character, "n_fracture", 5)
end
if strength >= 5 and HF.Chance(0.7) then
HF.AddAffliction(character, "cerebralhypoxia", strength * HF.RandomRange(0.1, 0.4))
end
end
-- extremities
if not fractureImmune and strength >= 1 and HF.LimbIsExtremity(limbtype) then
if
HF.Chance(
(strength - 15)
/ 100
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
* injuryChanceMultiplier
)
then
NT.BreakLimb(character, limbtype)
if HF.Chance((strength - 2) / 60) then
-- this is here to simulate open fractures
NT.ArteryCutLimb(character, limbtype)
end
end
if
HF.Chance(
HF.Clamp((strength - 5) / 120, 0, 0.5)
* NTC.GetMultiplier(character, "dislocationchance")
* NTConfig.Get("NT_dislocationChance", 1)
* injuryChanceMultiplier
) and not NT.LimbIsAmputated(character, limbtype)
then
NT.DislocateLimb(character, limbtype)
end
end
end
+98
View File
@@ -0,0 +1,98 @@
LuaUserData.MakeMethodAccessible(Descriptors["Barotrauma.HumanAIController"], "SpeakAboutIssues")
-- hopefully this stops bots from doing any rescuing at all.
-- and also hopefully my assumption that this very specific thing
-- about bots is what is causing them to eat frames is correct.
if NTConfig.Get("NT_disableBotAlgorithms", true) then
Hook.Patch("Barotrauma.AIObjectiveRescueAll", "IsValidTarget", {
"Barotrauma.Character",
"Barotrauma.Character",
"out System.Boolean",
}, function(instance, ptable)
-- TODO: some bot behavior
-- make it hostile act if:
-- surgery without corresponding ailments
-- treatment without ailments
-- basic self treatments:
-- find items to treat each other for blood loss or bleeding or suturable damage or fractures and dislocations
-- ^ would possibly need items to have proper suitable treatments too, and yk bots dont spawn with enough meds...
ptable.PreventExecution = true
return false
end, Hook.HookMethodType.Before)
end
local afflictions = {
"n_fracture", -- urgent perceivable afflictions
"h_arterialcut",
"ll_arterialcut",
"rl_arterialcut",
"ra_arterialcut",
"la_arterialcut",
"sym_hematemesis", -- urgent causes
"sym_paleskin",
"sym_confusion",
"sym_lightheadedness",
"pain_abdominal",
"inflammation",
"gangrene",
"fever",
"sym_headache",
"sym_blurredvision",
"t_fracture", -- not urgent afflictions
"h_fracture",
"ra_fracture",
"la_fracture",
"rl_fracture",
"ll_fracture",
"dislocation1",
"dislocation2",
"dislocation3",
"dislocation4",
"pain_chest", -- not urgent causes
"sym_weakness",
"sym_sweating",
"dyspnea",
"sym_bloating",
"sym_legswelling",
"sym_craving",
"sym_palpitations",
}
NT.SymsForNPC = { ntaffs = afflictions }
-- How to add own symptoms example:
--local goobertable = { "goober", "gooberer" }
--table.insert(NT.SymsForNPC, goobertable)
-- allows npcs to talk about their neuro afflictions
Hook.Patch("Barotrauma.HumanAIController", "SpeakAboutIssues", function(instance)
local character = instance.Character
if not HF.HasAffliction(character, "luabotomy", 1) then return end
local message = ""
local chatType = ChatMessageType.Default
if ChatMessage.CanUseRadio(character) then chatType = ChatMessageType.Radio end
for identifier in NT.SymsForNPC.ntaffs do
if HF.HasAffliction(character, identifier, 1) then
message = TextManager.Get("npcdialogsym." .. identifier)
character.Speak(message, chatType, math.random(0, 5), Identifier(identifier .. "DialogSym"), 600.0)
break
end
end
for table in NT.SymsForNPC do
for identifier in table do
if HF.HasAffliction(character, identifier, 1) then
message = TextManager.Get("npcdialogsym." .. identifier)
character.Speak(message, chatType, math.random(0, 5), Identifier(identifier .. "DialogSym"), 600.0)
break
end
end
end
end, Hook.HookMethodType.After)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+90
View File
@@ -0,0 +1,90 @@
-- Spawns items inside medstartercrate
-- Hooks XML Lua event "NT.medstartercrate.spawn" to create medstartercrate items and put them inside it
Hook.Add(
"NT.medstartercrate.spawn",
"NT.medstartercrate.spawn",
function(effect, deltaTime, item, targets, worldPosition)
Timer.Wait(function()
if item == nil then return end
-- check if the item already got populated before
-- got broken somehow and is no longer needed, handled with oneshot="true" for the StatusEffect inside the medstartercrate item that calls this hook on spawn
-- local populated = item.HasTag("used")
-- if populated then return end
-- add used tag
-- local tags = HF.SplitString(item.Tags,",")
-- table.insert(tags,"used")
-- local tagstring = ""
-- for index, value in ipairs(tags) do
-- tagstring = tagstring..value
-- if index < #tags then tagstring=tagstring.."," end
-- end
-- item.Tags = tagstring
-- populate with goodies!!
if item.Scale == 0.5 then return end
item.Scale = 0.5
HF.SpawnItemPlusFunction("medtoolbox", function(params)
HF.SpawnItemPlusFunction("defibrillator", nil, nil, params.item.OwnInventory, 0)
HF.SpawnItemPlusFunction("autocpr", nil, nil, params.item.OwnInventory, 1)
for i = 1, 2, 1 do
HF.SpawnItemPlusFunction("tourniquet", nil, nil, params.item.OwnInventory, 2)
end
for i = 1, 2, 1 do
HF.SpawnItemPlusFunction("ringerssolution", nil, nil, params.item.OwnInventory, 3)
end
HF.SpawnItemPlusFunction("surgicaldrill", nil, nil, params.item.OwnInventory, 4)
HF.SpawnItemPlusFunction("surgerysaw", nil, nil, params.item.OwnInventory, 5)
end, nil, item.OwnInventory, 0)
HF.SpawnItemPlusFunction("medtoolbox", function(params)
HF.SpawnItemPlusFunction("antibleeding1", nil, nil, params.item.OwnInventory, 0)
HF.SpawnItemPlusFunction("gypsum", nil, nil, params.item.OwnInventory, 1)
HF.SpawnItemPlusFunction("opium", nil, nil, params.item.OwnInventory, 2)
HF.SpawnItemPlusFunction("antibiotics", nil, nil, params.item.OwnInventory, 3)
HF.SpawnItemPlusFunction("ointment", nil, nil, params.item.OwnInventory, 4)
end, nil, item.OwnInventory, 1)
HF.SpawnItemPlusFunction("surgerytoolbox", function(params)
HF.SpawnItemPlusFunction("advscalpel", nil, nil, params.item.OwnInventory, 0)
HF.SpawnItemPlusFunction("advhemostat", nil, nil, params.item.OwnInventory, 1)
HF.SpawnItemPlusFunction("advretractors", nil, nil, params.item.OwnInventory, 2)
for i = 1, 16, 1 do
HF.SpawnItemPlusFunction("suture", nil, nil, params.item.OwnInventory, 3)
end
HF.SpawnItemPlusFunction("tweezers", nil, nil, params.item.OwnInventory, 4)
HF.SpawnItemPlusFunction("traumashears", nil, nil, params.item.OwnInventory, 5)
HF.SpawnItemPlusFunction("drainage", nil, nil, params.item.OwnInventory, 6)
HF.SpawnItemPlusFunction("needle", nil, nil, params.item.OwnInventory, 7)
end, nil, item.OwnInventory, 3)
HF.SpawnItemPlusFunction("bloodanalyzer", nil, nil, item.OwnInventory, 6)
HF.SpawnItemPlusFunction("healthscanner", function(params)
local prefab = ItemPrefab.GetItemPrefab("batterycell")
Entity.Spawner.AddItemToSpawnQueue(prefab, params["item"].WorldPosition, nil, nil, function(batteryItem)
params["item"].OwnInventory.TryPutItem(batteryItem)
end)
end, nil, item.OwnInventory, 7)
end, 35)
end
)
Hook.Add("character.giveJobItems", "NT.giveHealthScannersBatteries", function(character)
Timer.Wait(function()
for item in character.Inventory.AllItems do
local thisIdentifier = item.Prefab.Identifier.Value
if thisIdentifier == "healthscanner" then
if item.OwnInventory ~= nil and item.OwnInventory.GetItemAt(0) == nil then
local prefab = ItemPrefab.GetItemPrefab("batterycell")
Entity.Spawner.AddItemToSpawnQueue(prefab, character.WorldPosition, nil, nil, function(batteryItem)
item.OwnInventory.TryPutItem(batteryItem, character)
end)
end
end
end
end, 1000)
end)
+34
View File
@@ -0,0 +1,34 @@
-- Modders, please use ModDir:Neurotrauma when taking dependencies, and
-- name your patches with the word "neurotrauma" (letter case doesnt matter)
-- sets NT.modconflict to true if incompatible mod detected
-- this applies meta affliction "modconflict" every round
-- prints out the warning and incompatible mod on server startup
-- Hooks Lua event "roundStart" to do the above each round
NT.modconflict = false
function NT.CheckModConflicts()
NT.modconflict = false
if NTConfig.Get("NT_ignoreModConflicts", false) then return end
local itemsToCheck = { "antidama2", "opdeco_hospitalbed" }
for prefab in ItemPrefab.Prefabs do
if HF.TableContains(itemsToCheck, prefab.Identifier.Value) then
local mod = prefab.ConfigElement.ContentPackage.Name
if not string.find(string.lower(mod), "neurotrauma") then
NT.modconflict = true
print("Found Neurotrauma incompatibility with mod: ", mod)
print("WARNING! mod conflict detected! Neurotrauma may not function correctly and requires a patch!")
return
end
end
end
end
Timer.Wait(function()
NT.CheckModConflicts()
end, 1000)
Hook.Add("roundStart", "NT.RoundStart.modconflicts", function()
Timer.Wait(function()
NT.CheckModConflicts()
end, 10000)
end)
+278
View File
@@ -0,0 +1,278 @@
-- NT functions for multiscalpel mode setting
-- Hooks XML Lua events defined in the multiscalpel item.xml
-- Hooks Lua event "roundStart" to RefreshAllMultiscalpels descriptions
function NT.SetMultiscalpelFunction(item, func)
if func ~= "" then
item.Tags = "multiscalpel_" .. func
else
item.Tags = ""
end
NT.RefreshScalpelDescription(item)
end
local function GetMultiscalpelMode(item)
local functiontag = ""
local tags = HF.SplitString(item.Tags, ",")
for tag in tags do
if HF.StartsWith(tag, "multiscalpel_") then
functiontag = HF.SplitString(tag, "_")[2]
break
end
end
return functiontag
end
function NT.RefreshScalpelDescription(item)
-- if not HF.ItemHasTag(item,"init") then return end
-- hostside only
if Game.IsMultiplayer and CLIENT then return end
if not Entity.Spawner then
Timer.Wait(function()
NT.RefreshScalpelDescription(item)
end, 35)
return
end
local functiontag = GetMultiscalpelMode(item)
if functiontag == "" then return end
local targetinventory = item.ParentInventory
local targetslot = 0
if targetinventory ~= nil then targetslot = targetinventory.FindIndex(item) end
local function SpawnFunc(newscalpelitem, targetinventory)
if targetinventory ~= nil then targetinventory.TryPutItem(newscalpelitem, targetslot, true, true, nil) end
newscalpelitem.DescriptionTag = "multiscalpel." .. functiontag
newscalpelitem.Tags = "multiscalpel_" .. functiontag
end
HF.RemoveItem(item)
Timer.Wait(function()
local prefab = item.Prefab
Entity.Spawner.AddItemToSpawnQueue(prefab, item.WorldPosition, nil, nil, function(newscalpelitem)
SpawnFunc(newscalpelitem, targetinventory)
end)
end, 35)
end
-- Let's faithfully believe Ydrec that said the item descriptions are already serialized by base game
--Hook.Add("roundStart", "NT.RoundStart.Multiscalpels", function()
-- Timer.Wait(function()
-- NT.RefreshAllMultiscalpels()
-- end, 10000) -- maybe 10 seconds is enough?
--end)
function NT.RefreshAllMultiscalpels()
-- descriptions dont get serialized, so i have to respawn
-- every scalpel every round to keep their descriptions (big oof)
-- fetch scalpel items
local scalpelItems = {}
for item in Item.ItemList do
if item.Prefab.Identifier.Value == "multiscalpel" then table.insert(scalpelItems, item) end
end
-- refresh items
for scalpel in scalpelItems do
NT.RefreshScalpelDescription(scalpel)
end
end
--Timer.Wait(function()
-- NT.RefreshAllMultiscalpels()
--end, 50)
Hook.Add(
"NT.multiscalpel.incision",
"NT.multiscalpel.incision",
function(effect, deltaTime, item, targets, worldPosition)
NT.SetMultiscalpelFunction(item, "incision")
end
)
Hook.Add("NT.multiscalpel.kidneys", "NT.multiscalpel.kidneys", function(effect, deltaTime, item, targets, worldPosition)
NT.SetMultiscalpelFunction(item, "kidneys")
end)
Hook.Add("NT.multiscalpel.liver", "NT.multiscalpel.liver", function(effect, deltaTime, item, targets, worldPosition)
NT.SetMultiscalpelFunction(item, "liver")
end)
Hook.Add("NT.multiscalpel.lungs", "NT.multiscalpel.lungs", function(effect, deltaTime, item, targets, worldPosition)
NT.SetMultiscalpelFunction(item, "lungs")
end)
Hook.Add("NT.multiscalpel.heart", "NT.multiscalpel.heart", function(effect, deltaTime, item, targets, worldPosition)
NT.SetMultiscalpelFunction(item, "heart")
end)
Hook.Add("NT.multiscalpel.brain", "NT.multiscalpel.brain", function(effect, deltaTime, item, targets, worldPosition)
NT.SetMultiscalpelFunction(item, "brain")
end)
Hook.Add("NT.multiscalpel.bandage", "NT.multiscalpel.bandage", function(effect, deltaTime, item, targets, worldPosition)
NT.SetMultiscalpelFunction(item, "bandage")
end)
Hook.Add(
"NT.multiscalpel.speedflex",
"NT.multiscalpel.speedflex",
function(effect, deltaTime, item, targets, worldPosition)
NT.SetMultiscalpelFunction(item, "speedflex")
end
)
NT.ItemMethods.multiscalpel = function(item, usingCharacter, targetCharacter, limb)
local limbtype = HF.NormalizeLimbType(limb.type)
local mode = GetMultiscalpelMode(item)
if mode == "" then mode = "none" end
local modeFunctions = {
none = function(item, usingCharacter, targetCharacter, limb) end,
incision = NT.ItemMethods.advscalpel,
kidneys = NT.ItemMethods.organscalpel_kidneys,
liver = NT.ItemMethods.organscalpel_liver,
lungs = NT.ItemMethods.organscalpel_lungs,
heart = NT.ItemMethods.organscalpel_heart,
brain = NT.ItemMethods.organscalpel_brain,
bandage = function(item, usingCharacter, targetCharacter, limb)
-- remove casts, bandages, and if none of those apply, cause some damage
-- code snippet taken from NT.ItemMethods.traumashears
-- does the target have any cuttable afflictions?
local cuttables = HF.CombineArrays(NT.CuttableAfflictions, NT.TraumashearsAfflictions)
local canCut = false
for val in cuttables do
local prefab = AfflictionPrefab.Prefabs[val]
if prefab ~= nil then
if prefab.LimbSpecific then
if HF.HasAfflictionLimb(targetCharacter, val, limbtype, 0.1) then
canCut = true
break
end
elseif limbtype == prefab.IndicatorLimb then
if HF.HasAffliction(targetCharacter, val, 0.1) then
canCut = true
break
end
end
end
end
if canCut then
NT.ItemMethods.traumashears(item, usingCharacter, targetCharacter, limb)
else
-- malpractice time!!!!
local open = HF.HasAfflictionLimb(targetCharacter, "retractedskin", limbtype, 1)
local istorso = limbtype == LimbType.Torso
local ishead = limbtype == LimbType.Head
if not open then
HF.AddAfflictionLimb(targetCharacter, "bleeding", limbtype, 6 + math.random() * 4, usingCharacter)
HF.AddAfflictionLimb(
targetCharacter,
"lacerations",
limbtype,
2.5 + math.random() * 5,
usingCharacter
)
HF.GiveItem(targetCharacter, "ntsfx_slash")
else
if istorso then
-- stabbing an open torso (not good for the organs therein!)
HF.AddAffliction(targetCharacter, "internalbleeding", 6 + math.random() * 12, usingCharacter)
HF.AddAfflictionLimb(
targetCharacter,
"lacerations",
limbtype,
4 + math.random() * 6,
usingCharacter
)
HF.AddAfflictionLimb(
targetCharacter,
"internaldamage",
limbtype,
4 + math.random() * 6,
usingCharacter
)
local case = math.random()
local casecount = 4
if case < 1 / casecount then
HF.AddAffliction(targetCharacter, "kidneydamage", 10 + math.random() * 10, usingCharacter)
elseif case < 2 / casecount then
HF.AddAffliction(targetCharacter, "liverdamage", 10 + math.random() * 10, usingCharacter)
elseif case < 3 / casecount then
HF.AddAffliction(targetCharacter, "lungdamage", 10 + math.random() * 10, usingCharacter)
elseif case < 4 / casecount then
HF.AddAffliction(targetCharacter, "heartdamage", 10 + math.random() * 10, usingCharacter)
end
elseif ishead then
-- stabbing an open head (brain surgery done right!)
HF.AddAffliction(targetCharacter, "cerebralhypoxia", 15 + math.random() * 15, usingCharacter)
HF.AddAfflictionLimb(
targetCharacter,
"internaldamage",
limbtype,
10 + math.random() * 10,
usingCharacter
)
HF.AddAfflictionLimb(
targetCharacter,
"bleeding",
limbtype,
6 + math.random() * 12,
usingCharacter
)
else
-- stabbing an open arm or leg (how to cause fractures)
HF.AddAfflictionLimb(
targetCharacter,
"bleeding",
limbtype,
6 + math.random() * 6,
usingCharacter
)
HF.AddAfflictionLimb(
targetCharacter,
"lacerations",
limbtype,
4 + math.random() * 6,
usingCharacter
)
HF.AddAfflictionLimb(
targetCharacter,
"internaldamage",
limbtype,
4 + math.random() * 6,
usingCharacter
)
if HF.Chance(0.1) then NT.BreakLimb(targetCharacter, limbtype) end
end
HF.GiveItem(targetCharacter, "ntsfx_slash")
end
end
end,
speedflex = function(item, usingCharacter, targetCharacter, limb)
local animcontroller = targetCharacter.AnimController
local torsoLimb = limb
if animcontroller ~= nil then torsoLimb = animcontroller.MainLimb end
if limbtype == LimbType.Head then
NT.ItemMethods.organscalpel_brain(item, usingCharacter, targetCharacter, limb)
elseif limbtype == LimbType.LeftArm then
NT.ItemMethods.organscalpel_kidneys(item, usingCharacter, targetCharacter, torsoLimb)
elseif limbtype == LimbType.Torso then
NT.ItemMethods.organscalpel_liver(item, usingCharacter, targetCharacter, torsoLimb)
elseif limbtype == LimbType.RightArm then
NT.ItemMethods.organscalpel_heart(item, usingCharacter, targetCharacter, torsoLimb)
elseif limbtype == LimbType.LeftLeg then
NT.ItemMethods.organscalpel_lungs(item, usingCharacter, targetCharacter, torsoLimb)
end
end,
}
if modeFunctions[mode] ~= nil then modeFunctions[mode](item, usingCharacter, targetCharacter, limb) end
if mode ~= "none" then Timer.Wait(function()
item.Tags = "multiscalpel_" .. mode
end, 50) end
end
+334
View File
@@ -0,0 +1,334 @@
NTC = {} -- a class containing compatibility functions for other mods to make use of neurotraumas symptom system
-- use this function to register your expansion mod to be displayed by the
-- console lua startup readout of neurotrauma expansions
-- check surgery plus or cybernetics for an example
-- example of code for registering your expansion in init.lua:
-- MyExp = {} -- Example Expansions
-- MyExp.Name="My Expansion"
-- MyExp.Version = "A1.0"
-- MyExp.VersionNum = 01000000 -- split into two digits (01->1.; 00->0.; 00->0h; 00->0) -> 1.0.0h0
-- MyExp.MinNTVersion = "A1.7.1"
-- MyExp.MinNTVersionNum = 01070100 -- 01.07.01.00 -> A1.7.1h0
-- Timer.Wait(function() if NT ~= nil then NTC.RegisterExpansion(MyExp) end end,1)
NTC.RegisteredExpansions = {}
-- The function to add your addon to NT, see above for more info.
---@param expansionMainObject table The table of the addon.
function NTC.RegisterExpansion(expansionMainObject)
table.insert(NTC.RegisteredExpansions, expansionMainObject)
end
-- a table of tables, each character that has some custom data has an entry
NTC.CharacterData = {}
-- use this function to induce symptoms temporarily
-- duration is in humanupdates (~2 seconds), should at least be 2 to prevent symptom flickering
---@param character Character The character to set the symptom on.
---@param symptomidentifer string The identifier of the symptom.
---@param duration integer The number of human updates it lasts for.
function NTC.SetSymptomTrue(character, symptomidentifer, duration)
if duration == nil then duration = 2 end
NTC.AddEmptyCharacterData(character)
local data = NTC.GetCharacterData(character)
data[symptomidentifer] = duration
NTC.CharacterData[character.ID] = data
end
-- use this function to suppress symptoms temporarily. this takes precedence over NTC.SetSymptomTrue.
-- duration is in humanupdates (~2 seconds), should at least be 2 to prevent symptom flickering
---@param character Character The character to set the symptom on.
---@param symptomidentifer string The identifier of the symptom.
---@param duration integer The number of human updates it lasts for.
function NTC.SetSymptomFalse(character, symptomidentifer, duration)
if duration == nil then duration = 2 end
NTC.AddEmptyCharacterData(character)
local data = NTC.GetCharacterData(character)
data["!" .. symptomidentifer] = duration
NTC.CharacterData[character.ID] = data
end
-- usage example: anywhere in your lua code, cause 4 seconds (2 humanupdates) of pale skin with this:
-- NTC.SetSymptomTrue(targetCharacter,"sym_paleskin",2)
-- a list of possible symptom identifiers:
-- sym_unconsciousness
-- tachycardia
-- hyperventilation
-- hypoventilation
-- dyspnea
-- sym_cough
-- sym_paleskin
-- sym_lightheadedness
-- sym_blurredvision
-- sym_confusion
-- sym_headache
-- sym_legswelling
-- sym_weakness
-- sym_wheezing
-- sym_vomiting
-- sym_nausea
-- sym_hematemesis
-- sym_fever
-- sym_abdomdiscomfort
-- sym_bloating
-- sym_jaundice
-- sym_sweating
-- sym_palpitations
-- sym_craving
-- pain_abdominal
-- pain_chest
-- lockleftarm
-- lockrightarm
-- lockleftleg
-- lockrightleg
-- with the following identifiers you can either cause things or prevent them.
-- i recommend setting the duration when using these to cause things to 1.
-- triggersym_seizure
-- triggersym_coma
-- triggersym_stroke
-- triggersym_heartattack
-- triggersym_cardiacarrest
-- triggersym_respiratoryarrest
-- prints all of the current compatibility data in the chat
-- might be useful for debugging
function NTC.DebugPrintAllData()
local res = "neurotrauma compatibility data:\n"
for key, value in pairs(NTC.CharacterData) do
res = res .. "\n" .. value["character"].Name
for key2, value2 in pairs(value) do
res = res .. "\n " .. tostring(key2) .. " : " .. tostring(value2)
end
end
PrintChat(res)
end
NTC.PreHumanUpdateHooks = {}
-- use this function to add a function to be executed before humanupdate with a character parameter
---@param func function The actual function to be called before a human update.
function NTC.AddPreHumanUpdateHook(func)
NTC.PreHumanUpdateHooks[#NTC.PreHumanUpdateHooks + 1] = func
end
NTC.HumanUpdateHooks = {}
-- use this function to add a function to be executed after humanupdate with a character parameter
---@param func function The actual function to be called after a human update.
function NTC.AddHumanUpdateHook(func)
NTC.HumanUpdateHooks[#NTC.HumanUpdateHooks + 1] = func
end
NTC.OnDamagedHooks = {}
-- use this function to add a function to be executed after ondamaged
-- with a characterhealth, attack result and limb parameter
---@param func function The actual function to be called on damaged.
function NTC.AddOnDamagedHook(func)
NTC.OnDamagedHooks[#NTC.OnDamagedHooks + 1] = func
end
NTC.ModifyingOnDamagedHooks = {}
-- use this function to add a function to be executed before ondamaged
-- with a characterhealth, afflictions and limb parameter, and afflictions return type
---@param func function The actual function to be called on damaged. Modifying.
function NTC.AddModifyingOnDamagedHook(func)
NTC.ModifyingOnDamagedHooks[#NTC.ModifyingOnDamagedHooks + 1] = func
end
NTC.CharacterSpeedMultipliers = {}
-- use this function to multiply a characters speed for one human update.
-- should always be called from within a prehumanupdate hook
---@param character Character The character to set the speed multiplier to.
---@param multiplier number The value of the multiplier.
function NTC.MultiplySpeed(character, multiplier)
if NTC.CharacterSpeedMultipliers[character] == nil then
NTC.CharacterSpeedMultipliers[character] = multiplier
else
NTC.CharacterSpeedMultipliers[character] = NTC.CharacterSpeedMultipliers[character] * multiplier
end
end
-- use this function to register an affliction to be detected by the hematology analyzer
---@param identifier string The identifier of the affliction to be visible on hematology.
function NTC.AddHematologyAffliction(identifier)
Timer.Wait(function()
if not HF.TableContains(NT.HematologyDetectable, identifier) then
table.insert(NT.HematologyDetectable, identifier)
end
end, 1)
end
-- use this function to register an affliction to be healed by sutures
-- identifier: the identifier of the affliction to be healed
-- surgeryskillgain: how much surgery skill is gained by healing this affliction (optional, default: 0)
-- requiredaffliction: what affliction has to be present alongside the healed affliction for it to get healed (optional, default: none)
-- func: a function that gets run if the affliction is present. if provided, doesnt heal the affliction automatically (optional, default: none)
-- func(item, usingCharacter, targetCharacter, limb)
---@param identifier string The identifier of the affliction to be cured by sutures.
---@param surgeryskillgain number The surgeryskill increase from sutured affliction.
---@param requiredaffliction string The identifier of the requiredaffliction (Might need more documentation!)
---@param func function The function to be called when the affliction is cured with sutures.
function NTC.AddSuturedAffliction(identifier, surgeryskillgain, requiredaffliction, func)
Timer.Wait(function()
if not HF.TableContains(NT.SutureAfflictions, identifier) then
NT.SutureAfflictions[identifier] = {
xpgain = surgeryskillgain,
case = requiredaffliction,
func = func,
}
end
end, 1)
end
-- use this function to register an affliction to be healed by drainage
---@param identifier string The identifier of the affliction to be healed.
function NTC.AddDrainageAffliction(identifier)
Timer.Wait(function()
if not HF.TableContains(NT.DrainageAfflictions, identifier) then
table.insert(NT.DrainageAfflictions, identifier)
end
end, 1)
end
NTC.AfflictionsAffectingVitality = {
bleeding = true,
bleedingnonstop = true,
burn = true,
acidburn = true,
lacerations = true,
gunshotwound = true,
bitewounds = true,
explosiondamage = true,
blunttrauma = true,
internaldamage = true,
organdamage = true,
cerebralhypoxia = true,
gangrene = true,
th_amputation = true,
sh_amputation = true,
suturedw = true,
alcoholaddiction = true,
opiateaddiction = true,
}
-- use this function to register an affliction that will cause vitality damage. (Might need more documentation!)
---@param identifier string The identifier of the affliction to cause damage.
function NTC.AddAfflictionAffectingVitality(identifier)
NTC.AfflictionsAffectingVitality[identifier] = true
end
-- these functions are used by neurotrauma to check for symptom overrides
---@param character Character The character to check the symptom on.
---@param symptomidentifer string The identifier of the symptom.
---@return boolean
function NTC.GetSymptom(character, symptomidentifer)
local chardata = NTC.GetCharacterData(character)
if chardata == nil then return false end
local durationleft = chardata[symptomidentifer]
if durationleft == nil then return false end
return true
end
---@param character Character The character to check the symptom false on.
---@param symptomidentifer string The identifier of the symptom false.
---@return boolean
function NTC.GetSymptomFalse(character, symptomidentifer)
local chardata = NTC.GetCharacterData(character)
if chardata == nil then return false end
local durationleft = chardata["!" .. symptomidentifer]
if durationleft == nil then return false end
return true
end
-- sets multiplier data for one humanupdate, should be called from within a humanupdate hook
---@param character Character The character to set the multiplier on.
---@param multiplieridentifier string The identifier of the multiplier.
---@param multiplier number The multiplier number.
function NTC.SetMultiplier(character, multiplieridentifier, multiplier)
NTC.AddEmptyCharacterData(character)
local data = NTC.GetCharacterData(character)
data["mult_" .. multiplieridentifier] = NTC.GetMultiplier(character, multiplieridentifier) * multiplier
NTC.CharacterData[character.ID] = data
end
---@param character Character The character to set the multiplier on.
---@param multiplieridentifier string The identifier of the multiplier.
function NTC.GetMultiplier(character, multiplieridentifier)
local data = NTC.GetCharacterData(character)
if data == nil or data["mult_" .. multiplieridentifier] == nil then return 1 end
return data["mult_" .. multiplieridentifier]
end
-- sets tag data for one humanupdate, should be called from within a humanupdate hook
---@param character Character The character to set the tag on.
---@param multiplieridentifier string The identifier of the tag.
function NTC.SetTag(character, tagidentifier)
NTC.AddEmptyCharacterData(character)
local data = NTC.GetCharacterData(character)
data["tag_" .. tagidentifier] = 1
end
function NTC.HasTag(character, tagidentifier)
local data = NTC.GetCharacterData(character)
if data == nil or data["tag_" .. tagidentifier] == nil then return false end
return true
end
-- // Utility functions //
-- don't concern yourself with these
function NTC.AddEmptyCharacterData(character)
if NTC.GetCharacterData(character) ~= nil then return end
local newdat = {}
newdat["character"] = character
NTC.CharacterData[character.ID] = newdat
end
function NTC.CheckChardataEmpty(character)
local chardat = NTC.GetCharacterData(character)
if chardat == nil or HF.TableSize(chardat) > 1 then return end
-- remove entry from data
NTC.CharacterData[character.ID] = nil
end
function NTC.GetCharacterData(character)
return NTC.CharacterData[character.ID]
end
function NTC.TickCharacter(character)
local chardata = NTC.GetCharacterData(character)
if chardata == nil then return end
for key, value in pairs(chardata) do
if key ~= "character" then
if HF.StartsWith(key, "mult_") then -- multipliers
chardata[key] = nil
NTC.CheckChardataEmpty(character)
else -- symptoms
local durationleft = value
if durationleft ~= nil and durationleft > 1 then
chardata[key] = durationleft - 1
else
chardata[key] = nil
NTC.CheckChardataEmpty(character)
end
end
end
end
NTC.CharacterData[character.ID] = chardata
end
function NTC.GetSpeedMultiplier(character)
if NTC.CharacterSpeedMultipliers[character] ~= nil then return NTC.CharacterSpeedMultipliers[character] end
return 1
end
+733
View File
@@ -0,0 +1,733 @@
-- Hooks Lua event "character.applyDamage" to cause NT afflictions after attacks depending on the damaging affliction defined here in NT.OnDamagedMethods
local function getCalculatedConcussionReduction(armor, strength)
if armor == nil then return 0 end
local reduction = 0
if armor.HasTag("deepdiving") or armor.HasTag("deepdivinglarge") then
local modifiers = armor.GetComponentString("Wearable").DamageModifiers
for modifier in modifiers do
if string.find(modifier.AfflictionIdentifiers, "concussion") ~= nil then
reduction = strength - strength * modifier.DamageMultiplier
end
end
elseif armor.HasTag("smallitem") then
local modifiers = armor.GetComponentString("Wearable").DamageModifiers
for modifier in modifiers do
if string.find(modifier.AfflictionIdentifiers, "concussion") ~= nil then
reduction = strength - strength * modifier.DamageMultiplier
end
end
end
return reduction
end
Hook.Add(
"character.damageLimb",
"NT.ondamagedby",
function(
character,
worldPosition,
hitLimb,
afflictions,
stun,
playSound,
attackImpulse,
attacker,
damageMultiplier,
allowStacking,
penetration,
shouldImplode
)
if -- invalid attack data, don't do anything
character == nil
or character.IsDead
or not character.IsHuman
or afflictions == nil
or hitLimb == nil
or hitLimb.IsSevered
or attacker == nil
or not NTConfig.Get("NT_Calculations", true)
then
return
end
local creatureCategory = NTConfig.Get("NT_creatureNoFallDamage", 1)
-- they make the game miserable with falldamage on
for val in creatureCategory do
if attacker.SpeciesName == val then
HF.AddAffliction(character, "stopcreatureabuse", 2)
break
end
end
end
)
Hook.Add("character.applyDamage", "NT.ondamaged", function(characterHealth, attackResult, hitLimb)
--print(hitLimb.HealthIndex or hitLimb ~= nil)
if -- invalid attack data, don't do anything
characterHealth == nil
or characterHealth.Character == nil
or characterHealth.Character.IsDead
or not characterHealth.Character.IsHuman
or attackResult == nil
or attackResult.Afflictions == nil
or #attackResult.Afflictions <= 0
or hitLimb == nil
or hitLimb.IsSevered
or not NTConfig.Get("NT_Calculations", true)
then
return
end
if not HF.HasAffliction(characterHealth.Character, "luabotomy") then
HF.SetAffliction(characterHealth.Character, "luabotomy", 1)
end
local afflictions = attackResult.Afflictions
-- ntc
-- modifying ondamaged hooks
for key, val in pairs(NTC.ModifyingOnDamagedHooks) do
afflictions = val(characterHealth, afflictions, hitLimb)
end
local identifier = ""
local methodtorun = nil
for value in afflictions do
-- execute fitting method, if available
identifier = value.Prefab.Identifier.Value
methodtorun = NT.OnDamagedMethods[identifier]
if methodtorun ~= nil then
-- make resistance from afflictions apply
local resistance = HF.GetResistance(characterHealth.Character, identifier, hitLimb.type)
local strength = value.Strength * (1 - resistance)
methodtorun(characterHealth.Character, strength, hitLimb.type)
end
end
-- ntc
-- ondamaged hooks
for key, val in pairs(NTC.OnDamagedHooks) do
val(characterHealth, attackResult, hitLimb)
end
end)
NT.OnDamagedMethods = {}
local function HasLungs(c)
return not HF.HasAffliction(c, "lungremoved")
end
local function HasHeart(c)
return not HF.HasAffliction(c, "heartremoved")
end
-- cause foreign bodies, rib fractures, pneumothorax, tamponade, internal bleeding, fractures, neurotrauma
NT.OnDamagedMethods.gunshotwound = function(character, strength, limbtype)
limbtype = HF.NormalizeLimbType(limbtype)
local causeFullForeignBody = false
-- torso specific
if strength >= 1 and limbtype == LimbType.Torso then
local hitOrgan = false
if
HF.Chance(
HF.Clamp(strength * 0.02, 0, 0.3)
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
)
then
NT.BreakLimb(character, limbtype)
causeFullForeignBody = true
end
if
HasLungs(character)
and HF.Chance(
0.3 * NTC.GetMultiplier(character, "pneumothoraxchance") * NTConfig.Get("NT_pneumothoraxChance", 1)
)
then
HF.AddAffliction(character, "pneumothorax", 5)
HF.AddAffliction(character, "lungdamage", strength)
HF.AddAffliction(character, "organdamage", strength / 4)
hitOrgan = true
end
if
HasHeart(character)
and hitOrgan == false
and strength >= 5
and HF.Chance(
strength / 50 * NTC.GetMultiplier(character, "tamponadechance") * NTConfig.Get("NT_tamponadeChance", 1)
)
then
HF.AddAffliction(character, "tamponade", 5)
HF.AddAffliction(character, "heartdamage", strength)
HF.AddAffliction(character, "organdamage", strength / 4)
hitOrgan = true
end
if strength >= 5 then HF.AddAffliction(character, "internalbleeding", strength * HF.RandomRange(0.3, 0.6)) end
-- liver and kidney damage
if hitOrgan == false and strength >= 2 and HF.Chance(0.5) then
HF.AddAfflictionLimb(character, "organdamage", limbtype, strength / 4)
if HF.Chance(0.5) then
HF.AddAffliction(character, "liverdamage", strength)
else
HF.AddAffliction(character, "kidneydamage", strength)
end
end
end
-- head
if strength >= 1 and limbtype == LimbType.Head then
if
HF.Chance(
strength / 90 * NTC.GetMultiplier(character, "anyfracturechance") * NTConfig.Get("NT_fractureChance", 1)
)
then
NT.BreakLimb(character, limbtype)
causeFullForeignBody = true
end
if strength >= 5 and HF.Chance(0.7) then
HF.AddAffliction(character, "cerebralhypoxia", strength * HF.RandomRange(0.1, 0.4))
end
end
-- extremities
if strength >= 1 and HF.LimbIsExtremity(limbtype) then
if
NT.LimbIsBroken(character, limbtype)
and not NT.LimbIsAmputated(character, limbtype)
and HF.Chance(
strength
/ 60
* NTC.GetMultiplier(character, "traumamputatechance")
* NTConfig.Get("NT_traumaticAmputationChance", 1)
)
then
NT.TraumamputateLimb(character, limbtype)
end
if
HF.Chance(
strength / 60 * NTC.GetMultiplier(character, "anyfracturechance") * NTConfig.Get("NT_fractureChance", 1)
)
then
NT.BreakLimb(character, limbtype)
causeFullForeignBody = true
end
end
-- foreign bodies
if causeFullForeignBody then
HF.AddAfflictionLimb(
character,
"foreignbody",
limbtype,
HF.Clamp(strength, 0, 30) * NTC.GetMultiplier(character, "foreignbodymultiplier")
)
else
if HF.Chance(0.75) then
HF.AddAfflictionLimb(
character,
"foreignbody",
limbtype,
HF.Clamp(strength / 4, 0, 20) * NTC.GetMultiplier(character, "foreignbodymultiplier")
)
end
end
end
-- cause foreign bodies, rib fractures, pneumothorax, internal bleeding, concussion, fractures
NT.OnDamagedMethods.explosiondamage = function(character, strength, limbtype)
limbtype = HF.NormalizeLimbType(limbtype)
if HF.Chance(0.75) then
HF.AddAfflictionLimb(
character,
"foreignbody",
limbtype,
strength / 2 * NTC.GetMultiplier(character, "foreignbodymultiplier")
)
end
-- torso specific
if strength >= 1 and limbtype == LimbType.Torso then
if
strength >= 10
and HF.Chance(
strength / 50 * NTC.GetMultiplier(character, "anyfracturechance") * NTConfig.Get("NT_fractureChance", 1)
)
then
NT.BreakLimb(character, limbtype)
end
if
HasLungs(character)
and strength >= 5
and HF.Chance(
strength
/ 50
* NTC.GetMultiplier(character, "pneumothoraxchance")
* NTConfig.Get("NT_pneumothoraxChance", 1)
)
then
HF.AddAffliction(character, "pneumothorax", 5)
end
if strength >= 5 then HF.AddAffliction(character, "internalbleeding", strength * HF.RandomRange(0.2, 0.5)) end
end
-- head
if strength >= 1 and limbtype == LimbType.Head then
if strength >= 15 and HF.Chance(math.min(strength / 60, 0.7)) then
local armor1 = character.Inventory.GetItemInLimbSlot(InvSlotType.OuterClothes)
local armor2 = character.Inventory.GetItemInLimbSlot(InvSlotType.Head)
local reduceddmg = math.max(
10
- getCalculatedConcussionReduction(armor1, 10, limbtype)
- getCalculatedConcussionReduction(armor2, 10, limbtype),
0
)
HF.AddAfflictionResisted(character, "concussion", reduceddmg)
end
if
strength >= 15
and HF.Chance(
math.min(strength / 60, 0.7)
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
)
then
NT.BreakLimb(character, limbtype)
end
if
strength >= 15
and HF.Chance(
math.min(strength / 60, 0.7)
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
)
then
HF.AddAffliction(character, "n_fracture", 5)
end
if strength >= 75 and HF.Chance(0.25) then
-- drop previously held item
local previtem = HF.GetHeadWear(character)
if previtem ~= nil then previtem.Drop(character, true) end
NT.TraumamputateLimb(character, limbtype)
end
end
-- extremities
if strength >= 1 and HF.LimbIsExtremity(limbtype) then
if
NT.LimbIsBroken(character, limbtype)
and not NT.LimbIsAmputated(character, limbtype)
and HF.Chance(
strength
/ 60
* NTC.GetMultiplier(character, "traumamputatechance")
* NTConfig.Get("NT_traumaticAmputationChance", 1)
)
then
NT.TraumamputateLimb(character, limbtype)
end
if
HF.Chance(
strength / 60 * NTC.GetMultiplier(character, "anyfracturechance") * NTConfig.Get("NT_fractureChance", 1)
)
then
NT.BreakLimb(character, limbtype)
end
if
HF.Chance(
0.35 * NTC.GetMultiplier(character, "dislocationchance") * NTConfig.Get("NT_dislocationChance", 1)
) and not NT.LimbIsAmputated(character, limbtype)
then
NT.DislocateLimb(character, limbtype)
end
end
end
-- cause rib fractures, pneumothorax, internal bleeding, concussion, fractures
NT.OnDamagedMethods.bitewounds = function(character, strength, limbtype)
limbtype = HF.NormalizeLimbType(limbtype)
-- torso specific
if strength >= 1 and limbtype == LimbType.Torso then
if
strength >= 10
and HF.Chance(
(strength - 10)
/ 50
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
)
then
NT.BreakLimb(character, limbtype)
end
if
HasLungs(character)
and strength >= 5
and HF.Chance(
(strength - 5)
/ 50
* NTC.GetMultiplier(character, "pneumothoraxchance")
* NTConfig.Get("NT_pneumothoraxChance", 1)
)
then
HF.AddAffliction(character, "pneumothorax", 5)
end
if strength >= 5 then HF.AddAffliction(character, "internalbleeding", strength * HF.RandomRange(0.2, 0.5)) end
end
-- head
if strength >= 1 and limbtype == LimbType.Head then
if strength >= 15 and HF.Chance(math.min(strength / 60, 0.7)) then
local armor1 = character.Inventory.GetItemInLimbSlot(InvSlotType.OuterClothes)
local armor2 = character.Inventory.GetItemInLimbSlot(InvSlotType.Head)
local reduceddmg = math.max(
10
- getCalculatedConcussionReduction(armor1, 10, limbtype)
- getCalculatedConcussionReduction(armor2, 10, limbtype),
0
)
HF.AddAfflictionResisted(character, "concussion", reduceddmg)
end
if
strength >= 15
and HF.Chance(
math.min((strength - 10) / 60, 0.7)
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
)
then
NT.BreakLimb(character, limbtype)
end
end
-- extremities
if strength >= 1 and HF.LimbIsExtremity(limbtype) then
if
NT.LimbIsBroken(character, limbtype)
and not NT.LimbIsAmputated(character, limbtype)
and HF.Chance(
(strength - 5)
/ 60
* NTC.GetMultiplier(character, "traumamputatechance")
* NTConfig.Get("NT_traumaticAmputationChance", 1)
)
then
NT.TraumamputateLimb(character, limbtype, character.LastAttacker)
end
if
HF.Chance(
(strength - 5)
/ 60
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
)
then
NT.BreakLimb(character, limbtype)
end
end
end
-- cause rib fractures, pneumothorax, tamponade, internal bleeding, fractures
NT.OnDamagedMethods.lacerations = function(character, strength, limbtype)
limbtype = HF.NormalizeLimbType(limbtype)
-- torso specific
if strength >= 1 and limbtype == LimbType.Torso then
if
strength >= 10
and HF.Chance(
(strength - 10)
/ 50
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
)
then
NT.BreakLimb(character, limbtype)
end
if
HasLungs(character)
and strength >= 5
and HF.Chance(
(strength - 5)
/ 50
* NTC.GetMultiplier(character, "pneumothoraxchance")
* NTConfig.Get("NT_pneumothoraxChance", 1)
)
then
HF.AddAffliction(character, "pneumothorax", 5)
end
if
HasHeart(character)
and strength >= 5
and HF.Chance(
(strength - 5)
/ 50
* NTC.GetMultiplier(character, "tamponadechance")
* NTConfig.Get("NT_tamponadeChance", 1)
)
then
HF.AddAffliction(character, "tamponade", 5)
end
if strength >= 5 then HF.AddAffliction(character, "internalbleeding", strength * HF.RandomRange(0.2, 0.5)) end
end
-- head
if strength >= 1 and limbtype == LimbType.Head then
if
strength >= 15
and HF.Chance(
math.min((strength - 15) / 60, 0.7)
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
)
then
NT.BreakLimb(character, limbtype)
end
end
-- extremities
if strength >= 1 and HF.LimbIsExtremity(limbtype) then
if
NT.LimbIsBroken(character, limbtype)
and not NT.LimbIsAmputated(character, limbtype)
and HF.Chance(
strength
/ 60
* NTC.GetMultiplier(character, "traumamputatechance")
* NTConfig.Get("NT_traumaticAmputationChance", 1)
)
then
NT.TraumamputateLimb(character, limbtype)
end
if
HF.Chance(
(strength - 5)
/ 60
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
)
then
NT.BreakLimb(character, limbtype)
end
end
end
-- cause rib fractures, organ damage, pneumothorax, concussion, fractures, neurotrauma
NT.OnDamagedMethods.blunttrauma = function(character, strength, limbtype)
limbtype = HF.NormalizeLimbType(limbtype)
local fractureImmune = HF.HasAffliction(character, "cpr_fracturebuff")
-- torso
if not fractureImmune and strength >= 1 and limbtype == LimbType.Torso then
if
HF.Chance(
strength / 50 * NTC.GetMultiplier(character, "anyfracturechance") * NTConfig.Get("NT_fractureChance", 1)
)
then
NT.BreakLimb(character, limbtype)
end
HF.AddAffliction(character, "lungdamage", strength * HF.RandomRange(0, 1))
HF.AddAffliction(character, "heartdamage", strength * HF.RandomRange(0, 1))
HF.AddAffliction(character, "liverdamage", strength * HF.RandomRange(0, 1))
HF.AddAffliction(character, "kidneydamage", strength * HF.RandomRange(0, 1))
HF.AddAffliction(character, "organdamage", strength * HF.RandomRange(0, 1))
if
HasLungs(character)
and strength >= 5
and HF.Chance(
strength
/ 50
* NTC.GetMultiplier(character, "pneumothoraxchance")
* NTConfig.Get("NT_pneumothoraxChance", 1)
)
then
HF.AddAffliction(character, "pneumothorax", 5)
end
end
-- head
if not fractureImmune and strength >= 1 and limbtype == LimbType.Head then
if strength >= 15 and HF.Chance(math.min(strength / 60, 0.7)) then
local armor1 = character.Inventory.GetItemInLimbSlot(InvSlotType.OuterClothes)
local armor2 = character.Inventory.GetItemInLimbSlot(InvSlotType.Head)
local reduceddmg = math.max(
10
- getCalculatedConcussionReduction(armor1, 10, limbtype)
- getCalculatedConcussionReduction(armor2, 10, limbtype),
0
)
HF.AddAfflictionResisted(character, "concussion", reduceddmg)
end
if
strength >= 15
and HF.Chance(
math.min((strength - 10) / 60, 0.7)
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
)
then
NT.BreakLimb(character, limbtype)
end
if
strength >= 15
and HF.Chance(
math.min((strength - 10) / 60, 0.7)
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
)
then
HF.AddAffliction(character, "n_fracture", 5)
end
if strength >= 5 and HF.Chance(0.7) then
HF.AddAffliction(character, "cerebralhypoxia", strength * HF.RandomRange(0.1, 0.4))
end
end
-- extremities
if not fractureImmune and strength >= 1 and HF.LimbIsExtremity(limbtype) then
if
strength > 15
and NT.LimbIsBroken(character, limbtype)
and not NT.LimbIsAmputated(character, limbtype)
and HF.Chance(
strength
/ 100
* NTC.GetMultiplier(character, "traumamputatechance")
* NTConfig.Get("NT_traumaticAmputationChance", 1)
)
then
NT.TraumamputateLimb(character, limbtype)
end
if
HF.Chance(
(strength - 2)
/ 60
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
)
then
NT.BreakLimb(character, limbtype)
end
if
HF.Chance(
HF.Clamp((strength - 2) / 80, 0, 0.5)
* NTC.GetMultiplier(character, "dislocationchance")
* NTConfig.Get("NT_dislocationChance", 1)
) and not NT.LimbIsAmputated(character, limbtype)
then
NT.DislocateLimb(character, limbtype)
end
end
end
-- cause rib fractures, organ damage, pneumothorax, concussion, fractures
NT.OnDamagedMethods.internaldamage = function(character, strength, limbtype)
limbtype = HF.NormalizeLimbType(limbtype)
-- torso
if strength >= 1 and limbtype == LimbType.Torso then
if
HF.Chance(
(strength - 5)
/ 50
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
)
then
NT.BreakLimb(character, limbtype)
end
HF.AddAffliction(character, "lungdamage", strength * HF.RandomRange(0, 1))
HF.AddAffliction(character, "heartdamage", strength * HF.RandomRange(0, 1))
HF.AddAffliction(character, "liverdamage", strength * HF.RandomRange(0, 1))
HF.AddAffliction(character, "kidneydamage", strength * HF.RandomRange(0, 1))
HF.AddAffliction(character, "organdamage", strength * HF.RandomRange(0, 1))
if
HasLungs(character)
and strength >= 5
and HF.Chance(
(strength - 5)
/ 50
* NTC.GetMultiplier(character, "pneumothoraxchance")
* NTConfig.Get("NT_pneumothoraxChance", 1)
)
then
HF.AddAffliction(character, "pneumothorax", 5)
end
end
-- head
if strength >= 1 and limbtype == LimbType.Head then
if strength >= 15 and HF.Chance(math.min(strength / 60, 0.7)) then
local armor1 = character.Inventory.GetItemInLimbSlot(InvSlotType.OuterClothes)
local armor2 = character.Inventory.GetItemInLimbSlot(InvSlotType.Head)
local reduceddmg = math.max(
10
- getCalculatedConcussionReduction(armor1, 10, limbtype)
- getCalculatedConcussionReduction(armor2, 10, limbtype),
0
)
HF.AddAfflictionResisted(character, "concussion", reduceddmg)
end
if
strength >= 15
and HF.Chance(
math.min((strength - 5) / 60, 0.7)
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
)
then
NT.BreakLimb(character, limbtype)
end
if
strength >= 15
and HF.Chance(
math.min((strength - 5) / 60, 0.7)
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
)
then
HF.AddAffliction(character, "n_fracture", 5)
end
end
-- extremities
if strength >= 1 and HF.LimbIsExtremity(limbtype) then
if
strength > 10
and NT.LimbIsBroken(character, limbtype)
and not NT.LimbIsAmputated(character, limbtype)
and HF.Chance(
(strength - 10)
/ 60
* NTC.GetMultiplier(character, "traumamputatechance")
* NTConfig.Get("NT_traumaticAmputationChance", 1)
)
then
NT.TraumamputateLimb(character, limbtype)
end
if
HF.Chance(
(strength - 5)
/ 60
* NTC.GetMultiplier(character, "anyfracturechance")
* NTConfig.Get("NT_fractureChance", 1)
)
then
NT.BreakLimb(character, limbtype)
end
if
HF.Chance(
0.25 * NTC.GetMultiplier(character, "dislocationchance") * NTConfig.Get("NT_dislocationChance", 1)
) and not NT.LimbIsAmputated(character, limbtype)
then
NT.DislocateLimb(character, limbtype)
end
end
end
+20
View File
@@ -0,0 +1,20 @@
-- Hooks Lua event "Barotrauma.Character" to apply vanilla burning (formerly NT onfire) affliction and set a human on fire
Hook.HookMethod("Barotrauma.Character", "ApplyStatusEffects", function(instance, ptable)
if ptable.actionType == ActionType.OnFire then
local function ApplyBurn(character, limbtype)
HF.AddAfflictionLimb(character, "burning", limbtype, ptable.deltaTime * 3)
end
if instance.IsHuman then
if not HF.HasAffliction(instance, "luabotomy") then HF.SetAffliction(instance, "luabotomy", 1) end
ApplyBurn(instance, LimbType.Torso)
ApplyBurn(instance, LimbType.Head)
ApplyBurn(instance, LimbType.LeftArm)
ApplyBurn(instance, LimbType.RightArm)
ApplyBurn(instance, LimbType.LeftLeg)
ApplyBurn(instance, LimbType.RightLeg)
else
HF.AddAfflictionLimb(instance, "burning", instance.AnimController.MainLimb.type, ptable.deltaTime * 5)
end
end
end, Hook.HookMethodType.After)
+7
View File
@@ -0,0 +1,7 @@
-- Hooks XML Lua event "NT.causeScreams" to cause character to scream if config has enabled screaming
Hook.Add("NT.causeScreams", "NT.causeScreams", function(...)
if not NTConfig.Get("NT_screams", true) then return end
local character = table.pack(...)[3]
HF.SetAffliction(character, "screaming", 10)
end)
@@ -0,0 +1,13 @@
Hook.Patch("Barotrauma.Character", "Control", function(instance)
if instance.CharacterHealth.GetAfflictionStrengthByIdentifier("forceprone") > 1 then
instance.SetInput(InputType.Crouch, false, true)
end
end)
Hook.Patch("Barotrauma.Ragdoll", "get_ColliderHeightFromFloor", function(instance, ptable)
if instance.Character and instance.Character.CharacterHealth then
if instance.Character.CharacterHealth.GetAfflictionStrengthByIdentifier("forceprone") > 1 then
return Single(0.1)
end
end
end, Hook.HookMethodType.After)
+136
View File
@@ -0,0 +1,136 @@
-- Hooks a XML Lua event "surgerytable.update" to use for getting
-- Neurotrauma and vanilla character data with the surgical table or hospital bed
-- lifted and translated from betterhealthui
local NormalHeartrate = 60
local MaxTachycardiaHeartrate = 180
local MaxFibrillationHeartrate = 300
local function GetHeartrate(character)
if character == nil or character.CharacterHealth == nil or character.IsDead then return 0 end
local rate = NormalHeartrate
local cardiacarrest = character.CharacterHealth.GetAffliction("cardiacarrest")
-- return 0 rate if in cardiac arrest
if cardiacarrest ~= nil and cardiacarrest.Strength >= 0.5 then return 0 end
local tachycardia = character.CharacterHealth.GetAffliction("tachycardia")
local fibrillation = character.CharacterHealth.GetAffliction("fibrillation")
if fibrillation ~= nil then
rate = HF.Lerp(
MaxTachycardiaHeartrate,
MaxFibrillationHeartrate,
fibrillation.Strength / 100 * (1 + math.random() * 0.5)
)
elseif tachycardia ~= nil then
rate = HF.Lerp(NormalHeartrate, MaxTachycardiaHeartrate, tachycardia.Strength / 100)
end
return rate
end
local function GetPH(character)
if character == nil or character.CharacterHealth == nil then return 0 end
local acidosis = HF.GetAfflictionStrength(character, "acidosis", 0)
local alkalosis = HF.GetAfflictionStrength(character, "alkalosis", 0)
return alkalosis - acidosis
end
Hook.Add("surgerytable.update", "surgerytable.update", function(effect, deltaTime, item, targets, worldPosition)
-- fetch controller component
local controllerComponent = item.GetComponentString("Controller")
if controllerComponent == nil then
item.SendSignal("0", "state_out")
return
end
-- check if targets present
-- laying on the table
local target = controllerComponent.User
-- noone one the table? check the targets array for the one with the least vitality
if target == nil or not target.IsHuman then
local minVitality = 999
for index, value in ipairs(targets) do
if value.Name ~= nil and value.IsHuman and (value.Vitality < minVitality) then
minVitality = value.Vitality
target = value
end
end
end
-- no target found
if target == nil or not target.IsHuman then
item.SendSignal("0", "state_out")
return
end
-- send signals
item.SendSignal("1", "state_out")
if target.IsDead then
item.SendSignal("0", "alive_out")
else
item.SendSignal("1", "alive_out")
end
if target.IsDead or HF.HasAffliction(target, "sym_unconsciousness", 0.1) then
item.SendSignal("0", "conscious_out")
else
item.SendSignal("1", "conscious_out")
end
item.SendSignal(target.Name, "name_out")
item.SendSignal(tostring(HF.Round(target.Vitality)), "vitality_out")
if target.IsDead then
item.SendSignal("0", "bloodpressure_out")
else
item.SendSignal(tostring(HF.Round(HF.GetAfflictionStrength(target, "bloodpressure", 100))), "bloodpressure_out")
end
item.SendSignal(tostring(HF.Round(100 - HF.GetAfflictionStrength(target, "hypoxemia", 0))), "bloodoxygen_out")
item.SendSignal(tostring(HF.Round(HF.GetAfflictionStrength(target, "cerebralhypoxia", 0))), "neurotrauma_out")
item.SendSignal(tostring(HF.Round(HF.GetAfflictionStrength(target, "organdamage", 0))), "organdamage_out")
local heartrate = HF.Round(GetHeartrate(target))
item.SendSignal(tostring(heartrate), "heartrate_out")
local breathingrate = math.random(15, 18)
if HF.HasAffliction(target, "respiratoryarrest") or target.IsDead then
breathingrate = 0
elseif HF.HasAffliction(target, "hyperventilation") then
breathingrate = breathingrate + math.random(6, 8)
elseif HF.HasAffliction(target, "hypoventilation") then
breathingrate = breathingrate - math.random(6, 8)
end
item.SendSignal(tostring(breathingrate), "breathingrate_out")
item.SendSignal(tostring(HF.BoolToNum(HF.HasAffliction(target, "surgeryincision"), 1)), "insurgery_out")
if target.IsDead and target.causeOfDeath ~= nil then
item.SendSignal(HF.CauseOfDeathToString(target.causeOfDeath), "causeofdeath_out")
end
local bloodph = HF.Round(GetPH(target))
item.SendSignal(tostring(bloodph), "bloodph_out")
end)
--Hook.Add("surgerytable.forceon", "surgerytable.forceon", function (effect, deltaTime, item, targets, worldPosition)
-- -- fetch controller component
-- local controllerComponent = item.GetComponentString("Controller")
-- if controllerComponent == nil or controllerComponent.IsActive then return end
--
-- -- check if targets present
-- if targets == nil or #targets <= 0 then return end
-- local target = nil
-- for index, value in ipairs(targets) do
-- target=value
-- if target ~=nil then break end
-- end
-- if target == nil then return end
--
-- -- was experimenting with forcing the patient into laying down here, sort of worked... until 0 vitality.
-- -- it's too janky to be released.
--
-- -- target.Stun = 0
-- -- HF.SetAffliction(target,"givein",0)
-- -- controllerComponent.Select(target)
-- -- target.SelectedConstruction = item
--end)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+322
View File
@@ -0,0 +1,322 @@
-- set the below variable to true to enable debug and testing features
NT.TestingEnabled = false
Hook.Add("chatMessage", "NT.testing", function(msg, client)
if msg == "nt test" then -- a glorified suicide button
if client.Character == nil then return true end
HF.SetAfflictionLimb(client.Character, "gate_ta_ra", LimbType.RightArm, 100)
HF.SetAfflictionLimb(client.Character, "gate_ta_la", LimbType.LeftArm, 100)
HF.SetAfflictionLimb(client.Character, "gate_ta_rl", LimbType.RightLeg, 100)
HF.SetAfflictionLimb(client.Character, "gate_ta_ll", LimbType.LeftLeg, 100)
return true -- hide message
elseif msg == "nt unfuck" then -- a command to remove non-sensical stuff
if client.Character == nil then return true end
HF.SetAfflictionLimb(client.Character, "tll_amputation", LimbType.Head, 0)
HF.SetAfflictionLimb(client.Character, "trl_amputation", LimbType.Head, 0)
HF.SetAfflictionLimb(client.Character, "tla_amputation", LimbType.Head, 0)
HF.SetAfflictionLimb(client.Character, "tra_amputation", LimbType.Head, 0)
HF.SetAfflictionLimb(client.Character, "tll_amputation", LimbType.Torso, 0)
HF.SetAfflictionLimb(client.Character, "trl_amputation", LimbType.Torso, 0)
HF.SetAfflictionLimb(client.Character, "tla_amputation", LimbType.Torso, 0)
HF.SetAfflictionLimb(client.Character, "tra_amputation", LimbType.Torso, 0)
for key, character in pairs(Character.CharacterList) do
if not character.IsDead then
if character.IsHuman then
HF.AddAffliction(character, "luabotomypurger", 2)
if character.TeamID == 1 or character.TeamID == 2 then
Timer.Wait(function()
HF.SetAffliction(character, "luabotomy", 0.1)
end, 4000)
end
end
end
end
return true -- hide message
elseif msg == "nt1" then
if not NT.TestingEnabled then return end
-- insert testing stuff here
local test = { val = "true" }
local function testfunc(param)
param.val = "false"
end
print(test.val)
testfunc(test)
print(test.val)
return true
elseif msg == "nt2" then
if not NT.TestingEnabled then return end
-- insert other testing stuff here
local crewenum = Character.GetFriendlyCrew(client.Character)
local targetchar = nil
local i = 0
for char in crewenum do
print(char.Name)
targetchar = char
i = i + 1
if i == 2 then break end
end
client.SetClientCharacter(nil)
print(targetchar)
Timer.Wait(function()
client.SetClientCharacter(targetchar)
end, 50)
return true
end
end)
DebugConsole = LuaUserData.CreateStatic("Barotrauma.DebugConsole")
local function registerDebugCommands()
LuaUserData.MakeMethodAccessible(Descriptors["Barotrauma.DebugConsole"], "GetCharacterNames")
LuaUserData.MakeMethodAccessible(Descriptors["Barotrauma.DebugConsole"], "FindMatchingCharacter")
LuaUserData.MakeFieldAccessible(Descriptors["Barotrauma.CharacterHealth"], "afflictions")
LuaUserData.MakeFieldAccessible(Descriptors["Barotrauma.CharacterHealth"], "limbHealths")
LuaUserData.MakeMethodAccessible(
Descriptors["Barotrauma.CharacterHealth"],
"GetVitalityDecreaseWithVitalityMultipliers"
)
LuaUserData.RegisterType(
"System.Collections.Generic.Dictionary`2[[Barotrauma.Affliction],[Barotrauma.CharacterHealth+LimbHealth]]"
)
LuaUserData.RegisterType(
"System.Collections.Generic.KeyValuePair`2[[Barotrauma.Affliction],[Barotrauma.CharacterHealth+LimbHealth]]"
)
local function findCharacter(str)
local character = nil
if not str or str == "" or str == "/me" then
character = Character.Controlled
else
character = DebugConsole.FindMatchingCharacter({ str })
end
return character
end
Game.AddCommand(
"nt_listafflictions",
"nt_listafflictions [character name] [client/server]: Lists all afflictions on a character",
function(args)
if CLIENT and args[2] == "server" then
if Game.IsMultiplayer then
if not args[1] or args[1] == "/me" then
args[1] = Character.Controlled and Character.Controlled.Name or ""
end
Game.client.SendConsoleCommand("nt_listafflictions " .. '"' .. args[1] .. '"')
end
return
end
local target = findCharacter(args[1])
if not target then return end
print(target.Name, " vitality: ", target.Vitality, "/", target.MaxVitality, " Mass: ", target.Mass)
local genericafflictions, limbafflictions = {}, {}
for kvp in target.CharacterHealth.afflictions do
if kvp.Value then
if not limbafflictions[kvp.Value] then limbafflictions[kvp.Value] = {} end
table.insert(limbafflictions[kvp.Value], kvp.Key)
else
table.insert(genericafflictions, kvp.Key)
end
end
for limbhealth, afflictions in pairs(limbafflictions) do
print(limbhealth.Name or "Unnamed limb")
for affliction in afflictions do
print(
"# ",
affliction.Name,
" = ",
affliction.Strength,
" (vitality decrease: ",
target.CharacterHealth.GetVitalityDecreaseWithVitalityMultipliers(affliction),
")"
)
end
end
print("Generic afflictions")
for affliction in genericafflictions do
print(
"# ",
affliction.Name,
" = ",
affliction.Strength,
" (vitality decrease: ",
affliction.GetVitalityDecrease(target.CharacterHealth),
")"
)
end
end,
--GetValidArguments
function()
return { DebugConsole.GetCharacterNames(), { "client", "server" } }
end,
true
)
Game.AddCommand(
"nt_listcreatures",
"nt_listcreatures [printafflictionsgeneric/printafflictionsfull]: Lists all non-human creatures currently on the server",
function(args)
if CLIENT and Game.IsMultiplayer then
Game.client.SendConsoleCommand("nt_listcreatures " .. '"' .. args[1] .. '"')
return
end
local function printAfflictions(target, args)
local genericafflictions, limbafflictions = {}, {}
for kvp in target.CharacterHealth.afflictions do
if kvp.Value then
if not limbafflictions[kvp.Value] then limbafflictions[kvp.Value] = {} end
table.insert(limbafflictions[kvp.Value], kvp.Key)
else
table.insert(genericafflictions, kvp.Key)
end
end
if args[1] == "printafflictionsgeneric" or args[1] == "printafflictionsfull" then
print("Generic afflictions")
for affliction in genericafflictions do
print(
"# ",
affliction.Name,
" = ",
affliction.Strength,
" (vitality decrease: ",
affliction.GetVitalityDecrease(target.CharacterHealth),
")"
)
end
end
if args[1] == "printafflictionsfull" then
print("Limb afflictions")
for limbhealth, afflictions in pairs(limbafflictions) do
print(limbhealth.Name or "Unnamed limb")
for affliction in afflictions do
print(
"# ",
affliction.Name,
" = ",
affliction.Strength,
" (vitality decrease: ",
target.CharacterHealth.GetVitalityDecreaseWithVitalityMultipliers(affliction),
")"
)
end
end
end
end
for key, character in pairs(Character.CharacterList) do
if not character.IsHuman then
print(
character.SpeciesName,
" vitality: ",
character.Vitality,
"/",
character.MaxVitality,
" Mass: ",
character.Mass
)
if args[1] == "printafflictionsgeneric" or args[1] == "printafflictionsfull" then
printAfflictions(character, args)
end
end
end
end,
--GetValidArguments
function()
return { { "printafflictionsgeneric", "printafflictionsfull" } }
end,
true
)
Game.AddCommand(
"nt_nugget",
"nt_nugget [character name]: Nuggets the character",
function(args)
if CLIENT and Game.IsMultiplayer then
if not args[1] or args[1] == "/me" then
args[1] = Character.Controlled and Character.Controlled.Name or ""
end
Game.client.SendConsoleCommand("nt_nugget " .. '"' .. args[1] .. '"')
return
end
local target = findCharacter(args[1])
if not target then return end
HF.SetAfflictionLimb(target, "gate_ta_ra", LimbType.RightArm, 100)
HF.SetAfflictionLimb(target, "gate_ta_la", LimbType.LeftArm, 100)
HF.SetAfflictionLimb(target, "gate_ta_rl", LimbType.RightLeg, 100)
HF.SetAfflictionLimb(target, "gate_ta_ll", LimbType.LeftLeg, 100)
end,
--GetValidArguments
function()
return { DebugConsole.GetCharacterNames() }
end,
true
)
Game.AddCommand(
"nt_unnugget",
"nt_unnugget [character name]: Unnuggets the character",
function(args)
if CLIENT and Game.IsMultiplayer then
if not args[1] or args[1] == "/me" then
args[1] = Character.Controlled and Character.Controlled.Name or ""
end
Game.client.SendConsoleCommand("nt_unnugget " .. '"' .. args[1] .. '"')
return
end
local target = findCharacter(args[1])
if not target then return end
HF.SetAfflictionLimb(target, "tll_amputation", LimbType.Head, 0)
HF.SetAfflictionLimb(target, "trl_amputation", LimbType.Head, 0)
HF.SetAfflictionLimb(target, "tla_amputation", LimbType.Head, 0)
HF.SetAfflictionLimb(target, "tra_amputation", LimbType.Head, 0)
HF.SetAfflictionLimb(target, "tll_amputation", LimbType.Torso, 0)
HF.SetAfflictionLimb(target, "trl_amputation", LimbType.Torso, 0)
HF.SetAfflictionLimb(target, "tla_amputation", LimbType.Torso, 0)
HF.SetAfflictionLimb(target, "tra_amputation", LimbType.Torso, 0)
end,
--GetValidArguments
function()
return { DebugConsole.GetCharacterNames() }
end,
true
)
end
Game.AddCommand("nt_debug", "nt_debug : Enables debug neurotrauma commands", function()
if not NT.TestingEnabled then
print("neurotrauma debug enabled")
registerDebugCommands()
NT.TestingEnabled = true
local msg = Networking.Start("NT_debug")
Networking.Send(msg)
end
end, nil, true)
if CLIENT and Game.IsMultiplayer then Networking.Receive("NT_debug", function(msg)
registerDebugCommands()
end) end
if NT.TestingEnabled then registerDebugCommands() end
+4
View File
@@ -0,0 +1,4 @@
# TODO:
- implement body temp
- make the limb replacement surgery require osteosynthesis
- rest are probably in .lua files themselves