github.com/df-mc/dragonfly@v0.9.13/server/conf.go (about) 1 package server 2 3 import ( 4 "fmt" 5 "github.com/df-mc/dragonfly/server/block" 6 "github.com/df-mc/dragonfly/server/entity" 7 "github.com/df-mc/dragonfly/server/internal/packbuilder" 8 "github.com/df-mc/dragonfly/server/player" 9 "github.com/df-mc/dragonfly/server/player/playerdb" 10 "github.com/df-mc/dragonfly/server/session" 11 "github.com/df-mc/dragonfly/server/world" 12 "github.com/df-mc/dragonfly/server/world/biome" 13 "github.com/df-mc/dragonfly/server/world/generator" 14 "github.com/df-mc/dragonfly/server/world/mcdb" 15 "github.com/google/uuid" 16 "github.com/sandertv/gophertunnel/minecraft/resource" 17 "github.com/sirupsen/logrus" 18 "os" 19 "path/filepath" 20 "slices" 21 ) 22 23 // Config contains options for starting a Minecraft server. 24 type Config struct { 25 // Log is the Logger to use for logging information. If the Logger is a 26 // logrus.Logger, additional fields may be added to it for individual worlds 27 // to provide additional context. If left empty, Log will be set to a logger 28 // created with logrus.New(). 29 Log Logger 30 // Listeners is a list of functions to create a Listener using a Config, one 31 // for each Listener to be added to the Server. If left empty, no players 32 // will be able to connect to the Server. 33 Listeners []func(conf Config) (Listener, error) 34 // Name is the name of the server. By default, it is shown to users in the 35 // server list before joining the server and when opening the in-game menu. 36 Name string 37 // Resources is a slice of resource packs to use on the server. When joining 38 // the server, the player will then first be requested to download these 39 // resource packs. 40 Resources []*resource.Pack 41 // ResourcesRequires specifies if the downloading of resource packs is 42 // required to join the server. If set to true, players will not be able to 43 // join without first downloading and applying the Resources above. 44 ResourcesRequired bool 45 // DisableResourceBuilding specifies if automatic resource pack building for 46 // custom items should be disabled. Dragonfly, by default, automatically 47 // produces a resource pack for custom items. If this is not desired (for 48 // example if a resource pack already exists), this can be set to false. 49 DisableResourceBuilding bool 50 // Allower may be used to specify what players can join the server and what 51 // players cannot. By returning false in the Allow method, for example if 52 // the player has been banned, will prevent the player from joining. 53 Allower Allower 54 // AuthDisabled specifies if XBOX Live authentication should be disabled. 55 // Note that this should generally only be done for testing purposes or for 56 // local games. Allowing players to join without authentication is generally 57 // a security hazard. 58 AuthDisabled bool 59 // MaxPlayers is the maximum amount of players allowed to join the server at 60 // once. 61 MaxPlayers int 62 // MaxChunkRadius is the maximum view distance that each player may have, 63 // measured in chunks. A chunk radius generally leads to more memory usage. 64 MaxChunkRadius int 65 // JoinMessage, QuitMessage and ShutdownMessage are the messages to send for 66 // when a player joins or quits the server and when the server shuts down, 67 // kicking all online players. JoinMessage and QuitMessage may have a '%v' 68 // argument, which will be replaced with the name of the player joining or 69 // quitting. 70 JoinMessage, QuitMessage, ShutdownMessage string 71 // PlayerProvider is the player.Provider used for storing and loading player 72 // data. If left as nil, player data will be newly created every time a 73 // player joins the server and no data will be stored. 74 PlayerProvider player.Provider 75 // WorldProvider is the world.Provider used for storing and loading world 76 // data. If left as nil, world data will be newly created every time and 77 // chunks will always be newly generated when loaded. The world provider 78 // will be used for storing/loading the default overworld, nether and end. 79 WorldProvider world.Provider 80 // ReadOnlyWorld specifies if the standard worlds should be read only. If 81 // set to true, the WorldProvider won't be saved to at all. 82 ReadOnlyWorld bool 83 // Generator should return a function that specifies the world.Generator to 84 // use for every world.Dimension (world.Overworld, world.Nether and 85 // world.End). If left empty, Generator will be set to a flat world for each 86 // of the dimensions (with netherrack and end stone for nether/end 87 // respectively). 88 Generator func(dim world.Dimension) world.Generator 89 // RandomTickSpeed specifies the rate at which blocks should be ticked in 90 // the default worlds. Setting this value to -1 or lower will stop random 91 // ticking altogether, while setting it higher results in faster ticking. If 92 // left as 0, the RandomTickSpeed will default to a speed of 3 blocks per 93 // sub chunk per tick (normal ticking speed). 94 RandomTickSpeed int 95 // Entities is a world.EntityRegistry with all entity types registered that 96 // may be added to the Server's worlds. If no entity types are registered, 97 // Entities will be set to entity.DefaultRegistry. 98 Entities world.EntityRegistry 99 } 100 101 // Logger is used to report information and errors from a dragonfly Server. Any 102 // Logger implementation may be used by passing it to the Log field in Config. 103 type Logger interface { 104 world.Logger 105 session.Logger 106 Infof(format string, v ...any) 107 Fatalf(format string, v ...any) 108 Warnf(format string, v ...any) 109 } 110 111 // New creates a Server using fields of conf. The Server's worlds are created 112 // and connections from the Server's listeners may be accepted by calling 113 // Server.Listen() and Server.Accept() afterwards. 114 func (conf Config) New() *Server { 115 if conf.Log == nil { 116 conf.Log = logrus.New() 117 } 118 if len(conf.Listeners) == 0 { 119 conf.Log.Warnf("config: no listeners set, no connections will be accepted") 120 } 121 if conf.Name == "" { 122 conf.Name = "Dragonfly Server" 123 } 124 if conf.PlayerProvider == nil { 125 conf.PlayerProvider = player.NopProvider{} 126 } 127 if conf.Allower == nil { 128 conf.Allower = allower{} 129 } 130 if conf.WorldProvider == nil { 131 conf.WorldProvider = world.NopProvider{} 132 } 133 if conf.Generator == nil { 134 conf.Generator = loadGenerator 135 } 136 if conf.MaxChunkRadius == 0 { 137 conf.MaxChunkRadius = 12 138 } 139 if len(conf.Entities.Types()) == 0 { 140 conf.Entities = entity.DefaultRegistry 141 } 142 if !conf.DisableResourceBuilding { 143 if pack, ok := packbuilder.BuildResourcePack(); ok { 144 conf.Resources = append(conf.Resources, pack) 145 } 146 } 147 // Copy resources so that the slice can't be edited afterwards. 148 conf.Resources = slices.Clone(conf.Resources) 149 150 srv := &Server{ 151 conf: conf, 152 incoming: make(chan *session.Session), 153 p: make(map[uuid.UUID]*player.Player), 154 world: &world.World{}, nether: &world.World{}, end: &world.World{}, 155 } 156 srv.world = srv.createWorld(world.Overworld, &srv.nether, &srv.end) 157 srv.nether = srv.createWorld(world.Nether, &srv.world, &srv.end) 158 srv.end = srv.createWorld(world.End, &srv.nether, &srv.world) 159 160 srv.registerTargetFunc() 161 srv.checkNetIsolation() 162 163 return srv 164 } 165 166 // UserConfig is the user configuration for a Dragonfly server. It holds 167 // settings that affect different aspects of the server, such as its name and 168 // maximum players. UserConfig may be serialised and can be converted to a 169 // Config by calling UserConfig.Config(). 170 type UserConfig struct { 171 // Network holds settings related to network aspects of the server. 172 Network struct { 173 // Address is the address on which the server should listen. Players may 174 // connect to this address in order to join. 175 Address string 176 } 177 Server struct { 178 // Name is the name of the server as it shows up in the server list. 179 Name string 180 // ShutdownMessage is the message shown to players when the server shuts 181 // down. If empty, players will be directed to the menu screen right 182 // away. 183 ShutdownMessage string 184 // AuthEnabled controls whether players must be connected to Xbox Live 185 // in order to join the server. 186 AuthEnabled bool 187 // JoinMessage is the message that appears when a player joins the 188 // server. Leave this empty to disable it. %v is the placeholder for the 189 // username of the player 190 JoinMessage string 191 // QuitMessage is the message that appears when a player leaves the 192 // server. Leave this empty to disable it. %v is the placeholder for the 193 // username of the player 194 QuitMessage string 195 } 196 World struct { 197 // SaveData controls whether a world's data will be saved and loaded. 198 // If true, the server will use the default LevelDB data provider and if 199 // false, an empty provider will be used. To use your own provider, turn 200 // this value to false, as you will still be able to pass your own 201 // provider. 202 SaveData bool 203 // Folder is the folder that the data of the world resides in. 204 Folder string 205 } 206 Players struct { 207 // MaxCount is the maximum amount of players allowed to join the server 208 // at the same time. If set to 0, the amount of maximum players will 209 // grow every time a player joins. 210 MaxCount int 211 // MaximumChunkRadius is the maximum chunk radius that players may set 212 // in their settings. If they try to set it above this number, it will 213 // be capped and set to the max. 214 MaximumChunkRadius int 215 // SaveData controls whether a player's data will be saved and loaded. 216 // If true, the server will use the default LevelDB data provider and if 217 // false, an empty provider will be used. To use your own provider, turn 218 // this value to false, as you will still be able to pass your own 219 // provider. 220 SaveData bool 221 // Folder controls where the player data will be stored by the default 222 // LevelDB player provider if it is enabled. 223 Folder string 224 } 225 Resources struct { 226 // AutoBuildPack is if the server should automatically generate a 227 // resource pack for custom features. 228 AutoBuildPack bool 229 // Folder controls the location where resource packs will be loaded 230 // from. 231 Folder string 232 // Required is a boolean to force the client to load the resource pack 233 // on join. If they do not accept, they'll have to leave the server. 234 Required bool 235 } 236 } 237 238 // Config converts a UserConfig to a Config, so that it may be used for creating 239 // a Server. An error is returned if creating data providers or loading 240 // resources failed. 241 func (uc UserConfig) Config(log Logger) (Config, error) { 242 var err error 243 conf := Config{ 244 Log: log, 245 Name: uc.Server.Name, 246 ResourcesRequired: uc.Resources.Required, 247 AuthDisabled: !uc.Server.AuthEnabled, 248 MaxPlayers: uc.Players.MaxCount, 249 MaxChunkRadius: uc.Players.MaximumChunkRadius, 250 JoinMessage: uc.Server.JoinMessage, 251 QuitMessage: uc.Server.QuitMessage, 252 ShutdownMessage: uc.Server.ShutdownMessage, 253 DisableResourceBuilding: !uc.Resources.AutoBuildPack, 254 } 255 if uc.World.SaveData { 256 conf.WorldProvider, err = mcdb.Config{Log: log}.Open(uc.World.Folder) 257 if err != nil { 258 return conf, fmt.Errorf("create world provider: %w", err) 259 } 260 } 261 conf.Resources, err = loadResources(uc.Resources.Folder) 262 if err != nil { 263 return conf, fmt.Errorf("load resources: %w", err) 264 } 265 if uc.Players.SaveData { 266 conf.PlayerProvider, err = playerdb.NewProvider(uc.Players.Folder) 267 if err != nil { 268 return conf, fmt.Errorf("create player provider: %w", err) 269 } 270 } 271 conf.Listeners = append(conf.Listeners, uc.listenerFunc) 272 return conf, nil 273 } 274 275 // loadResources loads all resource packs found in a directory passed. 276 func loadResources(dir string) ([]*resource.Pack, error) { 277 _ = os.MkdirAll(dir, 0777) 278 279 resources, err := os.ReadDir(dir) 280 if err != nil { 281 return nil, fmt.Errorf("read dir: %w", err) 282 } 283 packs := make([]*resource.Pack, len(resources)) 284 for i, entry := range resources { 285 packs[i], err = resource.ReadPath(filepath.Join(dir, entry.Name())) 286 if err != nil { 287 return nil, fmt.Errorf("compile resource (%v): %w", entry.Name(), err) 288 } 289 } 290 return packs, nil 291 } 292 293 // loadGenerator loads a standard world.Generator for a world.Dimension. 294 func loadGenerator(dim world.Dimension) world.Generator { 295 switch dim { 296 case world.Overworld: 297 return generator.NewFlat(biome.Plains{}, []world.Block{block.Grass{}, block.Dirt{}, block.Dirt{}, block.Bedrock{}}) 298 case world.Nether: 299 return generator.NewFlat(biome.NetherWastes{}, []world.Block{block.Netherrack{}, block.Netherrack{}, block.Netherrack{}, block.Bedrock{}}) 300 case world.End: 301 return generator.NewFlat(biome.End{}, []world.Block{block.EndStone{}, block.EndStone{}, block.EndStone{}, block.Bedrock{}}) 302 } 303 panic("should never happen") 304 } 305 306 // DefaultConfig returns a configuration with the default values filled out. 307 func DefaultConfig() UserConfig { 308 c := UserConfig{} 309 c.Network.Address = ":19132" 310 c.Server.Name = "Dragonfly Server" 311 c.Server.ShutdownMessage = "Server closed." 312 c.Server.AuthEnabled = true 313 c.Server.JoinMessage = "%v has joined the game" 314 c.Server.QuitMessage = "%v has left the game" 315 c.World.SaveData = true 316 c.World.Folder = "world" 317 c.Players.MaximumChunkRadius = 32 318 c.Players.SaveData = true 319 c.Players.Folder = "players" 320 c.Resources.AutoBuildPack = true 321 c.Resources.Folder = "resources" 322 c.Resources.Required = false 323 return c 324 }