Add r reload

This commit is contained in:
2025-03-29 19:28:14 +01:00
parent 2dcbc9db7e
commit 5c2739d7da
4 changed files with 512 additions and 0 deletions

View File

@@ -0,0 +1,466 @@
if SERVER then return end
LuaUserData.RegisterType("Barotrauma.Items.Components.ItemContainer+SlotRestrictions")
LuaUserData.RegisterType('System.Collections.Immutable.ImmutableArray`1[[Barotrauma.Items.Components.ItemContainer+SlotRestrictions, Barotrauma]]')
LuaUserData.MakeFieldAccessible(Descriptors['Barotrauma.Items.Components.ItemContainer'], 'slotRestrictions')
LuaUserData.MakeFieldAccessible(Descriptors['Barotrauma.ItemInventory'], 'slots')
LuaUserData.MakeFieldAccessible(Descriptors["Barotrauma.CharacterInventory"], "slots")
-- 配置重试参数
local RETRY_CONFIG = {
INTERVAL = 0.3, -- 重试间隔(秒)
MAX_ATTEMPTS = 3, -- 最大尝试次数
VALIDITY_DURATION = 5,-- 记录有效期(秒)
GENERATION_INTERVAL = 0.5 -- 分代时间间隔
}
-- 状态存储表
local retryQueue = {} -- 结构:
-- {
-- [magID] =
-- {
-- generations =
-- {
-- [generationID] = { attempts, nextAttempt, expiry },
-- ...
-- },
-- mag = entityRef
-- },
-- ...
-- }
local function isSlotFull(slotRestriction, slot)
return slotRestriction.MaxStackSize - #slot.items
end
local function tryPutItemsInInventory(character, hand, anotherhand, handInv, handIEnumerable, anotherhandIEnumerable)
-- 重试队列管理
local function addToRetryQueue(mag)
if not mag then return end
local currentTime = Timer.GetTime()
local magID = mag.ID
-- 生成分代ID每0.5秒为一个分代)
local generationID = math.floor(currentTime / RETRY_CONFIG.GENERATION_INTERVAL)
-- 初始化队列条目
if not retryQueue[magID] then
retryQueue[magID] = {
mag = mag,
generations = {}
}
end
-- 更新分代记录
local entry = retryQueue[magID]
if not entry.generations[generationID] then
entry.generations[generationID] = {
attempts = 0,
nextAttempt = currentTime + RETRY_CONFIG.INTERVAL,
expiry = currentTime + RETRY_CONFIG.VALIDITY_DURATION
}
else
-- 延长该分代的过期时间
entry.generations[generationID].expiry = currentTime + RETRY_CONFIG.VALIDITY_DURATION
end
-- print(string.format("Added generation %d for %s", generationID, mag.Name))
end
if not handInv then return end
local handInvSlots = handInv.slots
local function getPlayerInvItemsWithoutHand()
local playerInvItems = character.Inventory.AllItemsMod
-- 去除双手持有的物品,避免在双持情况下互相抢弹药
for i = #playerInvItems, 1, -1 do
local item = playerInvItems[i]
if (hand and item.ID == hand.ID) or (anotherhand and item.ID == anotherhand.ID) then
table.remove(playerInvItems, i)
end
end
return playerInvItems
end
-- 内部堆叠实现原tryStackMagzine拆分
local function tryStackMagazineInternal(mag)
if not mag or mag.ConditionPercentage > 0 then
return false
end
-- 原有堆叠逻辑
local function tryStackInInventory(inventory, Mag)
local identifier = Mag.Prefab.Identifier
for i, slot in ipairs(inventory.slots) do
for _, item in ipairs(slot.items) do
if item.HasTag("weapon") then goto continue end
if item.Prefab.Identifier.Equals(identifier) and item.ConditionPercentage == 0 and item.ID ~= Mag.ID then -- 只有空弹匣可堆叠
if inventory.CanBePutInSlot(Mag, i-1) then
inventory.TryPutItem(Mag, i-1, false, true, nil)
return true
end
end
::continue::
end
end
return false
end
-- 尝试玩家库存
if tryStackInInventory(character.Inventory, mag) then
return true
end
-- 尝试子容器
for item in getPlayerInvItemsWithoutHand() do
if item.OwnInventory and tryStackInInventory(item.OwnInventory, mag) then
return true
end
end
return false
end
-- 外部入口函数替换原tryStackMagzine
local function tryStackMagzine(mag)
if not mag then return false end
-- 立即尝试
local success = tryStackMagazineInternal(mag)
-- 失败时加入队列
if not success then
-- 防止重复添加
addToRetryQueue(mag)
end
return success
end
-- -- 堆叠弹匣
-- local function tryStackMagzine(Mag)
-- if Mag == nil or Mag.ConditionPercentage ~= 0 then return false end
-- local function tryStackInInventory(inventory, Mag)
-- local identifier = Mag.Prefab.Identifier
-- for i, slot in ipairs(inventory.slots) do
-- for _, item in ipairs(slot.items) do
-- if item.HasTag("weapon") then goto continue end
-- if item.Prefab.Identifier.Equals(identifier) and item.ConditionPercentage == 0 and item.ID ~= Mag.ID then -- 只有空弹匣可堆叠
-- if inventory.TryPutItem(Mag, i-1, false, true, nil) then
-- return true
-- end
-- end
-- ::continue::
-- end
-- end
-- return false
-- end
-- -- 尝试将弹匣堆叠到玩家物品栏1-10
-- if tryStackInInventory(character.Inventory, Mag) then
-- return true
-- end
-- -- 尝试将弹匣堆叠到玩家背包、衣服等子物品栏
-- for item in getPlayerInvItemsWithoutHand() do
-- if item.OwnInventory then
-- if tryStackInInventory(item.OwnInventory, Mag) then
-- return true
-- end
-- end
-- end
-- return false
-- end
-- 卸载弹匣
local function unloadMag(index)
local unloadedMag = handInvSlots[index].items[1]
-- 尝试堆叠弹匣
if tryStackMagzine(unloadedMag) then return true end
local slots = character.Inventory.slots
-- 如果都失败了,优先尝试将弹匣放入玩家背包、衣服子物品栏
for i = #slots, 1, -1 do
if i == 4 or i == 5 or i == 8 then
if character.Inventory.TryPutItem(unloadedMag, i-1, false, true, nil) then
return true
end
end
end
-- 然后尝试将弹匣放入玩家物品栏1-10
for i = #slots, 1, -1 do
if i <= 8 or i == 19 then goto continue end
if character.Inventory.CanBePutInSlot(unloadedMag, i-1) then
character.Inventory.TryPutItem(unloadedMag, i-1, false, false, nil)
return true
end
::continue::
end
-- 保底情况将弹匣丢到地面暂时视为false目前bool未使用
unloadedMag.Drop(character, true, true)
return false
end
-- 根据 index 构建一个含有所有可用的弹药/弹匣的 table参数 num 是要寻找的数量
local function findAvailableItemInPlayerInv(index, num)
local itemTable = {}
for item in getPlayerInvItemsWithoutHand() do
local count = 0
-- 忽略掉所有带武器标签的物品,避免从其他武器中抢弹药
if item.HasTag("weapon") then goto continue end
if handInv.CanBePutInSlot(item, index) and item.ConditionPercentage > 0 then
if itemTable[item.Prefab.Identifier.value] == nil then itemTable[item.Prefab.Identifier.value] = {} end
table.insert(itemTable[item.Prefab.Identifier.value], item)
count = count + 1
if count >= num then break end
end
if item.OwnInventory then
for item2 in item.OwnInventory.AllItemsMod do
if handInv.CanBePutInSlot(item2, index) and item2.ConditionPercentage > 0 then
if itemTable[item2.Prefab.Identifier.value] == nil then itemTable[item2.Prefab.Identifier.value] = {} end
table.insert(itemTable[item2.Prefab.Identifier.value], item2)
count = count + 1
if count >= num then break end
end
end
end
::continue::
end
local maxLength = 0
local maxElement = {}
for identifier, items in pairs(itemTable) do
if #items > maxLength then
maxLength = #items
maxElement = itemTable[identifier]
end
end
return maxElement
end
-- 根据 index 寻找可用的弹匣但不要装入unloadedMag
local function findAvailableMagInPlayerInv(index, unloadedMag)
for item in getPlayerInvItemsWithoutHand() do
-- 忽略掉所有带武器标签的物品,避免从其他武器中抢弹药
if item.HasTag("weapon") then goto continue end
if item and item.ID ~= unloadedMag.ID and handInv.CanBePutInSlot(item, index) and item.ConditionPercentage > 0 then
return item
end
if item.OwnInventory then
for item2 in item.OwnInventory.AllItemsMod do
if item2 and item2.ID ~= unloadedMag.ID and handInv.CanBePutInSlot(item2, index) and item2.ConditionPercentage > 0 then
return item2
end
end
end
::continue::
end
return nil
end
-- 根据 identifier 构建一个含有所有可用的弹药/弹匣的 table参数 num 是要寻找的数量
local function findAvailableItemWithIdentifier(identifier, num)
local findTable = {}
local count = 0
for item in getPlayerInvItemsWithoutHand() do
-- 忽略掉所有带武器标签的物品,避免从其他武器中抢弹药
if item.HasTag("weapon") then goto continue end
if item.Prefab.Identifier.Equals(identifier) then
table.insert(findTable, item)
count = count + 1
if count >= num then
return findTable
end
end
if item.OwnInventory then
for item2 in item.OwnInventory.AllItemsMod do
if item2.Prefab.Identifier.Equals(identifier) then
table.insert(findTable, item2)
count = count + 1
if count >= num then
return findTable
end
end
end
end
::continue::
end
return findTable
end
-- 根据 identifier 返回一个可用于堆叠已有弹匣的物品
local function findAvailableForStackingInPlayerInv(identifier)
local itemList = {}
for item in getPlayerInvItemsWithoutHand() do
if item.HasTag("weapon") then goto continue end
if item.Prefab.Identifier.Equals(identifier) and item.ConditionPercentage > 0 then
table.insert(itemList, item)
end
if item.OwnInventory then
for item2 in item.OwnInventory.AllItemsMod do
if item2.Prefab.Identifier.Equals(identifier) and item2.ConditionPercentage > 0 then
table.insert(itemList, item2)
end
end
end
::continue::
end
-- 对 itemList 依照 ConditionPercentage 进行升序排序
table.sort(itemList, function(a, b) return a.ConditionPercentage < b.ConditionPercentage end)
return itemList
end
local function putItem(item, index, isForStacking, isForSplitting)
if item == nil or item.ConditionPercentage == 0 or item == hand or item == anotherhand then return end
if not handInv.TryPutItem(item, index, isForStacking, isForSplitting, character, true, true)
then return false end -- 如果上弹失败则返回false
return true
end
-- 对枪械中每个 SlotRestriction 进行处理
local itemContainer = handInv.Container
local i = math.max(itemContainer.ContainedStateIndicatorSlot + 1, 1) -- 准确定位弹匣的slot
local handInvSlotRestriction = itemContainer.slotRestrictions[i-1]
-- 空物品情况
if #handInvSlots[i].items == 0 then
for _, item in ipairs(findAvailableItemInPlayerInv(i - 1, isSlotFull(handInvSlotRestriction, handInvSlots[i]))) do
putItem(item, i - 1, false, false)
end
-- 已有可堆叠弹药的情况
elseif #handInvSlots[i].items > 0 and isSlotFull(handInvSlotRestriction, handInvSlots[i]) > 0 then
for _, item in ipairs(findAvailableItemWithIdentifier(handInvSlots[i].items[1].Prefab.Identifier, isSlotFull(handInvSlotRestriction, handInvSlots[i]))) do
putItem(item, i - 1, false, false)
end
end
-- 已有弹匣的情况
if isSlotFull(handInvSlotRestriction, handInvSlots[i]) == 0 and #handInvSlots[i].items == 1 and handInvSlots[i].items[1].ConditionPercentage ~= 100 then
local itemlist = findAvailableForStackingInPlayerInv(handInvSlots[i].items[1].Prefab.Identifier)
local item = itemlist[1]
if (#itemlist == 1 and handInvSlots[i].items[1].ConditionPercentage == 0) or (item and item.ConditionPercentage ~=100 and handInvSlots[i].items[1].ConditionPercentage == 0) then --特殊情况,只剩一个弹匣下处理堆叠问题
unloadMag(i)
putItem(item, i - 1, true, true)
end
if not putItem(item, i - 1, true, true) then -- 如果上弹失败,卸载弹匣
local unloadedMag = handInvSlots[i].items[1]
unloadMag(i)
-- 如果此时双手武器未装备,重新装备武器
local currentHand = character.Inventory.GetItemInLimbSlot(handIEnumerable[1])
local currentAnotherHand = character.Inventory.GetItemInLimbSlot(anotherhandIEnumerable[1])
if (currentHand == hand and currentAnotherHand == anotherhand) ~= true then
if hand and anotherhand and hand.ID == anotherhand.ID then -- 如果为双手武器
for _, handSlotType in ipairs { InvSlotType.LeftHand, InvSlotType.RightHand } do
local handSlotIndex = character.Inventory.FindLimbSlot(handSlotType)
if handSlotIndex >= 0 then
character.Inventory.TryPutItem(hand, handSlotIndex, true, false, character, true, true)
end
end
else -- 如果为单手武器或者双持武器
character.Inventory.TryPutItem(hand, character, handIEnumerable, true, true)
character.Inventory.TryPutItem(anotherhand, character, anotherhandIEnumerable, true, true)
end
end
local findMag = findAvailableMagInPlayerInv(i - 1, unloadedMag)
if #itemlist == 0 and unloadedMag.ConditionPercentage > 0 and findMag == nil then
putItem(unloadedMag, i - 1, false, false)
else
putItem(findMag, i - 1, false, false)
end
end
tryStackMagzine(item) -- 尝试堆叠空弹匣
end
-- 注册每帧检查在多人游戏对tryStackMagazine进行重试
Hook.Add("think", "magazineRetrySystem", function()
if not retryQueue then return end
local currentTime = Timer.GetTime()
-- 遍历所有条目
for magID, entry in pairs(retryQueue) do
local mag = entry.mag
local hasValidGenerations = false
-- 实体有效性检查
if not mag or mag.ID ~= magID then
retryQueue[magID] = nil
goto continue
end
for genID, genRecord in pairs(entry.generations) do
-- 清理过期分代
if currentTime > genRecord.expiry then
entry.generations[genID] = nil
-- print("Generation expired:", genID)
goto next_generation
end
-- 执行重试条件检查
if currentTime >= genRecord.nextAttempt then
-- 执行重试
local success = tryStackMagazineInternal(mag)
if success then
-- 成功时清除全部分代
retryQueue[magID] = nil
-- print("Success via generation:", genID)
goto continue
else
-- 更新重试状态
genRecord.attempts = genRecord.attempts + 1
genRecord.nextAttempt = currentTime + RETRY_CONFIG.INTERVAL
-- 超过最大尝试次数
if genRecord.attempts >= RETRY_CONFIG.MAX_ATTEMPTS then
entry.generations[genID] = nil
-- print("Max attempts for generation:", genID)
end
end
end
hasValidGenerations = true
::next_generation::
end
-- 清理空条目
if not hasValidGenerations then
retryQueue[magID] = nil
end
::continue::
end
end)
end
Hook.Patch("Barotrauma.Character", "ControlLocalPlayer", function(instance, ptable)
if retryQueue == nil then Hook.Remove("think", "magazineRetrySystem") end
if not PlayerInput.KeyHit(Keys.R) then return end
local Character = instance
if not Character then return end
local rightHand = Character.Inventory.GetItemInLimbSlot(InvSlotType.RightHand)
local leftHand = Character.Inventory.GetItemInLimbSlot(InvSlotType.LeftHand)
local rightHandIEnumerable = {InvSlotType.RightHand}
local leftHandIEnumerable = {InvSlotType.LeftHand}
if not rightHand and not leftHand then return end
if rightHand and rightHand.HasTag("weapon") then
tryPutItemsInInventory(Character, rightHand, leftHand, rightHand.OwnInventory, rightHandIEnumerable, leftHandIEnumerable)
end
if leftHand and not leftHand.Equals(rightHand) and leftHand.HasTag("weapon") then
tryPutItemsInInventory(Character, leftHand, rightHand, leftHand.OwnInventory, leftHandIEnumerable, rightHandIEnumerable)
end
end, Hook.HookMethodType.After)

BIN
Press R to Reload/PressRtoReload.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -0,0 +1,41 @@
{
"folders": [
{
"path": "."
},
],
"settings": {
"Lua.diagnostics.libraryFiles": "Enable",
"Lua.workspace.library": [
"D:/Projects/Barotrauma/types/client",
"D:/Projects/Barotrauma/types/shared",
],
"Lua.diagnostics.disable": [
"param-type-mismatch",
"return-type-mismatch",
"undefined-field",
"need-check-nil",
"assign-type-mismatch",
"redundant-return-value",
"missing-parameter",
"undefined-global",
"missing-return-value",
"undefined-doc-name",
"missing-return",
"cast-local-type",
"deprecated",
],
},
"launch": {
"version": "0.2.0",
"configurations": [
{
"name": "MoonSharp Attach",
"type": "moonsharp-debug",
"request": "attach",
"debugServer" : 41912
}
]
}
}

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<contentpackage name="Press R to Reload" modversion="1.0.18" corepackage="False" steamworkshopid="3413495302" gameversion="1.7.7.0" expectedhash="BB2C3780711FCEBBF4A73BF3AC961211" />