docs: organize diagrams to mirror code layout (util/ + features/)
Move flat docs/diagrams/*.puml into a hierarchy matching the source
package structure:
docs/diagrams/
├── bootstrap-sequence.puml (cross-cutting)
├── events-diagram.puml (cross-feature)
├── util/
│ ├── command-class-diagram.puml
│ ├── database-diagram.puml
│ ├── messages-class-diagram.puml
│ ├── broadcasts-class-diagram.puml
│ └── gui-class-diagram.puml
└── features/
├── team/
│ ├── team-class-diagram.puml
│ ├── team-config-class-diagram.puml
│ ├── builtin-commands-diagram.puml
│ ├── team-create-sequence.puml
│ ├── team-create-activity.puml
│ └── team-join-sequence.puml
├── player/
│ └── player-class-diagram.puml
└── moderation/
└── moderation-class-diagram.puml
README.md diagram index split into 4 sections (overview, util,
features/team, features/player, features/moderation) for readability;
all links updated. features.md auto-updated by sed for the new paths.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
@startuml moderation-class-diagram
|
||||
title CR-Core — Moderation feature (class diagram, skeleton)
|
||||
|
||||
skinparam classAttributeIconSize 0
|
||||
hide empty members
|
||||
|
||||
package "fr.luc.crcore.features.moderation" {
|
||||
|
||||
class ModerationState <<final>> {
|
||||
- playerId: UUID
|
||||
- enteredAt: Instant
|
||||
- inventoryContents: ItemStack[]
|
||||
- armorContents: ItemStack[]
|
||||
- offhandItem: ItemStack
|
||||
- xpLevel: int
|
||||
- xpProgress: float
|
||||
- health: double
|
||||
- foodLevel: int
|
||||
- saturation: float
|
||||
- location: Location
|
||||
- gameMode: GameMode
|
||||
- allowFlight / flying / walkSpeed / flySpeed
|
||||
--
|
||||
+ ModerationState(player)
|
||||
+ restoreTo(player): void
|
||||
}
|
||||
|
||||
interface ModerationRepository {
|
||||
+ findByPlayer(uuid): Optional<ModerationState>
|
||||
+ exists(uuid): boolean
|
||||
+ save(state): void
|
||||
+ delete(uuid): boolean
|
||||
+ findAll(): Collection<ModerationState>
|
||||
}
|
||||
|
||||
interface ModeratorTool {
|
||||
+ getKey(): String
|
||||
+ getSlot(): int ' 0..8
|
||||
+ buildIcon(): ItemStack
|
||||
+ onLeftClick(player): void
|
||||
+ onRightClick(player): void
|
||||
+ onInteractEntity(player, target): void
|
||||
}
|
||||
|
||||
class ModeratorToolRegistry {
|
||||
- bySlot: Map<Integer, ModeratorTool>
|
||||
- byKey: Map<String, ModeratorTool>
|
||||
+ register(tool): void
|
||||
+ unregister(key): boolean
|
||||
+ get(key): Optional<ModeratorTool>
|
||||
+ getBySlot(slot): Optional<ModeratorTool>
|
||||
+ all(): Collection<ModeratorTool>
|
||||
}
|
||||
|
||||
interface ModerationService {
|
||||
+ enter(player): void
|
||||
+ exit(player): void
|
||||
+ isInModeration(uuid): boolean
|
||||
+ getState(uuid): Optional<ModerationState>
|
||||
+ getActiveModerators(): Set<UUID>
|
||||
--
|
||||
+ vanish(player) / unvanish / isVanished / getVanishedPlayers
|
||||
+ freeze(uuid) / unfreeze / isFrozen / getFrozenPlayers
|
||||
+ getToolRegistry(): ModeratorToolRegistry
|
||||
}
|
||||
|
||||
package "fr.luc.crcore.features.moderation.impl" {
|
||||
class InMemoryModerationRepository
|
||||
InMemoryModerationRepository ..|> ModerationRepository
|
||||
|
||||
class ModerationServiceImpl {
|
||||
# plugin: Plugin
|
||||
# repository: ModerationRepository
|
||||
# toolRegistry: ModeratorToolRegistry
|
||||
# vanished: Set<UUID>
|
||||
# frozen: Set<UUID>
|
||||
--
|
||||
# onAfterEnter(player) ' hook
|
||||
# onAfterExit(player)
|
||||
}
|
||||
ModerationServiceImpl ..|> ModerationService
|
||||
|
||||
class BukkitEventFiringModerationServiceImpl
|
||||
BukkitEventFiringModerationServiceImpl --|> ModerationServiceImpl
|
||||
|
||||
class ModerationListener {
|
||||
+ registerOn(plugin): void
|
||||
--
|
||||
@ onInteract ' route → tool.onLeftClick / onRightClick
|
||||
@ onInteractEntity ' route → tool.onInteractEntity
|
||||
@ onDrop / onSwap ' lock hotbar
|
||||
@ onInventoryClick ' cancel sur self-inv
|
||||
@ onJoin ' re-hide vanished players
|
||||
@ onQuit ' unfreeze
|
||||
@ onMove ' cancel si frozen
|
||||
}
|
||||
ModerationListener ..|> "org.bukkit.event.Listener"
|
||||
ModerationListener --> ModerationService
|
||||
}
|
||||
|
||||
package "fr.luc.crcore.features.moderation.tool" {
|
||||
class ExitTool ' slot 8
|
||||
class VanishToggleTool ' slot 7
|
||||
class FreezeTool ' slot 2 (entity right-click)
|
||||
class InventorySpyTool ' slot 1 (entity right-click)
|
||||
class TeleportRandomPlayerTool ' slot 0
|
||||
ExitTool ..|> ModeratorTool
|
||||
VanishToggleTool ..|> ModeratorTool
|
||||
FreezeTool ..|> ModeratorTool
|
||||
InventorySpyTool ..|> ModeratorTool
|
||||
TeleportRandomPlayerTool ..|> ModeratorTool
|
||||
}
|
||||
|
||||
package "fr.luc.crcore.features.moderation.event" {
|
||||
abstract class ModerationEvent {
|
||||
- moderator: Player
|
||||
+ getModerator(): Player
|
||||
}
|
||||
ModerationEvent --|> "org.bukkit.event.Event"
|
||||
class ModerationEnterEvent
|
||||
class ModerationExitEvent
|
||||
ModerationEnterEvent --|> ModerationEvent
|
||||
ModerationExitEvent --|> ModerationEvent
|
||||
}
|
||||
|
||||
package "fr.luc.crcore.features.moderation.exception" {
|
||||
class ModerationException
|
||||
class ModerationAlreadyActiveException
|
||||
class ModerationNotActiveException
|
||||
ModerationException --|> RuntimeException
|
||||
ModerationAlreadyActiveException --|> ModerationException
|
||||
ModerationNotActiveException --|> ModerationException
|
||||
}
|
||||
|
||||
package "fr.luc.crcore.features.moderation.command" {
|
||||
class AdminToggleSubCommand {
|
||||
+ execute(ctx): CommandResult
|
||||
}
|
||||
AdminToggleSubCommand ..> ModerationService
|
||||
}
|
||||
|
||||
ModerationService ..> ModerationState : reads/writes
|
||||
ModerationService o--> ModerationRepository
|
||||
ModerationService o--> ModeratorToolRegistry
|
||||
ModeratorToolRegistry o--> "*" ModeratorTool
|
||||
}
|
||||
|
||||
package "fr.luc.crcore" {
|
||||
class CRCore {
|
||||
+ moderation(): ModerationService
|
||||
# buildModerationService(repo, registry): ModerationService
|
||||
# registerDefaultModeratorTools(registry, mod): void
|
||||
}
|
||||
CRCore "1" *-- "0..1" ModerationService : owns (if setupModeration)
|
||||
CRCore ..> ModerationListener : registers
|
||||
}
|
||||
|
||||
note bottom of ModerationServiceImpl
|
||||
enter(player) :
|
||||
1. ModerationState snapshot → repo.save
|
||||
2. clear inventory, set tools in hotbar
|
||||
3. gamemode CREATIVE + allowFlight
|
||||
4. vanish(player)
|
||||
5. onAfterEnter() → event fired
|
||||
|
||||
exit(player) :
|
||||
1. state.restoreTo(player) ' inv + xp + loc + gm + flight
|
||||
2. unvanish(player)
|
||||
3. unfreeze + repo.delete
|
||||
4. onAfterExit() → event fired
|
||||
|
||||
Skeleton : in-memory repository only.
|
||||
Persistance SQLite à venir.
|
||||
end note
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,110 @@
|
||||
@startuml player-class-diagram
|
||||
title CR-Core — Player domain (class diagram)
|
||||
|
||||
skinparam classAttributeIconSize 0
|
||||
hide empty members
|
||||
|
||||
' === Common abstractions ===
|
||||
|
||||
package "fr.luc.crcore.util.common" {
|
||||
|
||||
interface Identifiable {
|
||||
+ getId(): UUID
|
||||
}
|
||||
|
||||
interface ScoreHolder {
|
||||
+ getScore(name): int
|
||||
+ hasScore(name): boolean
|
||||
+ getScores(): Map<String, Integer>
|
||||
+ getTotalScore(): int
|
||||
+ addScore(name, delta): int
|
||||
+ setScore(name, value): int
|
||||
+ resetScore(name): boolean
|
||||
+ resetAllScores(): void
|
||||
}
|
||||
|
||||
abstract class AbstractEntity {
|
||||
- id: UUID
|
||||
+ getId(): UUID
|
||||
}
|
||||
|
||||
interface "Repository<T extends Identifiable>" as Repository {
|
||||
+ save(entity: T): T
|
||||
+ findById(id): Optional<T>
|
||||
+ findAll(): Collection<T>
|
||||
+ delete(id): boolean
|
||||
}
|
||||
|
||||
AbstractEntity ..|> Identifiable
|
||||
}
|
||||
|
||||
' === Player domain ===
|
||||
|
||||
package "fr.luc.crcore.features.player" {
|
||||
|
||||
class PlayerProfile {
|
||||
- scores: Map<String, Integer>
|
||||
+ PlayerProfile(playerId: UUID)
|
||||
+ getPlayerId(): UUID
|
||||
}
|
||||
|
||||
class PlayerRanking <<record>> {
|
||||
+ rank: int
|
||||
+ profile: PlayerProfile
|
||||
+ score: int
|
||||
}
|
||||
|
||||
interface PlayerProfileRepository
|
||||
|
||||
class InMemoryPlayerProfileRepository {
|
||||
- profiles: Map<UUID, PlayerProfile>
|
||||
}
|
||||
|
||||
interface PlayerProfileService {
|
||||
+ getOrCreateProfile(playerId): PlayerProfile
|
||||
+ getProfile(playerId): Optional<PlayerProfile>
|
||||
+ deleteProfile(playerId): boolean
|
||||
+ getAllProfiles(): Collection<PlayerProfile>
|
||||
--
|
||||
+ addScore(playerId, name, delta): int
|
||||
+ setScore(playerId, name, value): int
|
||||
+ getScore(playerId, name): int
|
||||
+ resetScore(playerId, name): boolean
|
||||
+ resetAllScores(playerId): void
|
||||
--
|
||||
+ getRankingByScore(name): List<PlayerRanking>
|
||||
+ getGlobalRanking(): List<PlayerRanking>
|
||||
+ getTopRankingByScore(name, limit): List<PlayerRanking>
|
||||
+ getTopGlobalRanking(limit): List<PlayerRanking>
|
||||
}
|
||||
|
||||
class PlayerProfileServiceImpl {
|
||||
- repository: PlayerProfileRepository
|
||||
--
|
||||
# newProfile(playerId): PlayerProfile
|
||||
# newRanking(rank, profile, score): PlayerRanking
|
||||
# rank(scoreFn): List<PlayerRanking>
|
||||
--
|
||||
# onProfileCreated(profile): void
|
||||
# onProfileDeleted(profile): void
|
||||
# onScoreChanged(profile, name, oldV, newV): void
|
||||
}
|
||||
|
||||
class PlayerException
|
||||
class PlayerProfileNotFoundException
|
||||
|
||||
PlayerProfile --|> AbstractEntity
|
||||
PlayerProfile ..|> ScoreHolder
|
||||
PlayerProfileRepository --|> Repository
|
||||
InMemoryPlayerProfileRepository ..|> PlayerProfileRepository
|
||||
PlayerProfileServiceImpl ..|> PlayerProfileService
|
||||
|
||||
PlayerRanking --> PlayerProfile
|
||||
PlayerProfileServiceImpl o--> PlayerProfileRepository
|
||||
PlayerProfileService ..> PlayerRanking : produces
|
||||
|
||||
PlayerException --|> RuntimeException
|
||||
PlayerProfileNotFoundException --|> PlayerException
|
||||
}
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,124 @@
|
||||
@startuml builtin-commands-diagram
|
||||
title CR-Core — Default /core team commands (admin / joueur)
|
||||
|
||||
skinparam classAttributeIconSize 0
|
||||
hide empty members
|
||||
|
||||
package "fr.luc.crcore.util.command" {
|
||||
abstract class BaseCommand
|
||||
abstract class SubCommand
|
||||
}
|
||||
|
||||
package "fr.luc.crcore.builtin" {
|
||||
|
||||
class CoreCommand
|
||||
CoreCommand --|> BaseCommand
|
||||
|
||||
package "fr.luc.crcore.features.team.command" {
|
||||
|
||||
class TeamGroupSubCommand {
|
||||
+ TeamGroupSubCommand(service)
|
||||
}
|
||||
TeamGroupSubCommand --|> SubCommand
|
||||
|
||||
class TeamArgumentTypes <<utility>> {
|
||||
+ {static} teamByName(service): ArgumentType<Team>
|
||||
}
|
||||
|
||||
' ─── ADMIN commands (permission seule, team par argument) ───
|
||||
package "admin" <<Rectangle>> {
|
||||
class TeamCreateSubCommand {
|
||||
perm: crcore.team.create
|
||||
args: name, tag, color, [leader]
|
||||
}
|
||||
class TeamDeleteSubCommand {
|
||||
perm: crcore.team.delete
|
||||
args: <team>
|
||||
}
|
||||
class TeamSetLeaderSubCommand {
|
||||
perm: crcore.team.setleader
|
||||
args: <team> <player>
|
||||
}
|
||||
class TeamScoreSubCommand {
|
||||
perm: crcore.team.score
|
||||
args: <team> <name> <add|set> <value>
|
||||
}
|
||||
class TeamAddSubCommand {
|
||||
perm: crcore.team.add
|
||||
args: <team> <player>
|
||||
}
|
||||
class TeamRemoveSubCommand {
|
||||
perm: crcore.team.remove
|
||||
args: <team> <player>
|
||||
}
|
||||
class TeamTransferSubCommand {
|
||||
perm: crcore.team.transfer
|
||||
args: <team> <player>
|
||||
}
|
||||
class TeamVisibilitySubCommand {
|
||||
perm: crcore.team.visibility
|
||||
args: <team> <vis>
|
||||
}
|
||||
class TeamSetSpawnSubCommand {
|
||||
perm: crcore.team.setspawn
|
||||
args: <team>
|
||||
playerOnly
|
||||
}
|
||||
}
|
||||
|
||||
' ─── PLAYER commands ───
|
||||
package "player" <<Rectangle>> {
|
||||
class TeamJoinSubCommand {
|
||||
perm: crcore.team.join
|
||||
args: <team>
|
||||
}
|
||||
class TeamLeaveSubCommand {
|
||||
perm: crcore.team.leave
|
||||
}
|
||||
class TeamInfoSubCommand {
|
||||
perm: crcore.team.info
|
||||
args: [team]
|
||||
}
|
||||
class TeamListSubCommand {
|
||||
perm: crcore.team.list
|
||||
}
|
||||
class TeamTopSubCommand {
|
||||
perm: crcore.team.top
|
||||
args: [score]
|
||||
}
|
||||
}
|
||||
|
||||
TeamCreateSubCommand --|> SubCommand
|
||||
TeamDeleteSubCommand --|> SubCommand
|
||||
TeamSetLeaderSubCommand --|> SubCommand
|
||||
TeamScoreSubCommand --|> SubCommand
|
||||
TeamAddSubCommand --|> SubCommand
|
||||
TeamRemoveSubCommand --|> SubCommand
|
||||
TeamTransferSubCommand --|> SubCommand
|
||||
TeamVisibilitySubCommand --|> SubCommand
|
||||
TeamSetSpawnSubCommand --|> SubCommand
|
||||
TeamJoinSubCommand --|> SubCommand
|
||||
TeamLeaveSubCommand --|> SubCommand
|
||||
TeamInfoSubCommand --|> SubCommand
|
||||
TeamListSubCommand --|> SubCommand
|
||||
TeamTopSubCommand --|> SubCommand
|
||||
|
||||
CoreCommand "1" *-- "1" TeamGroupSubCommand : contains
|
||||
TeamGroupSubCommand "1" *-- "14" SubCommand : contains
|
||||
}
|
||||
}
|
||||
|
||||
note bottom of TeamGroupSubCommand
|
||||
Le rôle LEADER reste dans le modèle Team
|
||||
(utilisable par les game plugins via l'API)
|
||||
mais n'accorde aucun privilège de commande
|
||||
dans le set par défaut.
|
||||
|
||||
Override d'une feuille :
|
||||
core.getCoreCommand()
|
||||
.findSubCommand("team")
|
||||
.replaceSubCommand("create",
|
||||
new MyCreate(svc));
|
||||
end note
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,251 @@
|
||||
@startuml team-class-diagram
|
||||
title CR-Core — Team domain (class diagram)
|
||||
|
||||
skinparam classAttributeIconSize 0
|
||||
hide empty members
|
||||
|
||||
' === Common abstractions ===
|
||||
|
||||
package "fr.luc.crcore.util.common" {
|
||||
|
||||
interface Identifiable {
|
||||
+ getId(): UUID
|
||||
}
|
||||
|
||||
interface Named {
|
||||
+ getName(): String
|
||||
}
|
||||
|
||||
interface ScoreHolder {
|
||||
+ getScore(name): int
|
||||
+ hasScore(name): boolean
|
||||
+ getScores(): Map<String, Integer>
|
||||
+ getTotalScore(): int
|
||||
+ addScore(name, delta): int
|
||||
+ setScore(name, value): int
|
||||
+ resetScore(name): boolean
|
||||
+ resetAllScores(): void
|
||||
}
|
||||
|
||||
abstract class AbstractEntity {
|
||||
- id: UUID
|
||||
+ AbstractEntity(id: UUID)
|
||||
+ getId(): UUID
|
||||
+ equals(o: Object): boolean
|
||||
+ hashCode(): int
|
||||
}
|
||||
|
||||
interface "Repository<T extends Identifiable>" as Repository {
|
||||
+ save(entity: T): T
|
||||
+ findById(id: UUID): Optional<T>
|
||||
+ findAll(): Collection<T>
|
||||
+ delete(id: UUID): boolean
|
||||
}
|
||||
|
||||
AbstractEntity ..|> Identifiable
|
||||
}
|
||||
|
||||
' === Team domain ===
|
||||
|
||||
package "fr.luc.crcore.features.team" {
|
||||
|
||||
enum TeamRole {
|
||||
LEADER
|
||||
MEMBER
|
||||
--
|
||||
+ isLeader(): boolean
|
||||
}
|
||||
|
||||
enum TeamVisibility {
|
||||
PUBLIC
|
||||
PRIVATE
|
||||
--
|
||||
+ isPublic(): boolean
|
||||
+ isPrivate(): boolean
|
||||
}
|
||||
|
||||
enum TeamColor {
|
||||
RED
|
||||
BLUE
|
||||
GREEN
|
||||
YELLOW
|
||||
AQUA
|
||||
LIGHT_PURPLE
|
||||
GOLD
|
||||
WHITE
|
||||
BLACK
|
||||
DARK_BLUE
|
||||
DARK_GREEN
|
||||
DARK_AQUA
|
||||
DARK_RED
|
||||
DARK_PURPLE
|
||||
DARK_GRAY
|
||||
GRAY
|
||||
--
|
||||
+ getChatColor(): ChatColor
|
||||
+ getDyeColor(): DyeColor
|
||||
+ getDisplayName(): String
|
||||
}
|
||||
|
||||
class TeamMember {
|
||||
- role: TeamRole
|
||||
- joinedAt: Instant
|
||||
+ getPlayerId(): UUID
|
||||
+ getRole(): TeamRole
|
||||
+ getJoinedAt(): Instant
|
||||
+ isLeader(): boolean
|
||||
+ withRole(role: TeamRole): TeamMember
|
||||
}
|
||||
|
||||
class Team {
|
||||
- name: String
|
||||
- tag: String
|
||||
- color: TeamColor
|
||||
- leaderId: UUID *(nullable)*
|
||||
- visibility: TeamVisibility
|
||||
- members: Set<TeamMember>
|
||||
- scores: Map<String, Integer>
|
||||
- spawnPoint: Location
|
||||
--
|
||||
+ Team(id, name, tag, color) ' leaderless, PRIVATE
|
||||
+ Team(id, name, tag, color, visibility) ' leaderless
|
||||
+ Team(id, name, tag, color, leaderId) ' with leader, PRIVATE
|
||||
+ Team(id, name, tag, color, leaderId, visibility)
|
||||
--
|
||||
+ getName(): String
|
||||
+ getTag(): String
|
||||
+ getColor(): TeamColor
|
||||
+ getLeaderId(): Optional<UUID>
|
||||
+ getLeader(): Optional<TeamMember>
|
||||
+ hasLeader(): boolean
|
||||
+ isLeader(playerId): boolean
|
||||
+ getVisibility(): TeamVisibility
|
||||
+ setVisibility(v): void
|
||||
+ isPublic(): boolean
|
||||
+ getMembers(): Set<TeamMember>
|
||||
+ getMember(playerId): Optional<TeamMember>
|
||||
+ hasMember(playerId): boolean
|
||||
+ size(): int
|
||||
+ addMember(playerId): TeamMember
|
||||
+ removeMember(playerId): boolean
|
||||
+ transferLeadership(newLeaderId): void ' strict: chef→chef
|
||||
+ setLeader(newLeaderId): void ' permissive: assign
|
||||
--
|
||||
+ getScore(name): int
|
||||
+ hasScore(name): boolean
|
||||
+ getScores(): Map<String, Integer>
|
||||
+ getTotalScore(): int
|
||||
+ addScore(name, delta): int
|
||||
+ setScore(name, value): int
|
||||
+ resetScore(name): boolean
|
||||
+ resetAllScores(): void
|
||||
--
|
||||
+ getSpawnPoint(): Optional<Location>
|
||||
+ hasSpawnPoint(): boolean
|
||||
+ setSpawnPoint(loc): void
|
||||
+ clearSpawnPoint(): void
|
||||
--
|
||||
# newMember(playerId, role): TeamMember
|
||||
}
|
||||
|
||||
class TeamRanking <<record>> {
|
||||
+ rank: int
|
||||
+ team: Team
|
||||
+ score: int
|
||||
}
|
||||
|
||||
interface TeamRepository {
|
||||
+ findByName(name: String): Optional<Team>
|
||||
+ findByTag(tag: String): Optional<Team>
|
||||
+ findByMember(playerId: UUID): Optional<Team>
|
||||
}
|
||||
|
||||
class InMemoryTeamRepository {
|
||||
- teams: Map<UUID, Team>
|
||||
}
|
||||
|
||||
interface TeamService {
|
||||
+ createTeam(name, tag, color): Team ' leaderless, PRIVATE
|
||||
+ createTeam(name, tag, color, visibility): Team ' leaderless
|
||||
+ createTeam(name, tag, color, leaderId): Team
|
||||
+ createTeam(name, tag, color, leaderId, visibility): Team
|
||||
+ dissolveTeam(teamId): boolean
|
||||
+ addMember(teamId, playerId): boolean
|
||||
+ removeMember(teamId, playerId): boolean
|
||||
+ joinTeam(teamId, playerId): boolean
|
||||
+ transferLeadership(teamId, newLeaderId): boolean ' strict: chef→chef
|
||||
+ setLeader(teamId, newLeaderId): boolean ' permissive (admin)
|
||||
+ setVisibility(teamId, visibility): void
|
||||
--
|
||||
+ addScore(teamId, name, delta): int
|
||||
+ setScore(teamId, name, value): int
|
||||
+ getScore(teamId, name): int
|
||||
+ resetScore(teamId, name): boolean
|
||||
+ resetAllScores(teamId): void
|
||||
--
|
||||
+ getRankingByScore(name): List<TeamRanking>
|
||||
+ getGlobalRanking(): List<TeamRanking>
|
||||
+ getTopRankingByScore(name, limit): List<TeamRanking>
|
||||
+ getTopGlobalRanking(limit): List<TeamRanking>
|
||||
--
|
||||
+ setSpawnPoint(teamId, loc): void
|
||||
+ clearSpawnPoint(teamId): void
|
||||
+ getSpawnPoint(teamId): Optional<Location>
|
||||
--
|
||||
+ getTeam / getTeamByName / getTeamByTag / getTeamOfPlayer
|
||||
+ getAllTeams(): Collection<Team>
|
||||
}
|
||||
|
||||
class TeamServiceImpl {
|
||||
- repository: TeamRepository
|
||||
--
|
||||
# newTeam(...): Team
|
||||
# newRanking(rank, team, score): TeamRanking
|
||||
# rank(scoreFn): List<TeamRanking>
|
||||
--
|
||||
# validateName(name): void
|
||||
# validateTag(tag): void
|
||||
# validateLeader(leaderId): void
|
||||
# validateJoinable(team, playerId): void
|
||||
--
|
||||
# onBeforeSave(team): void
|
||||
# onAfterCreate(team): void
|
||||
# onBeforeDissolve(team): void
|
||||
# onAfterDissolve(team): void
|
||||
# onMemberAdded(team, member): void
|
||||
# onMemberRemoved(team, playerId): void
|
||||
# onPlayerJoined(team, member): void
|
||||
# onLeadershipTransferred(team, oldId, newId): void
|
||||
# onVisibilityChanged(team, oldV, newV): void
|
||||
# onScoreChanged(team, name, oldV, newV): void
|
||||
# onSpawnPointChanged(team, oldLoc, newLoc): void
|
||||
}
|
||||
|
||||
class TeamException
|
||||
class TeamAlreadyExistsException
|
||||
class TeamNotFoundException
|
||||
class TeamAccessException
|
||||
|
||||
TeamMember --|> AbstractEntity
|
||||
Team --|> AbstractEntity
|
||||
Team ..|> Named
|
||||
Team ..|> ScoreHolder
|
||||
TeamRepository --|> Repository
|
||||
InMemoryTeamRepository ..|> TeamRepository
|
||||
TeamServiceImpl ..|> TeamService
|
||||
|
||||
Team "1" o-- "1..*" TeamMember : members
|
||||
Team --> TeamColor : color
|
||||
Team --> TeamVisibility : visibility
|
||||
TeamMember --> TeamRole : role
|
||||
TeamRanking --> Team
|
||||
TeamServiceImpl o--> TeamRepository
|
||||
TeamService ..> TeamRanking : produces
|
||||
|
||||
TeamException --|> RuntimeException
|
||||
TeamAlreadyExistsException --|> TeamException
|
||||
TeamNotFoundException --|> TeamException
|
||||
TeamAccessException --|> TeamException
|
||||
}
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,119 @@
|
||||
@startuml team-config-class-diagram
|
||||
title CR-Core — Team config (class diagram, cascade per-team → global → default)
|
||||
|
||||
skinparam classAttributeIconSize 0
|
||||
hide empty members
|
||||
|
||||
package "fr.luc.crcore.features.team.config" {
|
||||
|
||||
class "TeamSetting<T>" as TeamSetting <<final>> {
|
||||
- key: String
|
||||
- type: Class<T>
|
||||
- defaultValue: T
|
||||
- kind: Kind
|
||||
- parser: Function<Object,T>
|
||||
- serializer: Function<T,Object>
|
||||
--
|
||||
+ {static} ofBoolean(key, default): TeamSetting<Boolean>
|
||||
+ {static} ofInt(key, default): TeamSetting<Integer>
|
||||
+ {static} ofString(key, default): TeamSetting<String>
|
||||
+ {static} ofEnum(key, default): TeamSetting<E>
|
||||
--
|
||||
+ parse(raw): T
|
||||
+ serialize(value): Object
|
||||
+ getKey() / getType() / getDefaultValue() / getKind()
|
||||
}
|
||||
|
||||
enum "TeamSetting.Kind" as Kind {
|
||||
BOOLEAN
|
||||
INTEGER
|
||||
STRING
|
||||
ENUM
|
||||
}
|
||||
TeamSetting +-- Kind
|
||||
|
||||
class TeamSettings <<utility>> {
|
||||
+ {static} FRIENDLY_FIRE: TeamSetting<Boolean>
|
||||
+ {static} PVP_PROTECTION_SECONDS: TeamSetting<Integer>
|
||||
+ {static} MAX_SIZE: TeamSetting<Integer>
|
||||
+ {static} MIN_SIZE: TeamSetting<Integer>
|
||||
+ {static} RESPAWN_AT_TEAM_SPAWN: TeamSetting<Boolean>
|
||||
+ {static} TEAM_CHAT_ENABLED: TeamSetting<Boolean>
|
||||
+ {static} SHOW_TAG_ABOVE_HEAD: TeamSetting<Boolean>
|
||||
+ {static} TEAM_COLOR_IN_NAME: TeamSetting<Boolean>
|
||||
--
|
||||
+ {static} register(setting): void
|
||||
+ {static} get(key): Optional<TeamSetting<?>>
|
||||
+ {static} all(): Collection<TeamSetting<?>>
|
||||
}
|
||||
TeamSettings ..> TeamSetting
|
||||
|
||||
interface TeamConfigService {
|
||||
+ get(team, setting): T
|
||||
+ getGlobal(setting): T
|
||||
+ setPerTeam(team, setting, value): void
|
||||
+ resetPerTeam(team, setting): void
|
||||
+ setGlobal(setting, value): void
|
||||
+ reload(): void
|
||||
+ hasPerTeamOverride(team, setting): boolean
|
||||
+ getGlobalSnapshot(): Map<TeamSetting<?>, Object>
|
||||
+ getGlobalFileName(): Optional<String>
|
||||
}
|
||||
|
||||
package "fr.luc.crcore.features.team.config.impl" {
|
||||
class YamlTeamConfigService {
|
||||
- plugin: JavaPlugin
|
||||
- teamRepository: TeamRepository
|
||||
- userFile: File
|
||||
- globalValues: Map<String, Object>
|
||||
--
|
||||
- ensureUserFile(): void
|
||||
- rebuildGlobalValues(): void
|
||||
- persistGlobals(): void
|
||||
}
|
||||
YamlTeamConfigService ..|> TeamConfigService
|
||||
}
|
||||
|
||||
TeamConfigService ..> TeamSetting : reads/writes
|
||||
}
|
||||
|
||||
package "fr.luc.crcore.features.team" {
|
||||
class Team {
|
||||
- settings: Map<String, Object>
|
||||
+ getSettings(): Map<String, Object>
|
||||
}
|
||||
}
|
||||
|
||||
package "fr.luc.crcore.features.team.config.gui" {
|
||||
abstract class AbstractSettingsGui {
|
||||
- rebuild() : peint la grille
|
||||
# {abstract} getCurrentValue(setting): T
|
||||
# {abstract} onChange(setting, newValue): void
|
||||
# isOverride(setting): boolean
|
||||
# renderFooter(): void
|
||||
}
|
||||
class GlobalSettingsGui
|
||||
class TeamSettingsGui
|
||||
GlobalSettingsGui --|> AbstractSettingsGui
|
||||
TeamSettingsGui --|> AbstractSettingsGui
|
||||
AbstractSettingsGui --|> "fr.luc.crcore.util.gui.AbstractInventoryGui"
|
||||
|
||||
GlobalSettingsGui --> TeamConfigService
|
||||
TeamSettingsGui --> TeamConfigService
|
||||
TeamSettingsGui --> Team
|
||||
}
|
||||
|
||||
YamlTeamConfigService --> "fr.luc.crcore.features.team.TeamRepository" : persists per-team via save()
|
||||
TeamConfigService ..> Team : reads/writes settings map
|
||||
|
||||
note bottom of YamlTeamConfigService
|
||||
Cascade de résolution :
|
||||
1. team.getSettings().get(key) ← override per-team (SQLite)
|
||||
2. globalValues.get(key) ← <plugin>-team-config.yml
|
||||
3. setting.getDefaultValue() ← constante Java
|
||||
|
||||
Le YAML global est ré-écrit à chaque setGlobal() (persistant à crash).
|
||||
Les per-team sont écrits via teamRepository.save(team).
|
||||
end note
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,40 @@
|
||||
@startuml team-create-activity
|
||||
title CR-Core — Create Team (activity diagram)
|
||||
|
||||
start
|
||||
|
||||
:Player runs /team create <name> <tag> <color> [visibility];
|
||||
|
||||
if (Name already in use?) then (yes)
|
||||
:Reply "team name already taken";
|
||||
stop
|
||||
else (no)
|
||||
endif
|
||||
|
||||
if (Tag already in use?) then (yes)
|
||||
:Reply "team tag already taken";
|
||||
stop
|
||||
else (no)
|
||||
endif
|
||||
|
||||
if (Player already in a team?) then (yes)
|
||||
:Reply "you already belong to a team";
|
||||
stop
|
||||
else (no)
|
||||
endif
|
||||
|
||||
if (Color valid?) then (no)
|
||||
:Reply "unknown color";
|
||||
stop
|
||||
else (yes)
|
||||
endif
|
||||
|
||||
:Create Team(id = randomUUID, name, tag, color, leaderId = playerId, visibility);
|
||||
note right: visibility defaults to PRIVATE\nif not specified
|
||||
:Add player as TeamMember(role = LEADER);
|
||||
:Persist via TeamRepository.save(team);
|
||||
:Reply "team created";
|
||||
|
||||
stop
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,69 @@
|
||||
@startuml team-create-sequence
|
||||
title CR-Core — Create Team via command framework (sequence diagram)
|
||||
|
||||
actor Player
|
||||
participant "BaseCommand\n(TeamCommand)" as Base
|
||||
participant "SubCommand\n(TeamCreateSub)" as Sub
|
||||
participant "TeamService" as Service
|
||||
participant "TeamRepository" as Repo
|
||||
participant "Team" as Team
|
||||
|
||||
Player -> Base : /team create <name> <tag> <color>
|
||||
activate Base
|
||||
|
||||
Base -> Base : route args[0]="create" → TeamCreateSub
|
||||
Base -> Base : check permission + playerOnly
|
||||
Base -> Sub : buildContext(sender, label, subArgs)
|
||||
activate Sub
|
||||
Sub -> Sub : parse name (STRING) / tag (STRING) / color (enumOf TeamColor)
|
||||
Sub --> Base : CommandContext
|
||||
deactivate Sub
|
||||
|
||||
Base -> Sub : execute(ctx)
|
||||
activate Sub
|
||||
|
||||
Sub -> Service : createTeam(name, tag, color, playerId)
|
||||
activate Service
|
||||
|
||||
Service -> Service : validateName(name)
|
||||
Service -> Repo : findByName(name)
|
||||
activate Repo
|
||||
Repo --> Service : Optional.empty()
|
||||
deactivate Repo
|
||||
|
||||
Service -> Service : validateTag(tag)
|
||||
Service -> Repo : findByTag(tag)
|
||||
activate Repo
|
||||
Repo --> Service : Optional.empty()
|
||||
deactivate Repo
|
||||
|
||||
Service -> Service : validateLeader(playerId)
|
||||
Service -> Repo : findByMember(playerId)
|
||||
activate Repo
|
||||
Repo --> Service : Optional.empty()
|
||||
deactivate Repo
|
||||
|
||||
Service -> Team : newTeam(UUID.randomUUID(), name, tag, color, playerId, PRIVATE)
|
||||
activate Team
|
||||
Team -> Team : add newMember(playerId, LEADER)
|
||||
Team --> Service : team
|
||||
deactivate Team
|
||||
|
||||
Service -> Service : onBeforeSave(team)
|
||||
Service -> Repo : save(team)
|
||||
activate Repo
|
||||
Repo --> Service : team
|
||||
deactivate Repo
|
||||
Service -> Service : onAfterCreate(team)
|
||||
|
||||
Service --> Sub : team
|
||||
deactivate Service
|
||||
|
||||
Sub --> Base : CommandResult.success("Team created")
|
||||
deactivate Sub
|
||||
|
||||
Base -> Base : handleResult → sender.sendMessage(green text)
|
||||
Base --> Player : "Team <name> created"
|
||||
deactivate Base
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,62 @@
|
||||
@startuml team-join-sequence
|
||||
title CR-Core — Player joins a public team (sequence diagram)
|
||||
|
||||
actor Player
|
||||
participant "BaseCommand\n(TeamCommand)" as Base
|
||||
participant "SubCommand\n(TeamJoinSub)" as Sub
|
||||
participant "TeamService" as Service
|
||||
participant "TeamRepository" as Repo
|
||||
participant "Team" as Team
|
||||
|
||||
Player -> Base : /team join <name>
|
||||
activate Base
|
||||
|
||||
Base -> Base : route args[0]="join" → TeamJoinSub
|
||||
Base -> Sub : execute(ctx)
|
||||
activate Sub
|
||||
|
||||
Sub -> Service : getTeamByName(name)
|
||||
activate Service
|
||||
Service -> Repo : findByName(name)
|
||||
activate Repo
|
||||
Repo --> Service : Optional<Team>
|
||||
deactivate Repo
|
||||
Service --> Sub : Optional<Team>
|
||||
deactivate Service
|
||||
|
||||
alt team not found
|
||||
Sub --> Base : CommandResult.failure("Team not found")
|
||||
else team found
|
||||
Sub -> Service : joinTeam(team.id, playerId)
|
||||
activate Service
|
||||
|
||||
Service -> Service : validateJoinable(team, playerId)
|
||||
alt team is PRIVATE
|
||||
Service --> Sub : throw TeamAccessException
|
||||
Sub --> Base : CommandResult.failure(ex.message)
|
||||
else player already in a team
|
||||
Service --> Sub : throw TeamAccessException
|
||||
Sub --> Base : CommandResult.failure(ex.message)
|
||||
else allowed
|
||||
Service -> Team : addMember(playerId)
|
||||
activate Team
|
||||
Team --> Service : member
|
||||
deactivate Team
|
||||
Service -> Repo : save(team)
|
||||
activate Repo
|
||||
Repo --> Service : team
|
||||
deactivate Repo
|
||||
Service -> Service : onMemberAdded(team, member)
|
||||
Service -> Service : onPlayerJoined(team, member)
|
||||
Service --> Sub : true
|
||||
Sub --> Base : CommandResult.success("Joined " + team.name)
|
||||
end
|
||||
deactivate Service
|
||||
end
|
||||
|
||||
deactivate Sub
|
||||
Base -> Base : handleResult → sender.sendMessage
|
||||
Base --> Player : reply
|
||||
deactivate Base
|
||||
|
||||
@enduml
|
||||
Reference in New Issue
Block a user