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,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