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
62 changes: 62 additions & 0 deletions .github/workflows/dev_build_release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Dev Build Release

on:
push:
branches:
- main
- dev
workflow_dispatch:

permissions:
contents: write

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout OumProfile
uses: actions/checkout@v4

- name: Checkout OumLib
uses: actions/checkout@v4
with:
repository: sun-mc-dev/oumlib
path: oumlib
ref: dev
token: ${{ secrets.GH_PAT || github.token }}

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: 'maven'

- name: Build and Install OumLib
run: mvn clean install -DskipTests -f oumlib/pom.xml

- name: Build OumProfile
run: |
mvn clean package -DskipTests
cp target/oumprofile-*.jar target/OumProfile-dev.jar

- name: Delete Old Release
run: |
if gh release view dev-build >/dev/null 2>&1; then
gh release delete dev-build --cleanup-tag --yes
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Re-create Tag
run: |
git tag -d dev-build || true
git tag dev-build
git push origin dev-build

- name: Publish Dev-Build Release
run: |
gh release create dev-build target/OumProfile-dev.jar --title "Development Build" --notes "This is an automated development build of the main branch." --prerelease
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35 changes: 35 additions & 0 deletions .github/workflows/qodana_code_quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#-------------------------------------------------------------------------------#
# Discover additional configuration options in our documentation #
# https://www.jetbrains.com/help/qodana/github.html #
#-------------------------------------------------------------------------------#

name: Qodana
on:
workflow_dispatch:
pull_request:
push:
branches:
- main
- dev

jobs:
qodana:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
checks: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: 'Qodana Scan'
uses: JetBrains/qodana-action@v2026.1
with:
pr-mode: false
use-caches: true
post-pr-comment: true
use-annotations: true
upload-result: false
push-fixes: 'none'
92 changes: 59 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,36 +71,40 @@ The following placeholders are supported under the `oumprofile` namespace:

### Player Commands

| Command | Description | Permission | Default |
|:-------------------------|:-------------------------------------------|:-------------------------|:--------|
| `/profile` | Opens the profile selection GUI. | `profiles.use` | True |
| `/profile list` | Lists all created profiles in chat. | `profiles.use` | True |
| `/profile current` | Displays the active profile name. | `profiles.use` | True |
| `/profile create <name>` | Creates a new profile with the given name. | `profiles.create.<name>` | OP |
| `/profile switch <name>` | Switches to the specified profile. | `profiles.use` | True |
| `/profile delete <name>` | Deletes the specified profile. | `profiles.use` | True |
| `/profile alerts` | Toggles receiving admin profile alerts. | `profiles.alerts` | OP |
| Command | Description | Permission | Default |
|:-----------------------------------|:-------------------------------------------|:-------------------------|:--------|
| `/profile` | Opens the profile selection GUI. | `profiles.use` | True |
| `/profile list` | Lists all created profiles in chat. | `profiles.use` | True |
| `/profile current` | Displays the active profile name. | `profiles.use` | True |
| `/profile create <name>` | Creates a new profile with the given name. | `profiles.create.<name>` | OP |
| `/profile switch <name>` | Switches to the specified profile. | `profiles.use` | True |
| `/profile delete <name>` | Deletes the specified profile. | `profiles.use` | True |
| `/profile rename <old> <new>` | Renames a profile. | `profiles.use` | True |
| `/profile alerts` | Toggles receiving admin profile alerts. | `profiles.alerts` | OP |

*Aliases: `/profiles`, `/prof`*

### Bypass and Administrative Permissions

| Command / Permission | Description | Default |
|:-------------------------------------------|:-------------------------------------------------------------|:-------------------------------|
| `/profile admin-open <player>` | Opens the profile GUI for the specified player. | OP (requires `profiles.admin`) |
| `/profile admin-list <player>` | Lists all profiles of the specified player. | OP (requires `profiles.admin`) |
| `/profile admin-create <player> <profile>` | Creates a profile for the specified player. | OP (requires `profiles.admin`) |
| `/profile admin-switch <player> <profile>` | Forces the specified player to switch to a profile. | OP (requires `profiles.admin`) |
| `/profile admin-delete <player> <profile>` | Deletes a profile for the specified player. | OP (requires `profiles.admin`) |
| `/profile debug` | Toggles debug logging in console. | OP (requires `profiles.admin`) |
| `profiles.admin` | Access to administrative commands and config reload. | OP |
| `profiles.create.*` | Permission to create profiles with any name. | OP |
| `profiles.max.unlimited` | Bypasses all profile slot limits. | OP |
| `profiles.max.<number>` | Sets the maximum profile slot limit (e.g. `profiles.max.5`). | False |
| `profiles.bypass.combat` | Allows switching profiles while tagged in combat. | OP |
| `profiles.bypass.warmup` | Bypasses the switch countdown warmup. | OP |
| `profiles.bypass.cooldown` | Bypasses the switch cooldown. | OP |
| `profiles.alerts` | Receives administrative alerts for profile actions. | OP |
| Command / Permission | Description | Default |
|:--------------------------------------------------|:-------------------------------------------------------------|:-------------------------------|
| `/profile admin open <player>` | Opens the profile GUI for the specified player. | OP (requires `profiles.admin`) |
| `/profile admin list <player>` | Lists all profiles of the specified player. | OP (requires `profiles.admin`) |
| `/profile admin create <player> <profile>` | Creates a profile for the specified player. | OP (requires `profiles.admin`) |
| `/profile admin switch <player> <profile>` | Forces the specified player to switch to a profile. | OP (requires `profiles.admin`) |
| `/profile admin delete <player> <profile>` | Deletes a profile for the specified player. | OP (requires `profiles.admin`) |
| `/profile admin rename <player> <old> <new>` | Renames a profile for the specified player. | OP (requires `profiles.admin`) |
| `/profile admin export <player> <profile>` | Exports a player's profile to a JSON file. | OP (requires `profiles.admin`) |
| `/profile admin import <player> <file>` | Imports a profile from a JSON file for a player. | OP (requires `profiles.admin`) |
| `/profile debug` | Toggles debug logging in console. | OP (requires `profiles.admin`) |
| `profiles.admin` | Access to administrative commands and config reload. | OP |
| `profiles.create.*` | Permission to create profiles with any name. | OP |
| `profiles.max.unlimited` | Bypasses all profile slot limits. | OP |
| `profiles.max.<number>` | Sets the maximum profile slot limit (e.g. `profiles.max.5`). | False |
| `profiles.bypass.combat` | Allows switching profiles while tagged in combat. | OP |
| `profiles.bypass.warmup` | Bypasses the switch countdown warmup. | OP |
| `profiles.bypass.cooldown` | Bypasses the switch cooldown. | OP |
| `profiles.alerts` | Receives administrative alerts for profile actions. | OP |

---

Expand Down Expand Up @@ -211,6 +215,18 @@ messages:
warmupCancelledGeneric: "<color:#f38ba8>Profile switch cancelled.</color>"
debugEnabled: "<color:#a6e3a1>Debug mode enabled.</color>"
debugDisabled: "<color:#f38ba8>Debug mode disabled.</color>"
renameSuccess: "<color:#a6e3a1>Renamed profile <color:#fab387><old></color> to <color:#cba6f7><new></color>.</color>"
renameFail: "<color:#f38ba8>Could not rename profile <color:#fab387>'<old>'</color>.</color>"
cannotRenameDefault: "<color:#f38ba8>You cannot rename the default profile.</color>"
adminRenameSuccess: "<color:#a6e3a1>Renamed profile <color:#fab387><old></color> to <color:#cba6f7><new></color> for <color:#cba6f7><target></color>.</color>"
adminRenameFail: "<color:#f38ba8>Failed to rename profile for player.</color>"
adminAlertRename: "<color:#f38ba8><b>ALERT</b></color> <color:#585b70><b>|</b></color> <color:#a6adc8><player> renamed profile <color:#cba6f7><b><old></b></color> to <color:#cba6f7><b><new></b></color></color>"
profileNameTooLong: "<color:#f38ba8>Profile name must be at most <color:#fab387><max></color> characters.</color>"
profileNameInvalidChars: "<color:#f38ba8>Profile name contains invalid characters. Only letters, numbers, hyphens and underscores are allowed.</color>"
exportSuccess: "<color:#a6e3a1>Exported profile <color:#fab387><name></color> to file.</color>"
importSuccess: "<color:#a6e3a1>Imported profile <color:#fab387><name></color> for <color:#cba6f7><target></color>.</color>"
importFail: "<color:#f38ba8>Failed to import profile from file.</color>"


# GUI menus and chat input settings
gui:
Expand Down Expand Up @@ -340,19 +356,27 @@ dateFormat: "yyyy-MM-dd HH:mm"

# Enable administrative alerts when players switch, create, or delete profiles
adminAlertsEnabled: true

# Maximum character length for profile names
profileNameMaxLength: 16

# Regex pattern for valid profile names
profileNameRegex: "[a-zA-Z0-9_-]+"
```

### Configuration Options

#### Global Settings

| Option | Type | Default | Description |
|:---------------------|:--------------|:-------------------|:-----------------------------------------------------------------------------------|
| `debug` | Boolean | `false` | Enable detailed debug logging in the server console. |
| `defaultProfileName` | String | `default` | Name of the initial profile created automatically when a player first joins. |
| `dateFormat` | String | `yyyy-MM-dd HH:mm` | Date format used for displaying profile creation and last used timestamps. |
| `adminAlertsEnabled` | Boolean | `true` | Broadcast profile actions (create, delete, switch) to administrators. |
| `limitTiers` | List<Integer> | `[1, 3, 5, 10]` | Profile slot limit thresholds based on permission nodes (e.g. `oumprofile.max.5`). |
| Option | Type | Default | Description |
|:-----------------------|:--------------|:-------------------|:-----------------------------------------------------------------------------------|
| `debug` | Boolean | `false` | Enable detailed debug logging in the server console. |
| `defaultProfileName` | String | `default` | Name of the initial profile created automatically when a player first joins. |
| `dateFormat` | String | `yyyy-MM-dd HH:mm` | Date format used for displaying profile creation and last used timestamps. |
| `adminAlertsEnabled` | Boolean | `true` | Broadcast profile actions (create, delete, switch, rename) to administrators. |
| `limitTiers` | List<Integer> | `[1, 3, 5, 10]` | Profile slot limit thresholds based on permission nodes (e.g. `oumprofile.max.5`). |
| `profileNameMaxLength` | Integer | `16` | Maximum character length allowed for profile names. |
| `profileNameRegex` | String | `[a-zA-Z0-9_-]+` | Regex pattern that profile names must match. |

#### Switching Settings (`switching`)

Expand Down Expand Up @@ -456,10 +480,11 @@ public class OumProfileAPIExample {
boolean hasPvp = ProfileAPI.hasProfile(uuid, "pvp");
int maxSlots = ProfileAPI.getMaxProfiles(player);

// Programmatically create, delete, or switch profiles
// Programmatically create, delete, switch, or rename profiles
ProfileAPI.createProfile(player, "pvp");
ProfileAPI.deleteProfile(player, "pvp");
ProfileAPI.switchProfile(player, "pvp");
ProfileAPI.renameProfile(player, "pvp", "factions");

// Manage switch warmup states
boolean isWarmupActive = ProfileAPI.hasPendingWarmup(uuid);
Expand All @@ -485,6 +510,7 @@ public class OumProfileAPIExample {
* **`ProfileLoadEvent`**: Fired when a player's profiles are loaded on join.
* **`ProfileCreateEvent`** *(Cancellable)*: Fired before a profile is created.
* **`ProfileDeleteEvent`** *(Cancellable)*: Fired before a profile is deleted.
* **`ProfileRenameEvent`** *(Cancellable)*: Fired before a profile is renamed.
* **`ProfileSwitchEvent`** *(Cancellable)*: Fired when a profile switch is requested, and right before the switch takes
place.
* **`ProfilePostSwitchEvent`**: Fired after a profile switch completes.
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<groupId>dev.oum</groupId>
<artifactId>oumprofile</artifactId>
<version>1.1-RELEASE</version>
<version>1.2-SNAPSHOT</version>
<packaging>jar</packaging>

<properties>
Expand Down Expand Up @@ -43,7 +43,7 @@
<dependency>
<groupId>dev.oum</groupId>
<artifactId>oumlib-core</artifactId>
<version>1.0.6</version>
<version>1.0.8</version>
<scope>compile</scope>
</dependency>

Expand Down
48 changes: 48 additions & 0 deletions qodana.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#-------------------------------------------------------------------------------#
# Qodana analysis is configured by qodana.yaml file #
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
#-------------------------------------------------------------------------------#

#################################################################################
# WARNING: Do not store sensitive information in this file, #
# as its contents will be included in the Qodana report. #
#################################################################################
version: "1.0"

#Specify inspection profile for code analysis
profile:
name: qodana.starter

#Enable inspections
#include:
# - name: <SomeEnabledInspectionId>

#Disable inspections
#exclude:
# - name: <SomeDisabledInspectionId>
# paths:
# - <path/where/not/run/inspection>

projectJDK: "21" #(Applied in CI/CD pipeline)

#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
#bootstrap: sh ./prepare-qodana.sh

#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
#plugins:
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)

# Quality gate. Will fail the CI/CD pipeline if any condition is not met
# severityThresholds - configures maximum thresholds for different problem severities
# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code
# Code Coverage is available in Ultimate and Ultimate Plus plans
#failureConditions:
# severityThresholds:
# any: 15
# critical: 5
# testCoverageThresholds:
# fresh: 70
# total: 50

#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
linter: jetbrains/qodana-jvm-community:2026.1
12 changes: 12 additions & 0 deletions src/main/java/dev/oum/profile/api/ProfileAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -258,4 +258,16 @@ public static long getProfilePlaytimeSeconds(@NonNull UUID uuid, @NonNull String
Long pt = data.state().playtimeSeconds();
return pt != null ? pt : 0L;
}

/**
* Renames a profile for a player.
*
* @param player The player.
* @param oldName The current profile name.
* @param newName The new profile name.
* @return True if rename succeeded, false otherwise.
*/
public static boolean renameProfile(@NonNull Player player, @NonNull String oldName, @NonNull String newName) {
return manager().renameProfile(player, oldName, newName);
}
}
49 changes: 49 additions & 0 deletions src/main/java/dev/oum/profile/api/event/ProfileRenameEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package dev.oum.profile.api.event;

import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.jspecify.annotations.NonNull;

@SuppressWarnings("unused")
public class ProfileRenameEvent extends PlayerEvent implements Cancellable {

private static final HandlerList HANDLERS = new HandlerList();
private final String oldName;
private final String newName;
private boolean cancelled;

public ProfileRenameEvent(@NonNull Player player, @NonNull String oldName, @NonNull String newName) {
super(player);
this.oldName = oldName;
this.newName = newName;
}

public static @NonNull HandlerList getHandlerList() {
return HANDLERS;
}

public @NonNull String getOldName() {
return oldName;
}

public @NonNull String getNewName() {
return newName;
}

@Override
public boolean isCancelled() {
return cancelled;
}

@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}

@Override
public @NonNull HandlerList getHandlers() {
return HANDLERS;
}
}
Loading
Loading