Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ jobs:
runs-on: ubuntu-latest

steps:
# checkout the repo so gh commands work
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set release as latest
run: gh release edit ${{ env.RELEASE_TAG }} --draft=false --latest
env:
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Fixed` for any bug fixes.
- `Security` in case of vulnerabilities.

## [0.5.0] - 2025-07-27

### Added

- Added a new Challenge system to provide a mechanism to add extra challenges for players. This system is still in early stages, so currently only offers kill challenges.
- Added support for disabling "+XX Experience" scrolling combat text messages (via in-game `.xpconf` command)
- Added support for displaying current player buffs provided by XPRising

### Fixed

- Improved the display of heat > 6 stars. It will now show the heat number above that stage, as there is no maximum. This allows users to see the value as it drops.

## [0.4.10] - 2025-06-04

### Added
Expand Down
2 changes: 1 addition & 1 deletion ClientUI/ClientUI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="VRising.Unhollowed.Client" Version="1.1.8.9179701" />
<PackageReference Include="VRising.Unhollowed.Client" Version="1.1.9.9219901" />
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.735" IncludeAssets="compile" />
<PackageReference Include="BepInEx.PluginInfoProps" Version="2.1.0" />
</ItemGroup>
Expand Down
18 changes: 12 additions & 6 deletions ClientUI/Plugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public class Plugin : BasePlugin
{
private static ManualLogSource _logger;
internal static Plugin Instance { get; private set; }


private const string ConnectionGroup = "InternalConnection";
private static FrameTimer _uiInitialisedTimer = new();
private static FrameTimer _connectUiTimer;
private static FrameTimer _connectionUpdateTimer;
Expand Down Expand Up @@ -73,7 +74,7 @@ public override void Load()
_connectionProgressValue = (_connectionProgressValue + increment) % 100.0f;
UIManager.ContentPanel.ChangeProgress(new ProgressSerialisedMessage()
{
Group = "Connection",
Group = ConnectionGroup,
Label = "Connecting",
Colour = TurboColourMap,
Active = ProgressSerialisedMessage.ActiveState.Active,
Expand All @@ -87,7 +88,7 @@ public override void Load()
{
UIManager.ContentPanel.ChangeProgress(new ProgressSerialisedMessage()
{
Group = "Connection",
Group = ConnectionGroup,
Label = "Connecting",
Colour = "red",
Active = ProgressSerialisedMessage.ActiveState.Active,
Expand All @@ -99,7 +100,7 @@ public override void Load()

UIManager.ContentPanel.SetButton(new ActionSerialisedMessage()
{
Group = "Connection",
Group = ConnectionGroup,
ID = "RetryConnection",
Label = "Retry Connection?",
Colour = "red",
Expand All @@ -113,7 +114,7 @@ public override void Load()

UIManager.ContentPanel.SetButton(new ActionSerialisedMessage()
{
Group = "Connection",
Group = ConnectionGroup,
ID = "HideUI",
Label = "Hide UI",
Colour = "red",
Expand All @@ -125,7 +126,7 @@ public override void Load()
UIManager.SetActive(false);
});

UIManager.ContentPanel.OpenActionPanel();
UIManager.ContentPanel.OpenActionPanel(ConnectionGroup);
}
},
TimeSpan.FromMilliseconds(50),
Expand Down Expand Up @@ -205,6 +206,11 @@ private static void RegisterMessages()
UIManager.Reset();
Log(LogLevel.Info, $"Client initialisation successful");
});
ChatService.RegisterType<DisplayTextMessage>(((message, steamId) =>
{
if (message.Reset) UIManager.TextPanel.SetText(message.Title, message.Text);
else UIManager.TextPanel.AddText(message.Text);
}));
}

public new static void Log(LogLevel level, string message)
Expand Down
134 changes: 118 additions & 16 deletions ClientUI/UI/Panel/ActionPanel.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using ClientUI.UI.Util;
using UnityEngine;
using UnityEngine.UI;
using XPShared.Transport.Messages;
Expand All @@ -8,15 +9,44 @@ namespace ClientUI.UI.Panel;

public class ActionPanel
{
private const string ExpandText = "<";
private const string ContractText = ">";
private static readonly ColorBlock ClosedButtonColour = UIFactory.CreateColourBlock(Colour.SliderFill);
private static readonly ColorBlock OpenButtonColour = UIFactory.CreateColourBlock(Colour.SliderHandle);

private readonly GameObject _contentRoot;
private readonly GameObject _actionsContent;
private readonly GameObject _buttonsContent;

private readonly Dictionary<string, (GameObject, ButtonRef)> _actionGroups = new();
private readonly Dictionary<string, ButtonRef> _actions = new();

private string _activeGroup = "";

public ActionPanel(GameObject root)
{
_contentRoot = root;

_buttonsContent = UIFactory.CreateUIObject("ButtonsContent", _contentRoot);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(_buttonsContent, false, false, true, true, 2, 0, 0, 0, 0, TextAnchor.UpperRight);
UIFactory.SetLayoutElement(_buttonsContent, ignoreLayout: true);

// Set anchor/pivot to top right so it attaches to the root from that side
var buttonGroupRect = _buttonsContent.GetComponent<RectTransform>();
buttonGroupRect.SetAnchors(RectExtensions.PivotPresets.TopRight);
buttonGroupRect.SetPivot(RectExtensions.PivotPresets.TopRight);

_actionsContent = UIFactory.CreateUIObject("ActionsContent", _contentRoot);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(_actionsContent, false, false, true, true, 2, 0, 0, 0, 0, TextAnchor.UpperRight);
UIFactory.SetLayoutElement(_actionsContent, ignoreLayout: true);
var actionRect = _actionsContent.GetComponent<RectTransform>();
actionRect.anchorMin = Vector2.up;
actionRect.anchorMax = Vector2.up;
actionRect.pivot = Vector2.one;
actionRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 200);
actionRect.Translate(Vector3.left * 50);
}

private readonly Dictionary<string, GameObject> _buttonGroups = new();
private readonly Dictionary<string, ButtonRef> _buttons = new();

public bool Active
{
get => _contentRoot.active;
Expand All @@ -25,10 +55,10 @@ public bool Active

public void SetButton(ActionSerialisedMessage data, Action onClick = null)
{
if (!_buttons.TryGetValue(data.ID, out var button))
if (!_actions.TryGetValue(data.ID, out var button))
{
button = AddButton(data.Group, data.ID, data.Label, data.Colour);
_buttons[data.ID] = button;
_actions[data.ID] = button;
if (onClick == null)
{
button.OnClick = () =>
Expand All @@ -49,26 +79,98 @@ public void SetButton(ActionSerialisedMessage data, Action onClick = null)

internal void Reset()
{
foreach (var (_, buttonGroup) in _buttonGroups)
foreach (var (_, group) in _actionGroups)
{
GameObject.Destroy(buttonGroup);
GameObject.Destroy(group.Item1);
GameObject.Destroy(group.Item2.GameObject);
}
_buttonGroups.Clear();
_buttons.Clear();
_actionGroups.Clear();
_actions.Clear();
}

private ButtonRef AddButton(string group, string id, string text, string colour)
{
if (!_buttonGroups.TryGetValue(group, out var buttonGroup))
if (!_actionGroups.TryGetValue(group, out var buttonGroup))
{
buttonGroup = UIFactory.CreateUIObject(group, _contentRoot);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(buttonGroup, false, false, true, true, 3);
_buttonGroups.Add(group, buttonGroup);
// Set up the button that will open this group
var groupButton = UIFactory.CreateButton(_buttonsContent, $"{group}-button", ContractText);
UIFactory.SetLayoutElement(groupButton.GameObject, minHeight: 25, minWidth: 25, flexibleWidth: 0, flexibleHeight: 0);
groupButton.OnClick = () => ToggleGroup(group);

// actionGroup parented to groupButton
var actionGroup = UIFactory.CreateUIObject(group, groupButton.GameObject);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(actionGroup, false, false, true, true, 3, 0, 0, 0, 0, TextAnchor.UpperRight);
UIFactory.SetLayoutElement(actionGroup, ignoreLayout: true);
var actionRect = actionGroup.GetComponent<RectTransform>();
actionRect.anchorMin = Vector2.up;
actionRect.anchorMax = Vector2.up;
actionRect.pivot = Vector2.one;
actionRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 200);
actionRect.Translate(Vector3.left * 8);
actionGroup.SetActive(false);

buttonGroup = (actionGroup, groupButton);
_actionGroups.Add(group, buttonGroup);

// Make sure that the settings is the first button
if (group == SettingsButtonBase.Group)
{
groupButton.Transform.SetAsFirstSibling();
}
}
Color? normalColour = ColorUtility.TryParseHtmlString(colour, out var onlyColour) ? onlyColour : null;
var button = UIFactory.CreateButton(buttonGroup, id, text, normalColour);
UIFactory.SetLayoutElement(button.Component.gameObject, minHeight: 25, minWidth: 200, flexibleWidth: 0, flexibleHeight: 0);
var actionButton = UIFactory.CreateButton(buttonGroup.Item1, id, text, normalColour);
UIFactory.SetLayoutElement(actionButton.Component.gameObject, minHeight: 25, minWidth: 200, flexibleWidth: 0, flexibleHeight: 0);

return actionButton;
}

public void ToggleGroup(string group)
{
// Deactivate any active group
if (_activeGroup != "" && _actionGroups.TryGetValue(_activeGroup, out var previousActiveGroup))
{
previousActiveGroup.Item1.SetActive(false);
previousActiveGroup.Item2.ButtonText.text = ContractText;
previousActiveGroup.Item2.Component.colors = ClosedButtonColour;
}

// Only set the active group if we have a record of it
_activeGroup = _activeGroup == group || !_actionGroups.ContainsKey(group) ? "" : group;

// activate the new group as required
if (_activeGroup != "" && _actionGroups.TryGetValue(_activeGroup, out var newActiveGroup))
{
newActiveGroup.Item1.SetActive(true);
newActiveGroup.Item2.ButtonText.text = ExpandText;
newActiveGroup.Item2.Component.colors = OpenButtonColour;
}
}

public void ShowGroup(string group)
{
// Just ignore this if the group is already active, or blank, or we have no record of it
if (_activeGroup == group || group == "" || !_actionGroups.TryGetValue(group, out var newActiveGroup)) return;

_activeGroup = group;

// activate the new group as required
newActiveGroup.Item1.SetActive(true);
newActiveGroup.Item2.ButtonText.text = ExpandText;
newActiveGroup.Item2.Component.colors = OpenButtonColour;
}

public void HideGroup()
{
if (_activeGroup == "") return;

if (_actionGroups.TryGetValue(_activeGroup, out var oldActiveGroup))
{
oldActiveGroup.Item1.SetActive(false);
oldActiveGroup.Item2.ButtonText.text = ContractText;
oldActiveGroup.Item2.Component.colors = ClosedButtonColour;
}

return button;
_activeGroup = "";
}
}
46 changes: 14 additions & 32 deletions ClientUI/UI/Panel/ContentPanel.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using BepInEx.Logging;
using ClientUI.UniverseLib.UI.Panels;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using XPShared.Transport.Messages;
Expand All @@ -23,10 +21,7 @@ public class ContentPanel : ResizeablePanelBase
public override PanelDragger.ResizeTypes CanResize =>
_canDragAndResize ? PanelDragger.ResizeTypes.Horizontal : PanelDragger.ResizeTypes.None;

private const string ExpandText = "+";
private const string ContractText = "\u2212"; // Using unicode instead of "-" as it centers better
private GameObject _uiAnchor;
private ClientUI.UniverseLib.UI.Models.ButtonRef _expandButton;
private ActionPanel _actionPanel;
private ProgressBarPanel _progressBarPanel;
private NotificationPanel _notificationsPanel;
Expand All @@ -52,28 +47,15 @@ protected override void ConstructPanelContent()
Dragger.DraggableArea = Rect;
Dragger.OnEndResize();

_expandButton = UIFactory.CreateButton(ContentRoot, "ExpandActionsButton", ExpandText);
UIFactory.SetLayoutElement(_expandButton.GameObject, ignoreLayout: true);
_expandButton.ButtonText.fontSize = 30;
_expandButton.OnClick = ToggleActionPanel;
_expandButton.Transform.anchorMin = Vector2.up;
_expandButton.Transform.anchorMax = Vector2.up;
_expandButton.Transform.pivot = Vector2.one;
_expandButton.Transform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 30);
_expandButton.Transform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 30);
_expandButton.ButtonText.overflowMode = TextOverflowModes.Overflow;
_expandButton.Transform.Translate(Vector3.left * 10);
_expandButton.GameObject.SetActive(false);

var actionContentHolder = UIFactory.CreateUIObject("ActionsContent", ContentRoot);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(actionContentHolder, false, false, true, true, 2, 2, 2, 2, 2, TextAnchor.UpperLeft);
var actionContentHolder = UIFactory.CreateUIObject("ActionGroupButtonContent", ContentRoot);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(actionContentHolder, false, false, true, true);
UIFactory.SetLayoutElement(actionContentHolder, ignoreLayout: true);
var actionRect = actionContentHolder.GetComponent<RectTransform>();
actionRect.anchorMin = Vector2.up;
actionRect.anchorMax = Vector2.up;
actionRect.pivot = Vector2.one;
actionRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 200);
actionRect.Translate(Vector3.left * 10 + Vector3.down * 45);

// Set anchor/pivot to top left so panel can expand out left
var actionsRect = actionContentHolder.GetComponent<RectTransform>();
actionsRect.SetAnchors(RectExtensions.PivotPresets.TopLeft);
actionsRect.SetPivot(RectExtensions.PivotPresets.TopLeft);
actionsRect.Translate(Vector3.left * 10);

_actionPanel = new ActionPanel(actionContentHolder);
_actionPanel.Active = false;
Expand Down Expand Up @@ -136,7 +118,7 @@ internal override void Reset()

internal void SetButton(ActionSerialisedMessage data, Action onClick = null)
{
_expandButton.GameObject.SetActive(true);
_actionPanel.Active = true;
_actionPanel.SetButton(data, onClick);
}

Expand All @@ -152,15 +134,15 @@ internal void AddMessage(NotificationMessage data)
_notificationsPanel.AddNotification(data);
}

internal void OpenActionPanel()
internal void OpenActionPanel(string group)
{
if (!_actionPanel.Active) ToggleActionPanel();
_actionPanel.Active = true;
_actionPanel.ShowGroup(group);
}

private void ToggleActionPanel()
internal void CloseActionPanel()
{
_actionPanel.Active = !_actionPanel.Active;
_expandButton.ButtonText.text = _actionPanel.Active ? ContractText : ExpandText;
_actionPanel.HideGroup();
}

private void ToggleDragging(bool active)
Expand Down
Loading