valminhq-ValminEye icon

ValminEye

Server-only plugin that writes joins, leaves, boss fights, first boss kills and raids to the server log as JSON lines for Valmin. Install it on the dedicated server; players need nothing.

Last updated 8 hours ago
Total downloads 7
Total rating 0 
Categories Server-side
Dependency string valminhq-ValminEye-0.2.0
Dependants 0 other packages depend on this package

This mod requires the following mods to function

denikson-BepInExPack_Valheim-5.4.2351 icon
denikson-BepInExPack_Valheim

BepInEx pack for Valheim. Preconfigured with the correct entry point for mods and preferred defaults for the community.

Preferred version: 5.4.2351

README

ValminEye

A server-only BepInEx plugin for Valheim dedicated servers that reports game events Valmin cannot see in the vanilla log: who joined and left, boss fights and who fought them, the first time each boss is defeated, raids, and when the world has started.

Status: in development, not released. The event format below is a draft and may change before the first release. No event has been checked on a live server yet.

How it works

ValminEye opens no port and accepts no input. It writes one line per event to the BepInEx log, which reaches the server's standard output. Valmin already follows that stream for readiness, saves and joins, so the plugin adds no new transport, credentials or firewall rules.

Players keep a vanilla client. Nothing needs to be installed on their machines.

Commands sent into the game, such as chat warnings, are not part of this plugin. Valmin sends those over RCON through the Tristan-ValheimRcon mod. Messages posted to Discord are written by Valmin from these events, and their wording is set in Valmin, not here.

Event format (draft, version 1)

Every event is one log line from the BepInEx source ValminEye, with one JSON object as the message:

[Info   : ValminEye] {"v":1,"event":"join","player":"Alex","id":"Steam_76561198000000000"}
Field Meaning
v Format version. A consumer ignores lines with a version it does not know.
event Event name, from the table below.
Event Fields When
join player, id A player's character entered the world.
leave player, id A player who had joined left: quit, lost connection, kick or ban.
boss_first_kill key, boss, day, duration, wipes, players A boss was defeated for the first time in this world.
boss_fight_start key, boss, first, day, players A boss noticed players and a fight started.
boss_fight_end key, boss, first, outcome, day, duration, players The fight ended.
raid_start raid, name, players A raid started, named by the game's event name, for example army_eikthyr.
raid_end raid, name, players The raid ended.
world_start world, day The world has loaded and the server is about to open to players.
  • player is the character name. id is the platform id in the Steam_<id> form Valmin's player lists use.
  • key is the world key the game sets, for example defeated_eikthyr. boss is the boss's English name from the game's own text, for example Eikthyr. It is left out when the game has no name for the key, for example for a key added through the config.
  • day is the in-game day, the number players see as "Day N".
  • players lists everyone in the fight: each player the boss recorded as hitting it, and each player who died near it. Every entry has player, id and deaths, the number of times that player died near this boss since it was last defeated. Players who never died show 0.
  • duration is how long the fight lasted, in whole seconds. On boss_first_kill it is the winning fight, and it is left out when no fight was seen.
  • wipes is how many fights against this boss ended without a kill since the server started.
  • first is true when the boss had never been defeated in this world when the fight started. By default only these fights are reported (FirstOnly).
  • outcome is kill, wipe (the boss lost track of the players, because everyone died or ran, or everyone left its area) or gone (the boss disappeared without dying, for example removed by a command). On boss_fight_end, players lists everyone who came within DeathRadius of the boss during the fight, with the deaths in that fight. On boss_fight_start the entries have no deaths.
  • name on a raid is the message the game shows players when it starts, in English, for example The ground is shaking. The game has no other name for a raid. It is left out when the game has no text for the raid.
  • players on a raid lists the players inside the raid area when the line is written, each with player and id. The area is the circle the game itself uses for the raid. A raid ended with the stopevent command can have no players.
  • world is the world's name, as in the server's -world option.
  • A raid already running when the server restarts is not reported again.

Every field except v, event, key, raid, outcome, player and world can be turned off in the config, and so can every event.

There is no event for the plugin loading. BepInEx itself logs [Info : BepInEx] Loading [ValminEye 0.2.0], and Valmin reads that line to tell that the plugin runs and which version.

A boss fight that ended in a wipe:

[Info   : ValminEye] {"v":1,"event":"boss_fight_end","key":"defeated_gdking","boss":"The Elder","first":true,"outcome":"wipe","day":31,"duration":184,"players":[{"player":"Alex","id":"Steam_76561198000000000","deaths":2}]}

A boss kill example:

[Info   : ValminEye] {"v":1,"event":"boss_first_kill","key":"defeated_eikthyr","boss":"Eikthyr","day":12,"players":[{"player":"Alex","id":"Steam_76561198000000000","deaths":2},{"player":"Bo","id":"Steam_76561198000000001","deaths":0}]}

The plugin also writes a few plain-text lines from the same source, such as the list of bosses it found at the first boss check. Event lines are the ones whose message starts with {.

Lines stay well under 16 KB, the length at which Docker splits a log line.

Planned

death, key_set, key_removed, day and a snapshot after the world loads.

How bosses are found

The plugin needs no list of bosses. When the server starts, it reads the bosses from the game's own data, with the world key each one sets when defeated and its name, so a boss added by a game update is picked up without a plugin release. A key also counts as a boss kill when it starts with defeated_, or when it is listed in BossKeys, for modded bosses that use a different key.

A kill is a first kill when the world did not have the key before. Removing the key with the removekey console command makes the next kill a first kill again.

A player's death counts for a boss when it happens within DeathRadius metres of that boss while the boss is alive. The count for a boss starts over each time that boss is defeated.

A fight starts when a boss turns alert, the state in which it hunts players, with a player within DeathRadius metres. It ends when the boss dies, or when it calms down again: a boss calms down 30 seconds after it last sensed a player, and when every player has left its area. So a wipe is reported about half a minute after the last player died or ran. A restart ends no fight; the game forgets a boss's alert state when the server stops.

Configuration

The config file is BepInEx/config/io.valmin.eye.cfg. It is created with the defaults the first time the server starts with the plugin. Restart the server after editing it.

Section Setting Default Meaning
Join Enabled true Write join lines.
IncludeId true Add id.
Leave Enabled true Write leave lines.
IncludeId true Add id.
BossFirstKill Enabled true Write boss_first_kill lines.
IncludeBossName true Add boss.
IncludeDay true Add day.
IncludePlayers true Add players.
IncludePlayerIds true Add id to each player.
IncludeDeaths true Add deaths to each player.
IncludeDuration true Add duration.
IncludeWipes true Add wipes.
AnyDefeatedKey true Count any key starting with defeated_ as a boss kill.
BossKeys empty More keys that count as a boss kill, separated by commas.
DeathRadius 100 Metres from a boss within which a death counts for it and a player counts as in its fight. The default is the distance at which the game shows a boss health bar.
BossFight StartEnabled true Write boss_fight_start lines.
EndEnabled true Write boss_fight_end lines.
FirstOnly true Report only fights against bosses not yet defeated in this world.
IncludeBossName true Add boss.
IncludeFirst true Add first.
IncludeDay true Add day.
IncludeDuration true Add duration to the end line.
IncludePlayers true Add players.
IncludePlayerIds true Add id to each player.
IncludeDeaths true Add deaths to each player on the end line.
Raid StartEnabled true Write raid_start lines.
EndEnabled true Write raid_end lines.
IncludeName true Add name.
IncludePlayers true Add players.
IncludePlayerIds true Add id to each player.
World StartEnabled true Write world_start lines.
IncludeDay true Add day.

Build

The plugin compiles against the game's own assemblies, not a stub package, so a build always matches a real server version. The game files are never committed. You need the .NET 8 SDK, make, and either SteamCMD or Docker (for the steamcmd/steamcmd image).

make server        # download the dedicated server (Steam app 896660) into .cache/valheim-server
make               # build src/bin/Release/netstandard2.1/ValminEye.dll
make package       # also write the Thunderstore zip to dist/
make check-update  # tell whether Steam ships a newer server build than the installed one
make install PLUGINS_DIR=/path/to/server/BepInEx/plugins

To build against a server you already have, pass its directory: make SERVER_DIR=/path/to/server. After a game update, run make server && make: a hook whose target the game renamed or removed fails the build.

GitHub builds the same way: the Build workflow downloads the current dedicated server, compiles against it when a push or pull request changes the code or build files, and once a week, and keeps the package as an artifact.

To release, set Version in src/Plugin.cs, then publish a release on GitHub with a new tag of that version and a leading v (v0.2.0). The Release workflow builds the package, attaches it to the release and publishes it to Thunderstore and Hexium. A pre-release gets the package but is not published to either. It needs two repository secrets: THUNDERSTORE_TOKEN, a service account token of the Thunderstore team named in thunderstore.toml, and HEXIUM_TOKEN, a Hexium API token of the team with the same name.

The plugin runs on a server with denikson-BepInExPack_Valheim. Copy ValminEye.dll to its BepInEx/plugins/ directory.

Credits

The choice of game hooks was informed by OdinEye by sparcopt and its maintained fork js-ferguson/odin-eye, both under the MIT License.

License

MIT