-
-
Notifications
You must be signed in to change notification settings - Fork 85
feat: Add maximum player limit feature for lite #515
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,15 +15,19 @@ import ( | |
|
|
||
| // DefaultConfig is the default configuration for Lite mode. | ||
| var DefaultConfig = Config{ | ||
| Enabled: false, | ||
| Routes: []Route{}, | ||
| Enabled: false, | ||
| Routes: []Route{}, | ||
| MaxPlayers: 0, // 0 means unlimited | ||
| MaxPlayersMessage: "§cThis Node is full.Please try another node.", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use text component directly. also don't call it Node, it's a proxy |
||
| } | ||
|
|
||
| type ( | ||
| // Config is the configuration for Lite mode. | ||
| Config struct { | ||
| Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"` | ||
| Routes []Route `yaml:"routes,omitempty" json:"routes,omitempty"` | ||
| Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"` | ||
| Routes []Route `yaml:"routes,omitempty" json:"routes,omitempty"` | ||
| MaxPlayers int `yaml:"maxPlayers,omitempty" json:"maxPlayers,omitempty"` // Maximum number of players to accept, 0 means unlimited | ||
| MaxPlayersMessage string `yaml:"maxPlayersMessage,omitempty" json:"maxPlayersMessage,omitempty"` // Message to show when reaching max players limit | ||
|
Comment on lines
+29
to
+30
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can put the player specific settings into a sub-struct Players struct { MaxPlayers, MaxMessage } |
||
| } | ||
| Route struct { | ||
| Host configutil.SingleOrMulti[string] `json:"host,omitempty" yaml:"host,omitempty"` | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need this file. Just use |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package lite | ||
|
|
||
| import ( | ||
| "sync/atomic" | ||
| ) | ||
|
|
||
| // ConnectionCounter tracks the number of active connections in LiteMode | ||
| var ConnectionCounter = &counter{} | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do not put global state like this. A process could run many Gate instances at once, they must have their own counter |
||
|
|
||
| // counter is a thread-safe counter for connections | ||
| type counter struct { | ||
| count int32 | ||
| } | ||
|
|
||
| // Increment increments the counter by 1 and returns the new value | ||
| func (c *counter) Increment() int32 { | ||
| return atomic.AddInt32(&c.count, 1) | ||
| } | ||
|
|
||
| // Decrement decrements the counter by 1 and returns the new value | ||
| func (c *counter) Decrement() int32 { | ||
| return atomic.AddInt32(&c.count, -1) | ||
| } | ||
|
|
||
| // Count returns the current count | ||
| func (c *counter) Count() int32 { | ||
| return atomic.LoadInt32(&c.count) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,8 @@ import ( | |
|
|
||
| "github.com/go-logr/logr" | ||
| "github.com/jellydator/ttlcache/v3" | ||
| "go.minekube.com/common/minecraft/color" | ||
| "go.minekube.com/common/minecraft/component" | ||
| "go.minekube.com/gate/pkg/edition/java/internal/protoutil" | ||
| "go.minekube.com/gate/pkg/edition/java/lite/config" | ||
| "go.minekube.com/gate/pkg/edition/java/netmc" | ||
|
|
@@ -43,6 +45,35 @@ func Forward( | |
| return | ||
| } | ||
|
|
||
| // Check maximum connections limit if it's set (>0) | ||
| cfg := getConfig() | ||
| if cfg != nil && cfg.MaxPlayers > 0 { | ||
| currentCount := ConnectionCounter.Count() | ||
| if int(currentCount) >= cfg.MaxPlayers { | ||
| log.Info("connection rejected due to maximum player limit", | ||
| "current", currentCount, | ||
| "max", cfg.MaxPlayers) | ||
|
|
||
| // Use the configured message | ||
| message := cfg.MaxPlayersMessage | ||
| if message == "" { | ||
| message = "§cThis Node is full.Please try another node." | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stay DRY |
||
| } | ||
|
|
||
| // Disconnect and display custom message | ||
| reason := &component.Text{ | ||
| Content: message, | ||
| S: component.Style{Color: color.Red}, | ||
| } | ||
| _ = netmc.CloseWith(client, packet.NewDisconnect(reason, client.Protocol(), client.State().State)) | ||
| return | ||
| } | ||
|
Comment on lines
+49
to
+70
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like a lot of code, let's put it into a helper function |
||
| } | ||
|
|
||
| // Increment connection counter | ||
| ConnectionCounter.Increment() | ||
| defer ConnectionCounter.Decrement() | ||
|
|
||
| // Find a backend to dial successfully. | ||
| log, dst, err := tryBackends(nextBackend, func(log logr.Logger, backendAddr string) (logr.Logger, net.Conn, error) { | ||
| conn, err := dialRoute(client.Context(), dialTimeout, src.RemoteAddr(), route, backendAddr, handshake, pc, false) | ||
|
|
@@ -62,6 +93,19 @@ func Forward( | |
| pipe(log, src, dst) | ||
| } | ||
|
|
||
| // reference to the current lite mode config | ||
| var currentConfig *config.Config | ||
|
|
||
| // SetConfig sets the current lite mode config | ||
| func SetConfig(cfg *config.Config) { | ||
| currentConfig = cfg | ||
| } | ||
|
|
||
| // getConfig returns the current lite mode config | ||
| func getConfig() *config.Config { | ||
| return currentConfig | ||
| } | ||
|
|
||
|
Comment on lines
+96
to
+108
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a cheeky hack to pass state. It should be handled differently. |
||
| // errAllBackendsFailed is returned when all backends failed to dial. | ||
| var errAllBackendsFailed = errors.New("all backends failed") | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should use our text config component like other settings do to support the different legacy and modern text types.