feat: initial CR-Core library (team + player + command framework)
Pure Maven library for CR Minecraft game plugins, targeting Paper 1.16.5. Common abstractions (fr.luc.crcore.common): Identifiable, Named, ScoreHolder, AbstractEntity, Repository<T>. Team domain (fr.luc.crcore.team): Team entity with name/tag/color/leader/ visibility (PUBLIC|PRIVATE)/members/scores/spawn point, TeamMember, TeamRole/TeamColor/TeamVisibility enums, TeamRanking record, TeamService with overridable hooks (factories, validations, lifecycle events), in-memory repository, dedicated exception hierarchy. Player domain (fr.luc.crcore.player): PlayerProfile with named scores per player, PlayerProfileService with auto-creation, individual rankings, exception hierarchy. Both Team and PlayerProfile implement ScoreHolder. Command framework (fr.luc.crcore.command): Command interface, AbstractCommand base, BaseCommand (CommandExecutor + TabCompleter), SubCommand, CommandContext, CommandResult, ArgumentType<T> + ArgumentTypes catalogue (STRING, INTEGER, DOUBLE, BOOLEAN, ONLINE_PLAYER, enumOf, choice). Docs (docs/) is the single source of truth: README, setup, features, decisions log, and 6 PlantUML diagrams (team class/sequence/activity/join, player class, command class). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
@startuml command-class-diagram
|
||||
title CR-Core — Command framework (class diagram)
|
||||
|
||||
skinparam classAttributeIconSize 0
|
||||
hide empty members
|
||||
|
||||
package "fr.luc.crcore.command" {
|
||||
|
||||
interface Command {
|
||||
+ getName(): String
|
||||
+ getAliases(): List<String>
|
||||
+ getPermission(): String
|
||||
+ isPlayerOnly(): boolean
|
||||
+ getDescription(): String
|
||||
+ execute(ctx: CommandContext): CommandResult
|
||||
+ tabComplete(sender, argIndex, partial): List<String>
|
||||
+ matches(label: String): boolean
|
||||
}
|
||||
|
||||
abstract class AbstractCommand {
|
||||
- name: String
|
||||
- aliases: List<String>
|
||||
- permission: String
|
||||
- playerOnly: boolean
|
||||
- description: String
|
||||
- usage: String
|
||||
- arguments: List<ArgumentDef>
|
||||
# addAlias(...): void
|
||||
# permission(p): void
|
||||
# playerOnly(): void
|
||||
# description(d): void
|
||||
# usage(u): void
|
||||
# argument(name, type): void
|
||||
# optionalArgument(name, type): void
|
||||
# buildContext(sender, label, subArgs): CommandContext
|
||||
+ getRequiredArgumentCount(): int
|
||||
+ getTotalArgumentCount(): int
|
||||
+ getUsage(): String
|
||||
}
|
||||
|
||||
abstract class BaseCommand {
|
||||
- subCommandsByName: Map<String, SubCommand>
|
||||
- subCommandsByAlias: Map<String, SubCommand>
|
||||
# addSubCommand(sub: SubCommand): void
|
||||
+ findSubCommand(label: String): Optional<SubCommand>
|
||||
+ getSubCommands(): Collection<SubCommand>
|
||||
# execute(ctx): CommandResult
|
||||
+ onCommand(sender, cmd, label, args): boolean
|
||||
+ onTabComplete(sender, cmd, alias, args): List<String>
|
||||
# checkAccess(sender, target): boolean
|
||||
# handleResult(sender, result): void
|
||||
}
|
||||
|
||||
abstract class SubCommand {
|
||||
+ {abstract} execute(ctx: CommandContext): CommandResult
|
||||
}
|
||||
|
||||
class CommandContext {
|
||||
- sender: CommandSender
|
||||
- label: String
|
||||
- rawArgs: String[]
|
||||
- parsedArgs: Map<String, Object>
|
||||
+ getSender(): CommandSender
|
||||
+ isPlayer(): boolean
|
||||
+ getPlayer(): Optional<Player>
|
||||
+ requirePlayer(): Player
|
||||
+ get(name: String): T
|
||||
+ getOptional(name): Optional<T>
|
||||
+ has(name): boolean
|
||||
+ reply(msg): void
|
||||
}
|
||||
|
||||
class CommandResult {
|
||||
- type: Type
|
||||
- message: String
|
||||
+ getType(): Type
|
||||
+ getMessage(): String
|
||||
+ isSuccess(): boolean
|
||||
+ {static} success(): CommandResult
|
||||
+ {static} success(msg): CommandResult
|
||||
+ {static} failure(msg): CommandResult
|
||||
+ {static} invalidUsage(): CommandResult
|
||||
+ {static} noPermission(): CommandResult
|
||||
+ {static} playerOnly(): CommandResult
|
||||
}
|
||||
|
||||
enum "CommandResult.Type" as ResultType {
|
||||
SUCCESS
|
||||
FAILURE
|
||||
INVALID_USAGE
|
||||
NO_PERMISSION
|
||||
PLAYER_ONLY
|
||||
}
|
||||
|
||||
class CommandException
|
||||
|
||||
interface "ArgumentType<T>" as ArgumentType {
|
||||
+ parse(input: String): T
|
||||
+ suggestions(sender, partial): List<String>
|
||||
}
|
||||
|
||||
class ArgumentTypes << (S, #FFC107) static >> {
|
||||
+ {static} STRING: ArgumentType<String>
|
||||
+ {static} INTEGER: ArgumentType<Integer>
|
||||
+ {static} DOUBLE: ArgumentType<Double>
|
||||
+ {static} BOOLEAN: ArgumentType<Boolean>
|
||||
+ {static} ONLINE_PLAYER: ArgumentType<Player>
|
||||
+ {static} enumOf(type): ArgumentType<E>
|
||||
+ {static} choice(choices): ArgumentType<String>
|
||||
}
|
||||
|
||||
class ArgumentDef << (P, #BBBBBB) package-private >> {
|
||||
- name: String
|
||||
- type: ArgumentType<?>
|
||||
- required: boolean
|
||||
}
|
||||
|
||||
AbstractCommand ..|> Command
|
||||
BaseCommand --|> AbstractCommand
|
||||
SubCommand --|> AbstractCommand
|
||||
BaseCommand "1" o-- "*" SubCommand : subCommands
|
||||
AbstractCommand "1" *-- "*" ArgumentDef : arguments
|
||||
ArgumentDef --> ArgumentType
|
||||
CommandResult +-- ResultType
|
||||
CommandException --|> RuntimeException
|
||||
|
||||
BaseCommand ..> CommandContext : creates
|
||||
AbstractCommand ..> CommandContext : creates
|
||||
SubCommand ..> CommandResult : returns
|
||||
}
|
||||
|
||||
@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.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.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,239 @@
|
||||
@startuml team-class-diagram
|
||||
title CR-Core — Team domain (class diagram)
|
||||
|
||||
skinparam classAttributeIconSize 0
|
||||
hide empty members
|
||||
|
||||
' === Common abstractions ===
|
||||
|
||||
package "fr.luc.crcore.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.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
|
||||
- visibility: TeamVisibility
|
||||
- members: Set<TeamMember>
|
||||
- scores: Map<String, Integer>
|
||||
- spawnPoint: Location
|
||||
--
|
||||
+ getName(): String
|
||||
+ getTag(): String
|
||||
+ getColor(): TeamColor
|
||||
+ getLeaderId(): UUID
|
||||
+ getLeader(): TeamMember
|
||||
+ 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
|
||||
--
|
||||
+ 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, leaderId, [visibility]): Team
|
||||
+ dissolveTeam(teamId): boolean
|
||||
+ addMember(teamId, playerId): boolean
|
||||
+ removeMember(teamId, playerId): boolean
|
||||
+ joinTeam(teamId, playerId): boolean
|
||||
+ transferLeadership(teamId, newLeaderId): boolean
|
||||
+ 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,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