You are viewing a potentially older version of this package. View all versions.
Ryanlt-RyLib-1.1.0 icon

RyLib

Shared UI library for Holdfast client mods. Lets several mods add P menu tabs, a shared Mods tab, a battle map, player and kill log actions, key hints and world or minimap markers without colliding.

Date uploaded a week ago
Version 1.1.0
Download link Ryanlt-RyLib-1.1.0.zip
Downloads 7
Dependency string Ryanlt-RyLib-1.1.0

This mod requires the following mods to function

HoldfastModding-BepInExPack_Holdfast-5.4.2305 icon
HoldfastModding-BepInExPack_Holdfast

BepInEx pack for Holdfast: Nations At War. Preconfigured and ready to use.

Preferred version: 5.4.2305

README

RyLib

Latest release License

RyLib is a UI library for BepInEx client mods for Holdfast: Nations At War. Mods register P menu tabs, buttons, key hints and markers with RyLib by name, and RyLib places them in the game's UI. Several mods can add to the same menus and bars at once without overlapping.

RyLib adds nothing to the game by itself. Mods that use it list it as a dependency, and mod managers install it with them.

Features

Feature Description
P menu tabs Header tabs with pages of controls, a shared Mods tab with one sub tab per mod, and map pages.
Players tab bar Controls in the bar under the players list, next to Admin Raygun.
Player row actions Buttons on an expanded player row, in the same grid as the game's buttons.
Kill log actions Actions for the killer and the victim when a kill log row is clicked.
Key hints Entries in the free roam and spectating key bars.
World marks Rings, beams, labels and a glow visible through walls, on players or positions.
Minimap marks Markers on the game's minimap.
Map pages An overhead image of the battle with every player drawn by class and faction.
Helpers Faction colors, game icons in TextMeshPro text, and stock icon names.

Using RyLib in a mod

Reference RyLib.dll, add the dependency, and register in your plugin's Awake:

[BepInPlugin(Guid, "MyMod", "1.0.0")]
[BepInDependency(RyLib.RyLibPlugin.Guid, "1.1.0")]
public class MyMod : BaseUnityPlugin
{
    public const string Guid = "com.example.mymod";

    private void Awake()
    {
        RyLib.PlayerRow.AddAction(Guid, "Wave", RyLib.StockRowButton.Heal, playerId => Wave(playerId));
    }
}

Add "Ryanlt-RyLib-1.1.0" to the dependencies in manifest.json.

Conventions

  • Every call takes your plugin GUID as owner. RyLib uses it in log messages and to keep each mod's registrations separate.
  • Register everything in Awake. RyLib creates the UI when the game builds its own, and again after a map change.
  • Each callback runs inside its own try/catch. An exception is logged with your GUID and does not affect other mods.
  • Registering the same label twice from one owner logs a warning and returns false.
  • Tabs, pages and mod tabs are looked up by name. Asking for an existing name returns the existing one, and content from every mod is added to it. Asking for an existing name as a different kind of page is refused and logged with both owners.
  • A Func<bool> visible parameter is called several times a second, and the UI shows or hides to match. null means always visible.

P menu tabs

ModTab    PMenu.ModTab(string owner, string name, string icon = null, bool adminOnly = true, Func<bool> visible = null)
HeaderTab PMenu.Header(string owner, string name, string icon = null)
Page      HeaderTab.Page(string owner, string name, string icon = null, bool adminOnly = true, Func<bool> visible = null)
Page      HeaderTab.MapPage(string owner, string name, string icon = null, Func<bool> visible = null)
bool      ModTab.Section(string owner, string title, Action<Section> build)
bool      Page.Section(string owner, string title, Action<Section> build)
bool      PMenu.StockTabVisible(string owner, StockTab tab, Func<bool> visible)

PMenu.ModTab adds a sub tab to the shared Mods header tab. The Mods tab lists one button per mod on the left and shows the selected mod's sections on the right.

RyLib.ModTab tab = RyLib.PMenu.ModTab(Guid, "MyMod", RyLib.StockIcon.Shield);
tab.Section(Guid, "Display", section =>
{
    section.Toggle("Show rings", Settings.ShowRings, RyLib.StockIcon.Players);
    section.Stepper("Ring size", Settings.RingSize, 0.5f, "0.0");
});

PMenu.Header adds a header tab of your own after the game's tabs. The game's tab names are reserved: Players, Rules, Admin, Maps, Kill Log and Artillery. RyLib sizes every header label and icon to fit the bar, including the game's.

A header tab holds pages. Page holds sections and MapPage holds a map. A header with more than one page shows a row of icon buttons for switching between them. A header with no visible pages is hidden.

Pages and mod tabs with adminOnly set show only after the server accepts an rc login.

Section pages and the Mods tab use the header of the game's Admin tab, with the title in capitals between two gold bars. Each section starts with its title in capitals and a divider line across the rest of the row.

PMenu.StockTabVisible hides one of the game's own tabs while visible returns false. StockTab.Rules is the only one supported. While it is hidden, the other headers share its space and tab cycling skips it.

Section controls

A section is a titled group of controls in the game's style. Each control is bound to a BepInEx ConfigEntry. The control saves the value when it changes and updates when the config file changes.

Buttons:

void Toggle(string label, ConfigEntry<bool> entry, string icon = null)
void Cycle<T>(string label, ConfigEntry<T> entry, Func<T, string> text = null, Func<T, bool> isOn = null, string icon = null)
void Stepper(string label, ConfigEntry<float> entry, float step, string format = "0.00", string icon = null)
void Button(string label, Action onClick, string icon = null)
void ConfirmButton(string label, Action onClick, string icon = null)

Rows, each a label with a control drawn in the style of the game's Admin tab:

void Slider(string label, ConfigEntry<float> entry, float min, float max, float step, string format = "0.0")
void Slider(string label, ConfigEntry<int> entry, int min, int max)
void Dropdown<T>(string label, ConfigEntry<T> entry, Func<T, string> text = null)
void TextField(string label, ConfigEntry<string> entry)
Control Behavior
Toggle Switches the entry on and off. Lit while on.
Cycle Moves to the next enum value on each click. text gives the label for each value and isOn sets which values light the button.
Stepper Shows the value. Left click adds step, right click subtracts it, within the entry's acceptable range.
Button Calls onClick.
ConfirmButton The first click changes the label to "Confirm" followed by the label. A second click within four seconds calls onClick.
Slider A slider with a value box. Values snap to step and stay between min and max. Typing in the box also sets the value.
Dropdown A list of the enum's values. text gives the label for each value.
TextField A text box. The entry is saved when editing ends.

Sliders and text boxes sit in a dark box with the Admin tab's border, and dropdowns use the Admin tab's own dropdown.

Buttons sit in a grid at the top of the section and rows are listed below it. If the Admin tab templates cannot be found, a Slider becomes a Stepper, a Dropdown becomes a Cycle and a TextField is left out.

icon is the name of any sprite the game has loaded. StockIcon has some of them.

Players tab bar

bool PlayersBar.Toggle(string owner, string label, ConfigEntry<bool> entry, string icon = null)
bool PlayersBar.Cycle<T>(string owner, string label, ConfigEntry<T> entry, Func<T, string> text = null,
                         Func<T, bool> isOn = null, string icon = null)

Adds a Toggle or Cycle control to the bar under the players list, next to the game's Admin Raygun button.

Player row actions

bool PlayerRow.AddAction(string owner, string label, StockRowButton icon, Action<int> onClick,
                         Func<bool> visible = null)

Adds a button to the Actions block of an expanded player row in the P menu players tab. The block is only available to a logged in admin. Buttons from every mod go in a grid below the game's six buttons, at the same size and spacing, and the row grows to fit them.

  • icon uses the symbol of one of the game's row buttons: Slay, Revive, Slap, Heal, Bring, GoTo, Message, Kick or Ban.
  • onClick receives the network player ID of the row's player.
  • When visible returns false the button is removed and the grid closes the gap.

These buttons also appear in the kill log actions and on the map's selected player panel.

Kill log actions

bool KillLog.EnableActions(string owner, Func<bool> visible = null)

Turns on kill log actions. Clicking a row in the admin kill log opens a panel under it with actions for the killer and the victim: Spectate, Go To, Bring, Slay, Revive, Heal, Slap, Kick and every mod's player row actions. Slay, Slap and Kick need a second click within four seconds.

The actions are off until a mod calls EnableActions, and stay on while any mod that called it returns true from visible.

Key hints

bool KeyHints.Add(string owner, string label, Func<KeyCode[]> keys, Func<bool> visible = null)

Adds an entry to the key bar in free roam, after Admin Raygun, and to the key bar while spectating, after Report Player. The entry is a copy of one of the game's entries. Several keys share one entry and are shown as [ / ]. For a key the game has no icon for, RyLib draws one from the game's own key icons. Hints are shown only to a logged in admin.

RyLib.KeyHints.Add(Guid, "Spectate Rambos", () => new[] { KeyCode.LeftBracket, KeyCode.RightBracket });

World marks

bool World.Layer(string owner, Action<List<WorldMark>> fill, float hz = 10f)

fill is called hz times a second with an empty list. Add a WorldMark for each thing to draw. Marks are moved every frame, and a mark missing from the list is removed.

WorldMark field Description
Key ID for the mark, unique within your layer.
Follow Transform to follow. When null, Position is used.
Glow Object to glow in Color, visible through walls. Uses the game's highlighter. If two layers glow the same object, the first layer's glow is used.
Color Color for the ring, beam, label and glow.
Label, LabelHeight Text above the mark, drawn under the P menu. null for no label.
RingRadius, BeamHeight Ground ring and vertical beam in meters. 0 for none.

Minimap marks

bool Minimap.Layer(string owner, Action<List<MinimapMark>> fill, float hz = 4f)

Adds markers to the game's minimap. They use the game's minimap pointer class, which handles scaling, rotation and clamping to the edge.

MinimapMark field Description
Key ID for the mark.
Follow Transform to track. Required.
Shape Ring, Dot or Flag.
Faction Faction the mark belongs to. Marks for your faction are blue and marks for the enemy are red. Enemy marks only show when the game shows that faction, while it is revealed, in last stand or during an all charge. With None the mark always shows in Color.
AlwaysShow Shows the mark regardless of the reveal rules. Use this only for admin features.
Size Size in pixels. 0 for the default.

Map pages

bool Page.MapLayer(string owner, Action<List<MapMark>> fill)

A map page renders an overhead image of the current map once per round, at 8192 pixels on the long side, and draws each player as their class icon in their faction's color. The icons come from the game: the spawn menu class icons, the minimap officer marker and ability icons.

The image covers the terrains that hold the spawns and capture points, so decoration terrains around a map are left out. Players, horses and ships are hidden while the image is taken. Battle Area frames the live spawn positions and capture points, read each time it is pressed. Whole Map shows the full image.

The side panel has:

  • A legend of the classes each faction can spawn, with live counts.
  • A search box that filters the icons.
  • Zoom and pan.
  • For the selected player, Spectate, Go To, Bring, Teleport To, Slay and every mod's player row actions.

Teleport To and Teleport Me wait for a click on the map before teleporting.

A map layer adds marks to the map:

MapMark field Description
Key ID for the mark.
OnPlayer, PlayerId Attaches the mark to a player's icon. Otherwise the mark is placed at Position.
Shape Ring, Dot, Flag, or Outline. Outline draws a colored outline around the player's class icon.
Color Mark color.
Size Size in pixels. 0 for the default.
Tooltip Text shown on hover.

A map page only updates while it is open.

Helpers

Helper Description
Factions.Color(FactionCountry) Color for a faction.
Factions.Name(FactionCountry) Display name for a faction.
Factions.ClassName(PlayerClass) Display name for a class.
TextIcons.Tag(string gameSprite) A <sprite> tag that draws the named game sprite in TextMeshPro text, tinted by the text color. Returns null while the sprite is not loaded. A failed sprite is retried after ten seconds.
TextIcons.Swatch() A <sprite> tag for a rounded square, for color keys. Wrap it in a <color> tag to set its color.
StockIcon Names of P menu sprites for the icon parameters.
MapStyle.IconSize, GlowStyle RyLib's own config entries, for mods that show them on a page.

Settings

The config file is BepInEx\config\com.ryannlt.rylib.cfg. Changes to the file apply without a restart.

Setting Default Effect
[Glow] ScaleWithDistance true Scales glow outlines down with distance.
[Glow] OutlineWidth 0.6 Outline thickness. 0 turns the outline off.
[Glow] Brightness 0.7 Color multiplier. Values above 1 make the game's bloom brighten the outline.
[Glow] Glow 2 Halo strength. 0 turns the halo off.
[Glow] GlowWidth 0.04 Halo width.
[Map] IconSize 22 Size of the class icons on map pages, in pixels.

Building

Requirements:

  • The game.
  • BepInEx installed in an r2modman profile.
  • A .NET SDK, for the compiler.
powershell -ExecutionPolicy Bypass -File .\build.ps1

build.ps1 compiles against the game's assemblies and the BepInEx in the profile, then copies RyLib.dll to BepInEx\plugins\RyLib\ in that profile. Restart the game to load it.

Option Effect
-NoDeploy Build without copying. Use this while the game is running.
-ProfileName <name> Use a profile other than Dev.

package.ps1 builds RyLib and writes a Thunderstore zip to Package\. Both scripts expect the game in C:\Program Files (x86)\Steam\steamapps\common\Holdfast Nations At War. For another location, change $GameDir at the top of the script.

The PNG files in Icons\ are embedded in the DLL. They are class icons that the game does not include, as white shapes on a transparent background. RyLib turns them into outlined icons that can be tinted. All other icons are read from the game while it runs, and no game art is included in RyLib.

License

MIT.

CHANGELOG

Changelog

1.1.1

  • World rings follow the ground instead of floating at the height of the mark. On a bridge or floor above the terrain, the ring lies flat on it.

1.1.0

  • Settings rows. Sections can hold sliders, dropdowns and text fields in the style of the game's Admin tab: Section.Slider, Section.Dropdown and Section.TextField.
  • Section.ConfirmButton needs a second click within four seconds.
  • Admin tab look. Section pages and the Mods tab use the Admin tab's header with its gold bars, section titles have a divider line, and sliders and text boxes sit in the Admin tab's boxes.
  • Hide the Rules tab. PMenu.StockTabVisible(owner, StockTab.Rules, visible) hides the game's Rules tab, and the other headers share its space.
  • Map pages.
    • The image is 8192 pixels on the long side.
    • It covers only the terrains that hold the spawns and capture points.
    • Players, horses and ships are hidden while it is taken.
    • Battle Area frames the live spawn positions, so moved spawns are framed correctly.
  • TextIcons.Swatch() gives a rounded square for color keys.
  • American spelling. WorldMark.Colour, MinimapMark.Colour, MapMark.Colour and Factions.Colour() are now Color. Mods built against 1.0.0 that use them need a rebuild.

1.0.0

First release. Everything a mod can ask for is listed in the README; in short:

  • P menu tabs. Header tabs with pages of switches, cycles, steppers and buttons bound to config entries, and a shared Mods tab where each mod gets its own sub tab. Every header, the game's own included, is fitted to the space.
  • Players tab. Switches in the bar beside Admin Raygun, and buttons on an expanded player's admin row, laid out in the same grid as the stock ones.
  • Kill log actions. A click on a kill log row opens actions for both the killer and the victim.
  • Key hints. Entries in the free roam and spectating key bars, with key caps drawn to match the game's.
  • World and minimap marks. Rings, beams, labels and a through-walls glow in the world, and markers on the minimap that follow the game's own reveal rules.
  • Map pages. An overhead picture of the battle with every player drawn by class and faction, a class legend with live counts, search, zoom, and admin actions that never fire from a stray click.