467 lines
19 KiB
Lua
467 lines
19 KiB
Lua
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)
|