
SessionPulse
Paper/Folia/Spigot: gentle playtime reminders, milestone alerts, AFK-aware tracking, and optional session limits.
Оцените первым
30
1
Список изменений
Added
- Initial project scaffolding and repository setup.
- Gradle build: Java 21 toolchain, Shadow,
spigot-apicompile target, JUnit 6, andprocessResourcestemplating ofplugin.yml. plugin.ymldeclaring/spulse(alias/sessionpulse) and thesessionpulse.use,sessionpulse.adminandsessionpulse.exemptpermission nodes, withsessionpulse.admindeclaringsessionpulse.useas a child.scripts/dev-server.sh, which boots a local Paper server with the freshly built plugin installed.- Continuous Integration workflow running Gradle wrapper validation, tests and the
full build on every push to
mainand every pull request, uploading the shaded jar as a build artifact. platform/Scheduler, the plugin's only scheduling seam, withFoliaLibScheduleras its sole implementation and the only class permitted to name FoliaLib. The blanket cancel is deliberately absent from the interface, so a reload cannot reach it and silently stop the counting.- FoliaLib and Adventure shaded into the plugin jar, relocated under
com.ninja6.sessionpulse.lib, withfolia-supported: trueinplugin.yml. Every message goes throughNotifier, which renders throughBukkitAudienceson all platforms, because the relocated Adventure is not the copy Paper ships. - Boot legs in CI that enable and disable the plugin on Paper and Folia, and a build step that opens the packaged jar and proves the relocation applied.
config/PluginConfig, an immutable configuration snapshot, with theMilestone,OvertimePolicy,EnforcementPolicyandAfkModetypes it reads. A reload builds a new snapshot and swaps the plugin's reference to it, so a long-lived service holds() -> configrather than the object.- The default
config.yml, shipped and commented: every value carries its clamp range and the reason it exists. A scalar outside its range is clamped and the correction is logged with both numbers; a milestone entry that cannot be used is skipped and named by its position in the file, never replaced with a default. Nothing in configuration loading throws, so a malformed file produces warnings and a working plugin. - Boot-test assertions that the shipped
config.ymlreaches the data folder and loads on a real server without producing a single validation warning. session/SessionTrackerand the counted window: active seconds that accumulate across sessions and reset only after a player has been offline longer thantracking.window-reset-hours. Milestones and enforcement read the window rather than the current session, so quitting and rejoining cannot buy a fresh allowance. Lifetime playtime is a separate total that never resets. In-session time is measured monotonically and everything persisted is wall clock, becauseSystem.nanoTime()has no meaning across a restart.storage/YamlDataStorage: counted windows, lifetime playtime, last-seen and cooldowns persist todata.ymland survive a restart. Writes are in memory; the file is written by a periodic async flush, by a one-shot flush after every quit and cooldown, and synchronously at disable, and every write is forced to disk before it replaces the file. Adata.ymlthat cannot be parsed, is blank, or has the wrong shape is copied aside todata.yml.unreadablerather than overwritten; a misshapen one is then repaired so the next start finds a clean file. A file stamped with a newerschema-versionis never written over.notify/Notifier, the one door to a player's screen: chat, action bar, title and sound rendered from MiniMessage, with the configured prefix on chat only, and a legacy section-sign render for the String-only kick and pre-login screens that keeps hex colours.notify/Placeholdersfills<player>,<hours>,<minutes>and<cooldown>as text, never as markup, so a player name cannot inject a click event. Sounds are played with no emitter, at the listener's own position: the platform facets that carry one are skipped on Minecraft 26.x, where an emitter-taking call is dropped.- Configured milestones fire once per counted window, through
Notifieron the player's own region. A reload, a rejoin or a restart in the same window does not fire them again, and a player holdingsessionpulse.exemptnever receives one. - The overtime reminder, when enabled, repeats at
after-minutesand everyevery-minutesafter that, read against the counted window, so time spent AFK brings none forward and a rejoin, restart or reload never replays one. On a shared minute the milestone is sent first, on all of its channels including title and action bar, and the overtime chat line after it. Enabling or lowering overtime mid-session starts at the next point on the series. - AFK awareness: the counted window pauses for a player who has stopped playing, as
tracking.afk.modesays. AUTO uses EssentialsX where it is installed withauto-afkabove 0, and the built-in idle timer otherwise; players withoutessentials.afk.autoare also paused by the idle timer, and a manual/afkcounts wherever EssentialsX is bound. The idle timer counts moving into another block, steering a vehicle, clicks, inventory clicks, chat and commands, but not head rotation, pressure plates or riding a minecart. Water streams, bubble columns and knockback arrive as ordinary movement and still count, so a player parked in a current is not detected by the idle timer. EssentialsX is reached by reflection on the player's own region, so a server without it, or one that removes it mid-run, falls back rather than failing. The detector in force is named in the log at startup and on reload.plugin.ymlsoft-depends onEssentials. - Optional enforcement, off by default: with
enforcement.enabled, a player whose counted window reachesat-minutesis disconnected withkick-messageand refused at pre-login untilcooldown-minuteshave passed, with the time remaining on the refusal screen. The cooldown is written and flushed before the disconnect, so a restart or a crash does not clear it. The enforced break ends the counted window, so a player readmitted after the cooldown starts a fresh allowance instead of being disconnected again at once; lifetime playtime is kept. A reload that turns enforcement on applies on the next tick, and with enforcement off the login gate admits everyone. Players holdingsessionpulse.exemptare never disconnected. /spulse time,top,resetandreload, with tab completion.timeshows a player their own counted window and lifetime playtime; withsessionpulse.adminit takes any name, online or offline, and an offline window that the next join would reset is shown as zero.topranks the ten longest lifetimes, online players included.resetclears a player's counted window and any cooldown, online or offline, keeping lifetime and last-seen, so milestones fire again as the new window crosses them.reloadre-readsconfig.yml, reschedules the flush and the session tick by their own handles, and leaves the configuration in force if the file does not parse. Completion offers only what the sender may run, and only the online names they can see.- A gameplay smoke test on the Paper and Folia 1.21.11 legs: a protocol bot joins a
real server and the leg asserts, from what the bot received, that a milestone
delivers its chat line, action bar, title, subtitle and sound, that enforcement kicks
at the threshold, that the cooldown refuses the next login, and that the login is let
through once the cooldown lapses.
boot-test.shis renamedsmoke-test.sh, and the 1.20.4 legs remain boot-only. SessionPulseProbeBot, the MCProtocolLib client behind it, built by thebotClientJartask intobuild/test-fixturesand never wired intobuild.- A platform argument for
scripts/dev-server.sh([paper|folia|spigot] [mc-version], with the old version-only form still booting Paper), andscripts/dev-server.ps1, the same dev server for Windows PowerShell 5.1. Both forcedifficulty=peacefulintorun/server.propertieson every boot, so a test session is not interrupted by hostile mobs. A world that already exists keeps its difficulty inlevel.datand ignores the property, so--fresh/-Freshdeletesrun/world*and--op/-Opwritesrun/ops.jsonwith the player's offline UUID, which makes/difficulty peacefulavailable in game. - A Spigot 1.21.11 leg in the smoke matrix, playing the same scripted session as the Paper and Folia legs. The server jar is built with BuildTools on the runner and kept in the repository's Actions cache, keyed by the BuildTools build, so a warm run does not rebuild it. The smoke kick message is now coloured, and the bot legs assert that no MiniMessage tag or section sign reaches the bot unparsed, except on Spigot's login refusal, which CraftBukkit sends as literal text that the client still colours.
spigotas a platform forscripts/dev-server.shandscripts/dev-server.ps1, which build the server jar with BuildTools on the first run and reuse it after that.- A Latest Server Compatibility workflow, run on Mondays and on demand, that boots the plugin on the newest Paper and Folia versions fill lists. The pick is the newest version that is not a release candidate or pre-release, on whatever build channel that version has, so it can be an ALPHA or BETA build; the version, Java and channel are written to the job summary. The Java is the minimum fill publishes for that version. It never runs on pull requests, is not a required check, and does not cover Spigot.
- A release workflow, run when a
vX.Y.Z,-alpha.N,-beta.Nor-rc.Ntag is pushed. It refuses a malformed tag or one not onmain, runs the tests, buildsSessionPulse-<version>.jarwith a.sha256beside it, and publishes a GitHub release whose notes come from this file and say the plugin requires Java 21. A stable tag needs its own section here; a pre-release falls back to[Unreleased]. Modrinth (alpha, beta and release) and Hangar (beta and release) are published when their tokens are configured, and skipped otherwise. - The Hangar publishing plugin, driven entirely by properties and
HANGAR_API_TOKEN, so nothing about it runs during a build. META-INF/LICENSEandMETA-INF/THIRD_PARTY_NOTICES.mdin the plugin jar, carrying the MIT notices of the shaded Adventure, adventure-platform, examination, option and FoliaLib components. The build fails unless each is present exactly once.scripts/store-description.py, which generatesdocs/store-description.md, the Modrinth and Hangar project description, fromREADME.md. It strips the GitHub-only chrome and the download row, absolutises relative links and reference-style link definitions, folds typographic characters to ASCII, and fails on a line-initial#inside a code fence, a malformed heading, a relative markdown, reference or HTML target, non-ASCII outside a fence, an HTML block not closed before the next heading or block, or an output with no sections. A CI job runs it with--check.- README sections for requirements, commands, permissions and support.
RELEASE_PROCESS.mdsteps for pasting the store description and resubmitting a rejected Modrinth project, and the pre-1.0 policy: every release before1.0.0is a pre-release, and no unsuffixed tag is pushed belowv1.0.0. Also which changelog section a pre-release takes its notes from.
Changed
- README status and install instructions describe the pre-release channels and downloads instead of a build from source only.
- The
RELEASE_PROCESS.mdtag example is nowv0.1.0-alpha.1. - The brand assets notes moved from the README to
docs/assets/README.md.
Файлы
SessionPulse-0.1.0-alpha.1.jar(1.31 MiB)
ОсновнойМетаданные
Канал релиза
Alpha
Номер версии
0.1.0-alpha.1
Загрузчики
FoliaPaperPurpurSpigot
Версии игры
1.20.4–1.21.11
Загрузок
3
Дата публикации
16.09.2026
