359 lines
9.0 KiB
Go
359 lines
9.0 KiB
Go
package channel
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Channel type constants
|
|
const (
|
|
TypeNone = 0
|
|
TypeWorld = 1 // Persistent, loaded from database
|
|
TypeCustom = 2 // Temporary, deleted when empty
|
|
)
|
|
|
|
// Channel actions for client communication
|
|
const (
|
|
ActionJoin = 0 // Player joins channel
|
|
ActionLeave = 1 // Player leaves channel
|
|
ActionOtherJoin = 2 // Another player joins
|
|
ActionOtherLeave = 3 // Another player leaves
|
|
)
|
|
|
|
// Channel name and password limits
|
|
const (
|
|
MaxNameLength = 100
|
|
MaxPasswordLength = 100
|
|
)
|
|
|
|
// Channel restrictions
|
|
const (
|
|
NoLevelRestriction = 0
|
|
NoRaceRestriction = 0
|
|
NoClassRestriction = 0
|
|
)
|
|
|
|
// Channel represents a chat channel with membership and message routing capabilities
|
|
type Channel struct {
|
|
mu sync.RWMutex
|
|
name string
|
|
password string
|
|
channelType int
|
|
levelRestriction int32
|
|
raceRestriction int32
|
|
classRestriction int32
|
|
members []int32 // Character IDs
|
|
discordEnabled bool
|
|
created time.Time
|
|
}
|
|
|
|
// ChannelInfo provides basic channel information for client lists
|
|
type ChannelInfo struct {
|
|
Name string
|
|
HasPassword bool
|
|
MemberCount int
|
|
LevelRestriction int32
|
|
RaceRestriction int32
|
|
ClassRestriction int32
|
|
ChannelType int
|
|
}
|
|
|
|
// ChannelMember represents a member in a channel
|
|
type ChannelMember struct {
|
|
CharacterID int32
|
|
CharacterName string
|
|
Level int32
|
|
Race int32
|
|
Class int32
|
|
JoinedAt time.Time
|
|
}
|
|
|
|
// NewChannel creates a new channel instance
|
|
func NewChannel(name string) *Channel {
|
|
return &Channel{
|
|
name: name,
|
|
members: make([]int32, 0),
|
|
created: time.Now(),
|
|
}
|
|
}
|
|
|
|
// SetName sets the channel name
|
|
func (c *Channel) SetName(name string) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.name = name
|
|
}
|
|
|
|
// SetPassword sets the channel password
|
|
func (c *Channel) SetPassword(password string) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.password = password
|
|
}
|
|
|
|
// SetType sets the channel type
|
|
func (c *Channel) SetType(channelType int) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.channelType = channelType
|
|
}
|
|
|
|
// SetLevelRestriction sets the minimum level required to join
|
|
func (c *Channel) SetLevelRestriction(level int32) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.levelRestriction = level
|
|
}
|
|
|
|
// SetRacesAllowed sets the race bitmask for allowed races
|
|
func (c *Channel) SetRacesAllowed(races int32) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.raceRestriction = races
|
|
}
|
|
|
|
// SetClassesAllowed sets the class bitmask for allowed classes
|
|
func (c *Channel) SetClassesAllowed(classes int32) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.classRestriction = classes
|
|
}
|
|
|
|
// SetDiscordEnabled enables or disables Discord integration for this channel
|
|
func (c *Channel) SetDiscordEnabled(enabled bool) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.discordEnabled = enabled
|
|
}
|
|
|
|
// GetName returns the channel name
|
|
func (c *Channel) GetName() string {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return c.name
|
|
}
|
|
|
|
// GetType returns the channel type
|
|
func (c *Channel) GetType() int {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return c.channelType
|
|
}
|
|
|
|
// GetNumClients returns the number of clients in the channel
|
|
func (c *Channel) GetNumClients() int {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return len(c.members)
|
|
}
|
|
|
|
// GetCreatedTime returns when the channel was created
|
|
func (c *Channel) GetCreatedTime() time.Time {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return c.created
|
|
}
|
|
|
|
// HasPassword returns true if the channel has a password
|
|
func (c *Channel) HasPassword() bool {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return c.password != ""
|
|
}
|
|
|
|
// PasswordMatches checks if the provided password matches the channel password
|
|
func (c *Channel) PasswordMatches(password string) bool {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return c.password == password
|
|
}
|
|
|
|
// CanJoinChannelByLevel checks if a player's level meets the channel requirements
|
|
func (c *Channel) CanJoinChannelByLevel(level int32) bool {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return level >= c.levelRestriction
|
|
}
|
|
|
|
// CanJoinChannelByRace checks if a player's race is allowed in the channel
|
|
func (c *Channel) CanJoinChannelByRace(raceID int32) bool {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return c.raceRestriction == NoRaceRestriction || (c.raceRestriction&(1<<raceID)) != 0
|
|
}
|
|
|
|
// CanJoinChannelByClass checks if a player's class is allowed in the channel
|
|
func (c *Channel) CanJoinChannelByClass(classID int32) bool {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return c.classRestriction == NoClassRestriction || (c.classRestriction&(1<<classID)) != 0
|
|
}
|
|
|
|
// IsInChannel checks if a character is in the channel
|
|
func (c *Channel) IsInChannel(characterID int32) bool {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return c.isInChannel(characterID)
|
|
}
|
|
|
|
// isInChannel is the internal implementation without locking
|
|
func (c *Channel) isInChannel(characterID int32) bool {
|
|
return slices.Contains(c.members, characterID)
|
|
}
|
|
|
|
// JoinChannel adds a character to the channel
|
|
func (c *Channel) JoinChannel(characterID int32) error {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
return c.joinChannel(characterID)
|
|
}
|
|
|
|
// joinChannel is the internal implementation without locking
|
|
func (c *Channel) joinChannel(characterID int32) error {
|
|
// Check if already in channel
|
|
if c.isInChannel(characterID) {
|
|
return fmt.Errorf("character %d is already in channel %s", characterID, c.name)
|
|
}
|
|
|
|
// Add to members list
|
|
c.members = append(c.members, characterID)
|
|
return nil
|
|
}
|
|
|
|
// LeaveChannel removes a character from the channel
|
|
func (c *Channel) LeaveChannel(characterID int32) error {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
return c.leaveChannel(characterID)
|
|
}
|
|
|
|
// leaveChannel is the internal implementation without locking
|
|
func (c *Channel) leaveChannel(characterID int32) error {
|
|
// Find and remove the character
|
|
for i, memberID := range c.members {
|
|
if memberID == characterID {
|
|
// Remove member by swapping with last element and truncating
|
|
c.members[i] = c.members[len(c.members)-1]
|
|
c.members = c.members[:len(c.members)-1]
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("character %d is not in channel %s", characterID, c.name)
|
|
}
|
|
|
|
// GetMembers returns a copy of the current member list
|
|
func (c *Channel) GetMembers() []int32 {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
// Return a copy to prevent external modification
|
|
members := make([]int32, len(c.members))
|
|
copy(members, c.members)
|
|
return members
|
|
}
|
|
|
|
// GetChannelInfo returns basic channel information
|
|
func (c *Channel) GetChannelInfo() ChannelInfo {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
return ChannelInfo{
|
|
Name: c.name,
|
|
HasPassword: c.password != "",
|
|
MemberCount: len(c.members),
|
|
LevelRestriction: c.levelRestriction,
|
|
RaceRestriction: c.raceRestriction,
|
|
ClassRestriction: c.classRestriction,
|
|
ChannelType: c.channelType,
|
|
}
|
|
}
|
|
|
|
// ValidateJoin checks if a character can join the channel based on restrictions
|
|
func (c *Channel) ValidateJoin(level, race, class int32, password string) error {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
// Check password
|
|
if c.password != "" && c.password != password {
|
|
return fmt.Errorf("invalid password for channel %s", c.name)
|
|
}
|
|
|
|
// Check level restriction
|
|
if !c.CanJoinChannelByLevel(level) {
|
|
return fmt.Errorf("level %d does not meet minimum requirement of %d for channel %s",
|
|
level, c.levelRestriction, c.name)
|
|
}
|
|
|
|
// Check race restriction
|
|
if !c.CanJoinChannelByRace(race) {
|
|
return fmt.Errorf("race %d is not allowed in channel %s", race, c.name)
|
|
}
|
|
|
|
// Check class restriction
|
|
if !c.CanJoinChannelByClass(class) {
|
|
return fmt.Errorf("class %d is not allowed in channel %s", class, c.name)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsEmpty returns true if the channel has no members
|
|
func (c *Channel) IsEmpty() bool {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return len(c.members) == 0
|
|
}
|
|
|
|
// IsDiscordEnabled returns true if Discord integration is enabled for this channel
|
|
func (c *Channel) IsDiscordEnabled() bool {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return c.discordEnabled
|
|
}
|
|
|
|
// Copy creates a deep copy of the channel (useful for serialization or backups)
|
|
func (c *Channel) Copy() *Channel {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
newChannel := &Channel{
|
|
name: c.name,
|
|
password: c.password,
|
|
channelType: c.channelType,
|
|
levelRestriction: c.levelRestriction,
|
|
raceRestriction: c.raceRestriction,
|
|
classRestriction: c.classRestriction,
|
|
discordEnabled: c.discordEnabled,
|
|
created: c.created,
|
|
members: make([]int32, len(c.members)),
|
|
}
|
|
|
|
copy(newChannel.members, c.members)
|
|
return newChannel
|
|
}
|
|
|
|
// GetRestrictions returns the channel's access restrictions
|
|
func (c *Channel) GetRestrictions() (level, race, class int32) {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return c.levelRestriction, c.raceRestriction, c.classRestriction
|
|
}
|
|
|
|
// GetPassword returns the channel password (for admin purposes)
|
|
func (c *Channel) GetPassword() string {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return c.password
|
|
}
|
|
|
|
// UpdateRestrictions updates all access restrictions at once
|
|
func (c *Channel) UpdateRestrictions(level, race, class int32) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.levelRestriction = level
|
|
c.raceRestriction = race
|
|
c.classRestriction = class
|
|
} |