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  }