about summary refs log tree commit diff
path: root/nixos/modules/services/games
diff options
context:
space:
mode:
authorJeremy <jtbx@duck.com>2023-08-07 11:20:09 +1200
committerJeremy Baxter <jtbx@disroot.org>2024-02-14 10:57:08 +1300
commite01eda6edd426904679d78c1803ce8ce57666487 (patch)
tree3900f707bb8c2b3431fd99a34a74a935652a9ef0 /nixos/modules/services/games
parent32c95ed9d53bc1827fc66082c89c0aca9ee0ecba (diff)
nixos/teeworlds: add more configuration options, revise
- add 'package' option
- add 'game' and 'server' attrset
- reduce repetition by using functions bool and optionalSetting
- add default value for cfg.name
- revise some option descriptions
Diffstat (limited to 'nixos/modules/services/games')
-rw-r--r--nixos/modules/services/games/teeworlds.nix310
1 files changed, 298 insertions, 12 deletions
diff --git a/nixos/modules/services/games/teeworlds.nix b/nixos/modules/services/games/teeworlds.nix
index bd0df1ffca578..04b611fb3cb14 100644
--- a/nixos/modules/services/games/teeworlds.nix
+++ b/nixos/modules/services/games/teeworlds.nix
@@ -6,13 +6,86 @@ let
   cfg = config.services.teeworlds;
   register = cfg.register;
 
+  bool = b: if b != null && b then "1" else "0";
+  optionalSetting = s: setting: optionalString (s != null) "${setting} ${s}";
+  lookup = attrs: key: default: if attrs ? key then attrs."${key}" else default;
+
+  inactivePenaltyOptions = {
+    "spectator" = "1";
+    "spectator/kick" = "2";
+    "kick" = "3";
+  };
+  skillLevelOptions = {
+    "casual" = "0";
+    "normal" = "1";
+    "competitive" = "2";
+  };
+  tournamentModeOptions = {
+    "disable" = "0";
+    "enable"  = "1";
+    "restrictSpectators" = "2";
+  };
+
   teeworldsConf = pkgs.writeText "teeworlds.cfg" ''
     sv_port ${toString cfg.port}
-    sv_register ${if cfg.register then "1" else "0"}
-    ${optionalString (cfg.name != null) "sv_name ${cfg.name}"}
-    ${optionalString (cfg.motd != null) "sv_motd ${cfg.motd}"}
-    ${optionalString (cfg.password != null) "password ${cfg.password}"}
-    ${optionalString (cfg.rconPassword != null) "sv_rcon_password ${cfg.rconPassword}"}
+    sv_register ${bool cfg.register}
+    sv_name ${cfg.name}
+    ${optionalSetting cfg.motd "sv_motd"}
+    ${optionalSetting cfg.password "password"}
+    ${optionalSetting cfg.rconPassword "sv_rcon_password"}
+
+    ${optionalSetting cfg.server.bindAddr "bindaddr"}
+    ${optionalSetting cfg.server.hostName "sv_hostname"}
+    sv_high_bandwidth ${bool cfg.server.enableHighBandwidth}
+    sv_inactivekick ${lookup inactivePenaltyOptions cfg.server.inactivePenalty "spectator/kick"}
+    sv_inactivekick_spec ${bool cfg.server.kickInactiveSpectators}
+    sv_inactivekick_time ${toString cfg.server.inactiveTime}
+    sv_max_clients ${toString cfg.server.maxClients}
+    sv_max_clients_per_ip ${toString cfg.server.maxClientsPerIP}
+    sv_skill_level ${lookup skillLevelOptions cfg.server.skillLevel "normal"}
+    sv_spamprotection ${bool cfg.server.enableSpamProtection}
+
+    sv_gametype ${cfg.game.gameType}
+    sv_map ${cfg.game.map}
+    sv_match_swap ${bool cfg.game.swapTeams}
+    sv_player_ready_mode ${bool cfg.game.enableReadyMode}
+    sv_player_slots ${toString cfg.game.playerSlots}
+    sv_powerups ${bool cfg.game.enablePowerups}
+    sv_scorelimit ${toString cfg.game.scoreLimit}
+    sv_strict_spectate_mode ${bool cfg.game.restrictSpectators}
+    sv_teamdamage ${bool cfg.game.enableTeamDamage}
+    sv_timelimit ${toString cfg.game.timeLimit}
+    sv_tournament_mode ${lookup tournamentModeOptions cfg.server.tournamentMode "disable"}
+    sv_vote_kick ${bool cfg.game.enableVoteKick}
+    sv_vote_kick_bantime ${toString cfg.game.voteKickBanTime}
+    sv_vote_kick_min ${toString cfg.game.voteKickMinimumPlayers}
+
+    ${optionalSetting cfg.server.bindAddr "bindaddr"}
+    ${optionalSetting cfg.server.hostName "sv_hostname"}
+    sv_high_bandwidth ${bool cfg.server.enableHighBandwidth}
+    sv_inactivekick ${lookup inactivePenaltyOptions cfg.server.inactivePenalty "spectator/kick"}
+    sv_inactivekick_spec ${bool cfg.server.kickInactiveSpectators}
+    sv_inactivekick_time ${toString cfg.server.inactiveTime}
+    sv_max_clients ${toString cfg.server.maxClients}
+    sv_max_clients_per_ip ${toString cfg.server.maxClientsPerIP}
+    sv_skill_level ${lookup skillLevelOptions cfg.server.skillLevel "normal"}
+    sv_spamprotection ${bool cfg.server.enableSpamProtection}
+
+    sv_gametype ${cfg.game.gameType}
+    sv_map ${cfg.game.map}
+    sv_match_swap ${bool cfg.game.swapTeams}
+    sv_player_ready_mode ${bool cfg.game.enableReadyMode}
+    sv_player_slots ${toString cfg.game.playerSlots}
+    sv_powerups ${bool cfg.game.enablePowerups}
+    sv_scorelimit ${toString cfg.game.scoreLimit}
+    sv_strict_spectate_mode ${bool cfg.game.restrictSpectators}
+    sv_teamdamage ${bool cfg.game.enableTeamDamage}
+    sv_timelimit ${toString cfg.game.timeLimit}
+    sv_tournament_mode ${lookup tournamentModeOptions cfg.server.tournamentMode "disable"}
+    sv_vote_kick ${bool cfg.game.enableVoteKick}
+    sv_vote_kick_bantime ${toString cfg.game.voteKickBanTime}
+    sv_vote_kick_min ${toString cfg.game.voteKickMinimumPlayers}
+
     ${concatStringsSep "\n" cfg.extraOptions}
   '';
 
@@ -22,17 +95,19 @@ in
     services.teeworlds = {
       enable = mkEnableOption (lib.mdDoc "Teeworlds Server");
 
+      package = mkPackageOptionMD pkgs "teeworlds-server" { };
+
       openPorts = mkOption {
         type = types.bool;
         default = false;
-        description = lib.mdDoc "Whether to open firewall ports for Teeworlds";
+        description = lib.mdDoc "Whether to open firewall ports for Teeworlds.";
       };
 
       name = mkOption {
-        type = types.nullOr types.str;
-        default = null;
+        type = types.str;
+        default = "unnamed server";
         description = lib.mdDoc ''
-          Name of the server. Defaults to 'unnamed server'.
+          Name of the server.
         '';
       };
 
@@ -41,7 +116,7 @@ in
         example = true;
         default = false;
         description = lib.mdDoc ''
-          Whether the server registers as public server in the global server list. This is disabled by default because of privacy.
+          Whether the server registers as a public server in the global server list. This is disabled by default for privacy reasons.
         '';
       };
 
@@ -49,7 +124,7 @@ in
         type = types.nullOr types.str;
         default = null;
         description = lib.mdDoc ''
-          Set the server message of the day text.
+          The server's message of the day text.
         '';
       };
 
@@ -85,6 +160,217 @@ in
         '';
         example = [ "sv_map dm1" "sv_gametype dm" ];
       };
+
+      server = {
+        bindAddr = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = lib.mdDoc ''
+            The address the server will bind to.
+          '';
+        };
+
+        enableHighBandwidth = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc ''
+            Whether to enable high bandwidth mode on LAN servers. This will double the amount of bandwidth required for running the server.
+          '';
+        };
+
+        hostName = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = lib.mdDoc ''
+            Hostname for the server.
+          '';
+        };
+
+        inactivePenalty = mkOption {
+          type = types.enum [ "spectator" "spectator/kick" "kick" ];
+          example = "spectator";
+          default = "spectator/kick";
+          description = lib.mdDoc ''
+            Specify what to do when a client goes inactive (see [](#opt-services.teeworlds.server.inactiveTime)).
+
+            - `spectator`: send the client into spectator mode
+
+            - `spectator/kick`: send the client into a free spectator slot, otherwise kick the client
+
+            - `kick`: kick the client
+          '';
+        };
+
+        kickInactiveSpectators = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc ''
+            Whether to kick inactive spectators.
+          '';
+        };
+
+        inactiveTime = mkOption {
+          type = types.ints.unsigned;
+          default = 3;
+          description = lib.mdDoc ''
+            The amount of minutes a client has to idle before it is considered inactive.
+          '';
+        };
+
+        maxClients = mkOption {
+          type = types.ints.unsigned;
+          default = 12;
+          description = lib.mdDoc ''
+            The maximum amount of clients that can be connected to the server at the same time.
+          '';
+        };
+
+        maxClientsPerIP = mkOption {
+          type = types.ints.unsigned;
+          default = 12;
+          description = lib.mdDoc ''
+            The maximum amount of clients with the same IP address that can be connected to the server at the same time.
+          '';
+        };
+
+        skillLevel = mkOption {
+          type = types.enum [ "casual" "normal" "competitive" ];
+          default = "normal";
+          description = lib.mdDoc ''
+            The skill level shown in the server browser.
+          '';
+        };
+
+        enableSpamProtection = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Whether to enable chat spam protection.
+          '';
+        };
+      };
+
+      game = {
+        gameType = mkOption {
+          type = types.str;
+          example = "ctf";
+          default = "dm";
+          description = lib.mdDoc ''
+            The game type to use on the server.
+
+            The default gametypes are `dm`, `tdm`, `ctf`, `lms`, and `lts`.
+          '';
+        };
+
+        map = mkOption {
+          type = types.str;
+          example = "ctf5";
+          default = "dm1";
+          description = lib.mdDoc ''
+            The map to use on the server.
+          '';
+        };
+
+        swapTeams = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Whether to swap teams each round.
+          '';
+        };
+
+        enableReadyMode = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc ''
+            Whether to enable "ready mode"; where players can pause/unpause the game
+            and start the game in warmup, using their ready state.
+          '';
+        };
+
+        playerSlots = mkOption {
+          type = types.ints.unsigned;
+          default = 8;
+          description = lib.mdDoc ''
+            The amount of slots to reserve for players (as opposed to spectators).
+          '';
+        };
+
+        enablePowerups = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Whether to allow powerups such as the ninja.
+          '';
+        };
+
+        scoreLimit = mkOption {
+          type = types.ints.unsigned;
+          example = 400;
+          default = 20;
+          description = lib.mdDoc ''
+            The score limit needed to win a round.
+          '';
+        };
+
+        restrictSpectators = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc ''
+            Whether to restrict access to information such as health, ammo and armour in spectator mode.
+          '';
+        };
+
+        enableTeamDamage = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc ''
+            Whether to enable team damage; whether to allow team mates to inflict damage on one another.
+          '';
+        };
+
+        timeLimit = mkOption {
+          type = types.ints.unsigned;
+          default = 0;
+          description = lib.mdDoc ''
+            Time limit of the game. In cases of equal points, there will be sudden death.
+            Setting this to 0 disables a time limit.
+          '';
+        };
+
+        tournamentMode = mkOption {
+          type = types.enum [ "disable" "enable" "restrictSpectators" ];
+          default = "disable";
+          description = lib.mdDoc ''
+            Whether to enable tournament mode. In tournament mode, players join as spectators.
+            If this is set to `restrictSpectators`, tournament mode is enabled but spectator chat is restricted.
+          '';
+        };
+
+        enableVoteKick = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Whether to enable voting to kick players.
+          '';
+        };
+
+        voteKickBanTime = mkOption {
+          type = types.ints.unsigned;
+          default = 5;
+          description = lib.mdDoc ''
+            The amount of minutes that a player is banned for if they get kicked by a vote.
+          '';
+        };
+
+        voteKickMinimumPlayers = mkOption {
+          type = types.ints.unsigned;
+          default = 5;
+          description = lib.mdDoc ''
+            The minimum amount of players required to start a kick vote.
+          '';
+        };
+      };
     };
   };
 
@@ -100,7 +386,7 @@ in
 
       serviceConfig = {
         DynamicUser = true;
-        ExecStart = "${pkgs.teeworlds-server}/bin/teeworlds_srv -f ${teeworldsConf}";
+        ExecStart = "${cfg.package}/bin/teeworlds_srv -f ${teeworldsConf}";
 
         # Hardening
         CapabilityBoundingSet = false;