324 lines
10 KiB
C#
324 lines
10 KiB
C#
using Barotrauma.Items.Components;
|
|
using Barotrauma;
|
|
using HarmonyLib;
|
|
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
using System.Collections.Generic;
|
|
using System;
|
|
using System.ComponentModel;
|
|
using Barotrauma.Networking;
|
|
using System.Linq;
|
|
|
|
namespace BaroMod_sjx
|
|
{
|
|
partial class ItemBoxImpl
|
|
{
|
|
|
|
[HarmonyPatch(typeof(Inventory), nameof(Inventory.DrawSlot))]
|
|
class Patch_DrawSlot
|
|
{
|
|
public class context
|
|
{
|
|
public SpriteBatch spriteBatch;
|
|
|
|
public Inventory inventory;
|
|
public Sprite? indicatorSprite;
|
|
public Sprite? emptyIndicatorSprite;
|
|
public Sprite? itemSprite;
|
|
public Rectangle conditionIndicatorArea;
|
|
|
|
public int max_value;
|
|
public int cur_value;
|
|
public Vector2 sprite_pos;
|
|
public float sprite_scale;
|
|
public float rotation;
|
|
public Color spriteColor;
|
|
public context(SpriteBatch sb, Inventory inv, Sprite? full, Sprite? empty, Sprite? item, Rectangle area, int max, int cur, Vector2 sp, float ss, float rot, Color sc)
|
|
{
|
|
spriteBatch = sb;
|
|
inventory = inv;
|
|
indicatorSprite = full;
|
|
emptyIndicatorSprite = empty;
|
|
itemSprite = item;
|
|
conditionIndicatorArea = area;
|
|
max_value = max;
|
|
cur_value = cur;
|
|
sprite_pos = sp;
|
|
sprite_scale = ss;
|
|
rotation = rot;
|
|
spriteColor = sc;
|
|
}
|
|
}
|
|
|
|
static private void Invoke_DrawItemStateIndicator(
|
|
SpriteBatch spriteBatch, Inventory inventory,
|
|
Sprite indicatorSprite, Sprite emptyIndicatorSprite, Rectangle containedIndicatorArea, float containedState,
|
|
bool pulsate = false)
|
|
{
|
|
AccessTools.Method(typeof(Inventory), "DrawItemStateIndicator")!
|
|
.Invoke(null, new object[] { spriteBatch, inventory, indicatorSprite, emptyIndicatorSprite, containedIndicatorArea, containedState, pulsate });
|
|
}
|
|
|
|
private static Sprite? GetTargetSprite(ConditionStorage conditionStorage, Inventory iv)
|
|
{
|
|
Inventory.ItemSlot target_slot;
|
|
{
|
|
Inventory.ItemSlot[] slots = (AccessTools.Field(typeof(Inventory), "slots").GetValue(iv)! as Inventory.ItemSlot[])!;
|
|
if (conditionStorage.slotIndex >= slots.Length)
|
|
{
|
|
DebugConsole.LogError($"ConditionStorage of {(iv.Owner as Item)!.Prefab.Identifier} specified index {conditionStorage.slotIndex} out of {slots.Length}!");
|
|
return null;
|
|
}
|
|
target_slot = slots[conditionStorage.slotIndex];
|
|
}
|
|
if (target_slot.Any())
|
|
{
|
|
Item i = target_slot.First();
|
|
return i.Prefab.InventoryIcon ?? i.Sprite;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static bool Prefix(out context? __state,
|
|
SpriteBatch spriteBatch, Inventory inventory, VisualSlot slot, Item item, int slotIndex)
|
|
{
|
|
if (inventory != null && item != null && get_componentsByType(item).TryGetValue(typeof(ConditionStorage), out List<ItemComponent>? comps))
|
|
{
|
|
ConditionStorage conditionStorage = (comps.First() as ConditionStorage)!;
|
|
if (!conditionStorage.showIcon && !conditionStorage.showCount)
|
|
{
|
|
__state = null;
|
|
return true;
|
|
}
|
|
|
|
|
|
Rectangle rect = slot.Rect;
|
|
rect.Location += slot.DrawOffset.ToPoint();
|
|
|
|
if (slot.HighlightColor.A > 0)
|
|
{
|
|
float inflateAmount = (slot.HighlightColor.A / 255.0f) * slot.HighlightScaleUpAmount * 0.5f;
|
|
rect.Inflate(rect.Width * inflateAmount, rect.Height * inflateAmount);
|
|
}
|
|
|
|
var itemContainer = item.GetComponent<ItemContainer>();
|
|
|
|
Sprite? indicatorSprite;
|
|
Sprite? emptyIndicatorSprite;
|
|
Rectangle conditionIndicatorArea;
|
|
if (conditionStorage.showCount)
|
|
{
|
|
|
|
|
|
if (itemContainer is null)
|
|
{
|
|
DebugConsole.LogError($"Item {item.Prefab.Identifier} has ConditionStorage but no ItemContainer!");
|
|
__state = null;
|
|
return true;
|
|
}
|
|
if (itemContainer.InventoryTopSprite != null || itemContainer.InventoryBottomSprite != null)
|
|
{
|
|
__state = null;
|
|
return true;
|
|
}
|
|
int dir = slot.SubInventoryDir;
|
|
|
|
if (itemContainer.ShowContainedStateIndicator)
|
|
{
|
|
conditionIndicatorArea = new Rectangle(rect.X, rect.Bottom - (int)(10 * GUI.Scale), rect.Width, (int)(10 * GUI.Scale));
|
|
}
|
|
else
|
|
{
|
|
conditionIndicatorArea = new Rectangle(
|
|
rect.X, dir < 0 ? rect.Bottom + HUDLayoutSettings.Padding / 2 : rect.Y - HUDLayoutSettings.Padding / 2 - Inventory.ContainedIndicatorHeight,
|
|
rect.Width, Inventory.ContainedIndicatorHeight);
|
|
conditionIndicatorArea.Inflate(-4, 0);
|
|
}
|
|
|
|
|
|
GUIComponentStyle indicatorStyle = GUIStyle.GetComponentStyle("ContainedStateIndicator.Default")!;
|
|
indicatorSprite = indicatorStyle.GetDefaultSprite();
|
|
emptyIndicatorSprite = indicatorStyle.GetSprite(GUIComponent.ComponentState.Hover);
|
|
}
|
|
else
|
|
{
|
|
indicatorSprite = null;
|
|
emptyIndicatorSprite = null;
|
|
conditionIndicatorArea = new Rectangle();
|
|
}
|
|
|
|
Vector2 itemPos;
|
|
float scale;
|
|
float rotation;
|
|
Sprite? item_sprite;
|
|
Color spriteColor;
|
|
|
|
if (conditionStorage.showIcon)
|
|
{
|
|
item_sprite = GetTargetSprite(conditionStorage, itemContainer.Inventory!);
|
|
if (item_sprite != null)
|
|
{
|
|
scale = Math.Min(Math.Min((rect.Width - 10) / item_sprite.size.X, (rect.Height - 10) / item_sprite.size.Y), 2.0f);
|
|
itemPos = rect.Center.ToVector2();
|
|
if (itemPos.Y > GameMain.GraphicsHeight)
|
|
{
|
|
itemPos.Y -= Math.Min(
|
|
(itemPos.Y + item_sprite.size.Y / 2 * scale) - GameMain.GraphicsHeight,
|
|
(itemPos.Y - item_sprite.size.Y / 2 * scale) - rect.Y);
|
|
}
|
|
|
|
rotation = 0.0f;
|
|
if (slot.HighlightColor.A > 0)
|
|
{
|
|
rotation = (float)Math.Sin(slot.HighlightTimer * MathHelper.TwoPi) * slot.HighlightTimer * 0.3f;
|
|
}
|
|
|
|
spriteColor = item_sprite == item.Sprite ? item.GetSpriteColor() : item.GetInventoryIconColor();
|
|
}
|
|
else
|
|
{
|
|
scale = 1.0f;
|
|
rotation = 0.0f;
|
|
spriteColor = Color.White;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
item_sprite = null;
|
|
scale = 1.0f;
|
|
rotation = 0.0f;
|
|
spriteColor = Color.White;
|
|
}
|
|
Vector2 center = rect.Center.ToVector2() + (new Vector2(conditionStorage.iconShiftX, conditionStorage.iconShiftY)) * slot.Rect.Size.ToVector2() * 0.5f;
|
|
__state = new context(spriteBatch, inventory, indicatorSprite, emptyIndicatorSprite, item_sprite,
|
|
conditionIndicatorArea, conditionStorage.maxItemCount, conditionStorage.currentItemCount, center,
|
|
scale * conditionStorage.iconScale, rotation, spriteColor);
|
|
}
|
|
else
|
|
{
|
|
__state = null;
|
|
}
|
|
return true;
|
|
}
|
|
public static void Postfix(context? __state)
|
|
{
|
|
if (__state != null)
|
|
{
|
|
__state.itemSprite?.Draw(__state.spriteBatch, __state.sprite_pos, __state.spriteColor, __state.rotation, __state.sprite_scale);
|
|
if (__state.indicatorSprite != null && __state.emptyIndicatorSprite != null)
|
|
{
|
|
Invoke_DrawItemStateIndicator(__state.spriteBatch, __state.inventory, __state.indicatorSprite, __state.emptyIndicatorSprite, __state.conditionIndicatorArea,
|
|
__state.cur_value / (float)__state.max_value);
|
|
string info_text = $"{__state.cur_value}/{__state.max_value}";
|
|
float text_scale = 0.75f;
|
|
Vector2 info_size = GUIStyle.SmallFont.MeasureString(info_text) * text_scale;
|
|
GUIStyle.SmallFont.DrawString(__state.spriteBatch, info_text, __state.conditionIndicatorArea.Center.ToVector2() - (info_size * 0.5f), Color.White, 0.0f, Vector2.Zero, text_scale, SpriteEffects.None, 0.0f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
partial class ConditionStorage : ItemComponent, IServerSerializable
|
|
{
|
|
private CoroutineHandle? resetPredictionCoroutine = null;
|
|
private int? last_server_update_count = null;
|
|
private float resetPredictionTimer = 1.0f;
|
|
|
|
float last_update_time = 0;
|
|
|
|
const double remove_time = 1.0;
|
|
|
|
class ItemStackedInfo
|
|
{
|
|
public Item target;
|
|
public Inventory removed_from;
|
|
public Character user;
|
|
public double timestamp;
|
|
public int slot;
|
|
public ItemStackedInfo(Item item, Character character, Inventory removedFrom, int from_slot)
|
|
{
|
|
target = item;
|
|
removed_from = removedFrom;
|
|
user = character;
|
|
slot = from_slot;
|
|
timestamp = Timing.TotalTime;
|
|
}
|
|
}
|
|
|
|
// keep a list of items removed on client side so that they can be put back into container after timeour
|
|
private List<ItemStackedInfo> removed = new List<ItemStackedInfo>();
|
|
|
|
|
|
void RemoveItem_track(Item item, Character user, Inventory removedFrom, int slot)
|
|
{
|
|
removed.Add(new ItemStackedInfo(item, user, removedFrom, slot));
|
|
}
|
|
|
|
void UpdateRemovedItems()
|
|
{
|
|
var copy = removed.CreateCopy();
|
|
foreach (var item in copy)
|
|
{
|
|
// updated from server to be removed
|
|
if (item.target.Removed)
|
|
{
|
|
removed.Remove(item);
|
|
}
|
|
// timeout for removed item. put it back.
|
|
else if (Timing.TotalTime - item.timestamp > remove_time)
|
|
{
|
|
if (!item.removed_from.TryPutItem(item.target, item.slot, false, false, item.user, false, false))
|
|
{
|
|
item.target.Drop(item.user, true, true);
|
|
}
|
|
removed.Remove(item);
|
|
}
|
|
}
|
|
if (removed.Any())
|
|
{
|
|
IsActive = true;
|
|
}
|
|
}
|
|
|
|
partial void OnCountPredictionChanged()
|
|
{
|
|
if (GameMain.Client == null || !last_server_update_count.HasValue) { return; }
|
|
if (resetPredictionCoroutine == null || !CoroutineManager.IsCoroutineRunning(resetPredictionCoroutine))
|
|
{
|
|
resetPredictionCoroutine = CoroutineManager.StartCoroutine(ResetPredictionAfterDelay());
|
|
}
|
|
}
|
|
|
|
private IEnumerable<CoroutineStatus> ResetPredictionAfterDelay()
|
|
{
|
|
while (resetPredictionTimer > 0.0f)
|
|
{
|
|
resetPredictionTimer -= CoroutineManager.DeltaTime;
|
|
yield return CoroutineStatus.Running;
|
|
}
|
|
if (last_server_update_count.HasValue) { SetItemCount(last_server_update_count.Value, false); }
|
|
resetPredictionCoroutine = null;
|
|
yield return CoroutineStatus.Success;
|
|
}
|
|
|
|
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
|
{
|
|
if (last_update_time <= sendingTime)
|
|
{
|
|
last_update_time = sendingTime;
|
|
last_server_update_count = msg.ReadRangedInteger(0, maxItemCount);
|
|
SetItemCount(last_server_update_count.Value, true);
|
|
}
|
|
else
|
|
{
|
|
// discard the number, but still extract it from stream.
|
|
msg.ReadRangedInteger(0, maxItemCount);
|
|
}
|
|
}
|
|
}
|
|
}
|