git: 6bb96dba53ce - main - games/ktx: New port: Kombat Teams eXtreme is a popular QuakeWorld server modification
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sat, 22 Feb 2025 02:27:13 UTC
The branch main has been updated by vvd:
URL: https://cgit.FreeBSD.org/ports/commit/?id=6bb96dba53ce7ddf66061facefa348cbd0f27022
commit 6bb96dba53ce7ddf66061facefa348cbd0f27022
Author: Vladimir Druzenko <vvd@FreeBSD.org>
AuthorDate: 2025-02-22 02:24:12 +0000
Commit: Vladimir Druzenko <vvd@FreeBSD.org>
CommitDate: 2025-02-22 02:24:12 +0000
games/ktx: New port: Kombat Teams eXtreme is a popular QuakeWorld server modification
https://github.com/QW-Group/ktx/
---
GIDs | 2 +-
UIDs | 2 +-
games/Makefile | 1 +
games/ktx/Makefile | 63 +++++
games/ktx/distinfo | 3 +
games/ktx/files/ktx.in | 130 ++++++++++
...igs_ktx_configs_usermodes_matchless_default.cfg | 45 ++++
games/ktx/files/patch-socd | 192 ++++++++++++++
games/ktx/pkg-descr | 7 +
games/ktx/pkg-message | 17 ++
games/ktx/pkg-plist | 282 +++++++++++++++++++++
11 files changed, 742 insertions(+), 2 deletions(-)
diff --git a/GIDs b/GIDs
index 5b9c791f1edc..78fcf9d4be0d 100644
--- a/GIDs
+++ b/GIDs
@@ -440,7 +440,7 @@ openbao:*:482:
# free: 497
rustypaste:*:498:
# free: 499
-# free: 500
+qw:*:500:
qwfwd:*:501:
birdvty:*:502:
# free: 503
diff --git a/UIDs b/UIDs
index abc54781310f..a4dbaa399ef7 100644
--- a/UIDs
+++ b/UIDs
@@ -446,7 +446,7 @@ openbao:*:482:482:daemon:0:0:OpenBao Daemon:/nonexistent:/usr/sbin/nologin
# free: 497
rustypaste:*:498:498::0:0:Minimal file upload/pastebin service:/nonexistent:/usr/sbin/nologin
# free: 499
-# free: 500
+qw:*:500:500::0:0:QuakeWorld Server:/nonexistent:/usr/sbin/nologin
qwfwd:*:501:501::0:0:QuakeWorld Proxy:/nonexistent:/usr/sbin/nologin
# free: 502
# free: 503
diff --git a/games/Makefile b/games/Makefile
index 9afad89bf7e0..a4086e1704cd 100644
--- a/games/Makefile
+++ b/games/Makefile
@@ -475,6 +475,7 @@
SUBDIR += ksquares
SUBDIR += ksudoku
SUBDIR += ktuberling
+ SUBDIR += ktx
SUBDIR += kubrick
SUBDIR += kuklomenos
SUBDIR += ladder
diff --git a/games/ktx/Makefile b/games/ktx/Makefile
new file mode 100644
index 000000000000..3f6c877e06dc
--- /dev/null
+++ b/games/ktx/Makefile
@@ -0,0 +1,63 @@
+PORTNAME= ktx
+DISTVERSIONPREFIX= v
+DISTVERSION= 1.43
+CATEGORIES= games
+
+MAINTAINER= vvd@FreeBSD.org
+COMMENT= Kombat Teams eXtreme is a popular QuakeWorld server modification
+WWW= https://github.com/QW-Group/ktx/
+
+LICENSE= GPLv2
+
+RUN_DEPENDS= mvdsv:games/mvdsv
+
+USES= cmake dos2unix
+DOS2UNIX_FILES= resources/example-configs/ktx/ktx.cfg \
+ resources/example-configs/ktx/mvdsv.cfg \
+ resources/example-configs/ktx/problem.cfg \
+ resources/example-configs/ktx/race/routes/ztricks2.route
+USE_GITHUB= yes
+GH_ACCOUNT= QW-Group
+USE_RC_SUBR= ${PORTNAME}
+EXTRACT_AFTER_ARGS= --exclude ${PKGNAME}/.git* \
+ --exclude ${PKGNAME}/build_cmake.sh \
+ --exclude ${PKGNAME}/resources/example-configs/id1/"copy PAK0 and PAK1 here" \
+ --exclude ${PKGNAME}/tools/cross-cmake \
+ --exclude ${PKGNAME}/tools/vs \
+ --no-same-owner --no-same-permissions
+QWDIR= ${PREFIX}/quake
+SUB_LIST= QWDIR=${QWDIR} QWUSER=${USERS}
+USERS= qw
+GROUPS= qw
+PLIST_SUB= QWGROUP=${GROUPS}
+PORTDATA= *
+
+.include "${.CURDIR}/../quake-data/Makefile.include"
+.include <bsd.port.options.mk>
+
+do-install:
+ ${REINPLACE_CMD} -e 's|^set k_defmap|// set k_defmap|' \
+ ${WRKSRC}/resources/example-configs/ktx/ktx.cfg
+ ${MKDIR} ${STAGEDIR}${DATADIR}
+ cd ${WRKSRC}/resources && \
+ ${COPYTREE_SHARE} 'extralog logo' ${STAGEDIR}${DATADIR}
+ cd ${WRKSRC}/resources/example-configs && \
+ ${COPYTREE_SHARE} '*' ${STAGEDIR}${QWDIR}
+ ${MKDIR} ${STAGEDIR}${QWDIR}/ktx/demos ${STAGEDIR}${QWDIR}/qw
+ ${MV} ${STAGEDIR}${QWDIR}/id1/maps ${STAGEDIR}${QWDIR}/qw
+ ${INSTALL_PROGRAM} ${BUILD_WRKSRC}/qwprogs.so \
+ ${STAGEDIR}${DATADIR}
+ ${LN} -s ${DATADIR}/qwprogs.so ${STAGEDIR}${QWDIR}/ktx/
+ ${LN} -s ${Q1DIR}/id1/pak0.pak ${STAGEDIR}${QWDIR}/id1/
+ ${LN} -s ${Q1DIR}/id1/pak1.pak ${STAGEDIR}${QWDIR}/id1/
+ ${LN} -s ${Q1DIR}/id1/maps ${STAGEDIR}${QWDIR}/id1/
+
+.for f in port1 port2 port3 portffa servers
+ cd ${STAGEDIR}${QWDIR} && ${MV} ${f} ${f}.sample
+.endfor
+.for f in ktx.cfg listip.cfg matchless.cfg mvdfinish.qws mvdsv.cfg \
+ port1.cfg port2.cfg port3.cfg problem.cfg pwd.cfg server.cfg vip_ip.cfg
+ cd ${STAGEDIR}${QWDIR}/ktx && ${MV} ${f} ${f}.sample
+.endfor
+
+.include <bsd.port.mk>
diff --git a/games/ktx/distinfo b/games/ktx/distinfo
new file mode 100644
index 000000000000..ea3c5fe117e3
--- /dev/null
+++ b/games/ktx/distinfo
@@ -0,0 +1,3 @@
+TIMESTAMP = 1740182613
+SHA256 (QW-Group-ktx-v1.43_GH0.tar.gz) = 720f719c490d8e9e975b6bcab773e62bf4051c828ac394c61ab8f99e9e9c995b
+SIZE (QW-Group-ktx-v1.43_GH0.tar.gz) = 1696285
diff --git a/games/ktx/files/ktx.in b/games/ktx/files/ktx.in
new file mode 100755
index 000000000000..3e6e3075cc92
--- /dev/null
+++ b/games/ktx/files/ktx.in
@@ -0,0 +1,130 @@
+#!/bin/sh
+
+# PROVIDE: ktx
+# REQUIRE: LOGIN
+# KEYWORD: shutdown
+#
+# Add the following lines to /etc/rc.conf or /etc/rc.conf.local to
+# enable ktx:
+# ktx_(instance_)?enable (bool): Set to "NO" by default.
+# Set it to "YES" to enable ktx.
+# ktx_(instance_)?port (int): UDP port (default 27500).
+# ktx_(instance_)?gamedir (str): Gamedir for ktx (default ktx).
+# ktx_(instance_)?args (str): Custom additional arguments to be passed
+# to ${__qwserver} (default empty).
+# ktx_(instance_)?user (str): User to run ${__qwserver} as. Default
+# to "%%QWUSER%%" created by the port.
+# ktx_(instance_)?qwserver (path): Full path to QuakeWorld server to run ktx
+# (default %%LOCALBASE%%/bin/mvdsv).
+# ktx_(instance_)?log (path): Console log file (default
+# ${__gamedir}/${name}_(instance_)?${__port}.log).
+# ktx_(instance_)?randommaps (str): List of maps from which one is randomly
+# selected to start the server (default empty).
+# ktx_(instance_)?qwdir (path): QuakeWorld root directory
+# (default %%QWDIR%%).
+# ktx_instances (str): Set to "" by default.
+# If defined, list of instances to enable.
+
+. /etc/rc.subr
+
+case $0 in
+/etc/rc*)
+ # during boot (shutdown) $0 is /etc/rc (/etc/rc.shutdown),
+ # so get the name of the script from $_file
+ name=$_file
+ ;;
+*)
+ name=$0
+ ;;
+esac
+
+name=${name##*/}
+rcvar="${name}_enable"
+
+load_rc_config "${name}"
+
+eval "${rcvar}=\${${rcvar}:-'NO'}"
+eval "__port=\${${name}_port:-'27500'}"
+eval "__gamedir=\${${name}_gamedir:-'ktx'}"
+eval "__args=\${${name}_args:-''}"
+eval "__user=\${${name}_user:-'%%QWUSER%%'}"
+eval "__qwserver=\${${name}_qwserver:-'%%LOCALBASE%%/bin/mvdsv'}"
+eval "__log=\${${name}_log:-${__gamedir}/${name}_${__port}.log}"
+eval "__randommaps=\${${name}_randommaps:-''}"
+eval "__qwdir=\${${name}_qwdir:-'%%QWDIR%%'}"
+eval "${name}_chdir=${__qwdir}"
+eval "__instances=\${${name}_instances:-''}"
+
+pidfiledir="/var/run"
+pidfile="${pidfiledir}/${name}.pid"
+
+if [ -n "$2" ]; then
+ instance="$2"
+ load_rc_config ${name}_${instance}
+ case "${__instances}" in
+ "$2 "*|*" $2 "*|*" $2"|"$2")
+ eval "__port=\${${name}_${instance}_port:-${__port}}"
+ eval "__gamedir=\${${name}_${instance}_gamedir:-${__gamedir}}"
+ eval "__args=\${${name}_${instance}_args:-${__args}}"
+ eval "__user=\${${name}_${instance}_user:-${__user}}"
+ eval "__qwserver=\${${name}_${instance}_qwserver:-${__qwserver}}"
+ eval "__log=\${${name}_${instance}_log:-${__gamedir}/${name}_${instance}_${__port}.log}"
+ eval "__randommaps=\${${name}_${instance}_randommaps:-${__randommaps}}"
+ eval "__qwdir=\${${name}_${instance}_qwdir:-${__qwdir}}"
+ eval "${name}_chdir=${__qwdir}"
+ pidfile="${pidfiledir}/${name}_${instance}.pid"
+ ;;
+ *)
+ err 1 "$2 not found in ${name}_instances" ;;
+ esac
+else
+ if [ -n "${__instances}" -a -n "$1" ]; then
+ for instance in ${__instances}; do
+ eval "_enable=\${${name}_${instance}_enable}"
+ eval "__enable=\${_enable:-\${${name}_enable}}"
+ case "${__enable}" in
+ [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0)
+ continue
+ ;;
+ [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
+ ;;
+ *)
+ if [ -z "${_enable}" ]; then
+ _var=${name}_enable
+ else
+ _var=${name}_${instance}_enable
+ fi
+ warn "Bad value '${__enable}' for ${_var}. " \
+ "Instance ${instance} skipped."
+ continue
+ ;;
+ esac
+ echo "===> ${name} instance: ${instance}"
+ %%PREFIX%%/etc/rc.d/${name} $1 ${instance}
+ retcode="$?"
+ if [ "0${retcode}" -eq 0 ]; then
+ success="${instance} ${success}"
+ else
+ failed="${instance} (retcode=${retcode}) ${failed}"
+ fi
+ done
+ echo "===> ${name} instances success: ${success}"
+ echo "===> ${name} instances failed: ${failed}"
+ exit 0
+ fi
+fi
+
+if [ -n "${__randommaps}" -a \( "x$1" = "xstart" -o "x$1" = "xrestart" \) ]; then
+ RAND=`/usr/bin/head -c2 /dev/urandom | /usr/bin/od -An -t d`
+ #RAND=`echo | awk '{srand(); print rand()*1000000}'`
+ #RAND=$((`/bin/date +%N | /usr/bin/sed 's|^[0]*||'`+0))
+ LEN=`echo ${__randommaps} | /usr/bin/wc -w`
+ N=$((${RAND} % ${LEN} + 1))
+ MAP=`echo ${__randommaps} | /usr/bin/awk "{print \\$${N}}"`
+ __args="${__args} +map ${MAP}"
+fi
+
+command="/usr/sbin/daemon"
+command_args="-P ${pidfile} -u ${__user} -R 5 -f -H -o ${__log} -m 3 ${__qwserver} -port ${__port} -game ${__gamedir} ${__args}"
+
+run_rc_command "$1"
diff --git a/games/ktx/files/patch-resources_example-configs_ktx_configs_usermodes_matchless_default.cfg b/games/ktx/files/patch-resources_example-configs_ktx_configs_usermodes_matchless_default.cfg
new file mode 100644
index 000000000000..f10298dabc00
--- /dev/null
+++ b/games/ktx/files/patch-resources_example-configs_ktx_configs_usermodes_matchless_default.cfg
@@ -0,0 +1,45 @@
+--- resources/example-configs/ktx/configs/usermodes/matchless/default.cfg.orig 2024-03-10 07:30:18 UTC
++++ resources/example-configs/ktx/configs/usermodes/matchless/default.cfg
+@@ -4,7 +4,7 @@ set k_matchless_max_idle_time 0 // maximum time a
+ // matchless settings
+ set k_matchless_countdown 0 // countdown in matchless mode (0 = off, 1 = on)
+ set k_matchless_max_idle_time 0 // maximum time a user can be idle before being forced to spectator in matchless
+-set k_pow_min_players 1 // number of players on server required for powerups to be turned on
++set k_pow_min_players 3 // number of players on server required for powerups to be turned on
+ set k_pow_check_time 10 // how often should ktx check if the k_pow_min_players requirement has been met (seconds)
+ set k_no_vote_map 0 // allow map voting and /next_map (0 = allow voting, 1 = disallow voting)
+ set k_no_vote_break 1 // allow vote break (0 = allow break, 1 = disallow break)
+@@ -14,14 +14,27 @@ samelevel 0 // change level a
+
+ // Other settings
+ samelevel 0 // change level after match ends
+-timelimit 10 // round timelimit in minutes
+-maxclients 8 // maximum number of clients to connect
++timelimit 5 // round timelimit in minutes
++fraglimit 30
++maxclients 24 // maximum number of clients to connect
++maxspectators 8
+ set k_specktalk 1 // spectators can talk to players during round
+
+ // map rotation
+-set k_ml_0 "dm2"
+-set k_ml_1 "dm3"
+-set k_ml_2 "e1m2"
+-//set k_ml_3 "e1m3" // etc
++set k_ml_0 "bravado"
++set k_ml_1 "ztndm2"
++set k_ml_2 "aerowalk"
++set k_ml_3 "monsoon"
++set k_ml_4 "ztndm5"
++set k_ml_5 "shifter"
++set k_ml_6 "spinev2"
++set k_ml_7 "ztndm3"
++set k_ml_8 "catalyst"
++set k_ml_9 "toxicity"
++set k_ml_10 "ztndm4"
++set k_ml_11 "pkeg1"
++set k_ml_12 "ztndm6"
++set k_ml_13 "skyhigh"
++set k_ml_14 "pocket"
+
+ set k_random_maplist 0 // select random map from k_ml_XXX variables (0 = off, 1 = on)
diff --git a/games/ktx/files/patch-socd b/games/ktx/files/patch-socd
new file mode 100644
index 000000000000..03e80334b71e
--- /dev/null
+++ b/games/ktx/files/patch-socd
@@ -0,0 +1,192 @@
+From 631b375f3dbb150c3f37430a0a45f237ffac8153 Mon Sep 17 00:00:00 2001
+From: blaze <blaze@discord>
+Date: Tue, 30 Jul 2024 22:49:02 -0700
+Subject: [PATCH] socd detection
+
+---
+ .gitignore | 3 +++
+ include/progs.h | 11 ++++++++
+ src/client.c | 61 ++++++++++++++++++++++++++++++++++++++++++++
+ src/commands.c | 32 +++++++++++++++++++++--
+ src/stats.c | 8 ++++++
+ src/world.c | 2 ++
+ tools/vs/ktx.vcxproj | 34 ++++++++++++++++++------
+ 7 files changed, 141 insertions(+), 10 deletions(-)
+
+--- include/progs.h.orig
++++ include/progs.h
+@@ -944,6 +944,17 @@ typedef struct gedict_s
+ float fIllegalFPSWarnings;
+ // ILLEGALFPS]
+
++// SOCD detectioin
++ float fStrafeChangeCount;
++ float fFramePerfectStrafeChangeCount;
++ int socdDetected;
++ int socdChecksCount;
++ float fLastSideMoveSpeed;
++ int matchStrafeChangeCount;
++ int matchPerfectStrafeCount;
++ int nullStrafeCount;
++// SOCD
++
+ float shownick_time; // used to force centerprint is off at desired time
+ clientType_t ct; // client type for client edicts
+ // { timing
+--- src/client.c.orig
++++ src/client.c
+@@ -1658,6 +1658,16 @@ void ClientConnect()
+ SendIntermissionToClient();
+ }
+
++// SOCD
++ self->socdChecksCount = 0;
++ self->socdDetected = 0;
++ self->fStrafeChangeCount = 0;
++ self->fFramePerfectStrafeChangeCount = 0;
++ self->fLastSideMoveSpeed = 0;
++ self->matchStrafeChangeCount = 0;
++ self->matchPerfectStrafeCount = 0;
++ self->nullStrafeCount = 0;
++
+ // ILLEGALFPS[
+
+ // Zibbo's frametime checking code
+@@ -3520,6 +3530,57 @@ void PlayerPreThink()
+ }
+ #endif
+
++// SOCD detection
++ {
++ float fSideMoveSpeed = self->movement[1];
++
++ if ((fSideMoveSpeed != 0) && (fSideMoveSpeed != self->fLastSideMoveSpeed) && (self->nullStrafeCount < 3)) //strafechange
++ {
++ self->fStrafeChangeCount += 1;
++ if (match_in_progress)
++ self->matchStrafeChangeCount += 1;
++
++ if ((fSideMoveSpeed != 0) && (self->fLastSideMoveSpeed != 0))
++ {
++ self->fFramePerfectStrafeChangeCount += 1;
++ if (match_in_progress)
++ self->matchPerfectStrafeCount += 1;
++ }
++
++ self->nullStrafeCount = 0;
++ }
++ else
++ {
++ if (0 == fSideMoveSpeed)
++ self->nullStrafeCount += 1;
++ else
++ self->nullStrafeCount = 0;
++ }
++
++ self->fLastSideMoveSpeed = fSideMoveSpeed;
++
++ if (self->fStrafeChangeCount >= 16)
++ {
++ if (self->fFramePerfectStrafeChangeCount / self->fStrafeChangeCount >= 0.75)
++ {
++ int k_allow_socd_warning = cvar("k_allow_socd_warning");
++
++ self->socdDetected += 1;
++
++ if ((!match_in_progress) && (!self->isBot) && k_allow_socd_warning)
++ {
++ G_bprint(PRINT_HIGH,
++ "Warning! %s: SOCD movement assistance detected. Please disable iDrive or keyboard SOCD features.\n",
++ self->netname);
++ }
++ }
++
++ self->socdChecksCount += 1;
++ self->fStrafeChangeCount = 0;
++ self->fFramePerfectStrafeChangeCount = 0;
++ }
++ }
++
+ // ILLEGALFPS[
+
+ self->fAverageFrameTime += g_globalvars.frametime;
+--- src/commands.c.orig
++++ src/commands.c
+@@ -8114,16 +8114,47 @@ void fcheck()
+
+ if (!is_real_adm(self))
+ {
+- if (strneq(arg_x, "f_version") && strneq(arg_x, "f_modified") && strneq(arg_x, "f_server"))
++ if (strneq(arg_x, "f_version") && strneq(arg_x, "f_modified") && strneq(arg_x, "f_server") && strneq(arg_x, "f_movement"))
+ {
+ G_sprint(self, 2, "You are not allowed to check \020%s\021\n"
+- "available checks are: f_version, f_modified and f_server\n",
++ "available checks are: f_version, f_modified, f_server and f_movement\n",
+ arg_x);
+
+ return;
+ }
+ }
+
++ if (streq(arg_x, "f_movement"))
++ {
++ G_bprint(2, "%s is checking \020%s\021\n", self->netname, arg_x);
++
++ for (i = 1; i <= MAX_CLIENTS; i++)
++ {
++ if (!strnull(g_edicts[i].netname))
++ {
++ if (g_edicts[i].socdDetected > 0)
++ {
++ G_bprint(2, "%s: SOCD movement assistance detected!\n", g_edicts[i].netname);
++ }
++ else
++ {
++ if (g_edicts[i].socdChecksCount >= 2)
++ {
++ G_bprint(2, "%s: no assistance detected.\n", g_edicts[i].netname);
++ }
++ else
++ {
++ G_bprint(2, "%s: not enough movement to run SOCD detection. Please move around a map.\n", g_edicts[i].netname);
++ }
++ }
++ G_bprint(2, "%s: %s:%d/%d %s:%d/%d\n", redtext("Movement"), redtext("Perfect strafes"),
++ g_edicts[i].matchPerfectStrafeCount, g_edicts[i].matchStrafeChangeCount,
++ redtext("SOCD detections"), g_edicts[i].socdDetected, g_edicts[i].socdChecksCount);
++ }
++ }
++ return;
++ }
++
+ for (i = 1; i <= MAX_CLIENTS; i++)
+ {
+ if (g_edicts[i].f_checkbuf)
+--- src/stats.c.orig
++++ src/stats.c
+@@ -763,6 +763,14 @@ void OnePlayerStats(gedict_t *p, int tp)
+ p->ps.vel_frames > 0 ? p->ps.velocity_sum / p->ps.vel_frames : 0.);
+ }
+
++ if (!p->isBot)
++ {
++ G_bprint(2, "%s: %s:%d/%d %s:%d/%d\n", redtext("Movement"), redtext("Perfect strafes"),
++ p->matchPerfectStrafeCount, p->matchStrafeChangeCount, redtext("SOCD detections"),
++ p->socdDetected, p->socdChecksCount);
++ }
++
++
+ // armors + megahealths
+ if (!lgc_enabled())
+ {
+--- src/world.c.orig
++++ src/world.c
+@@ -1000,6 +1000,8 @@ void FirstFrame()
+
+ RegisterCvar("k_teamoverlay"); // q3 like team overlay
+
++ RegisterCvar("k_allow_socd_warning"); // socd
++
+ // { SP
+ RegisterCvarEx("k_monster_spawn_time", "20");
+ // }
diff --git a/games/ktx/pkg-descr b/games/ktx/pkg-descr
new file mode 100644
index 000000000000..2a698696b7c8
--- /dev/null
+++ b/games/ktx/pkg-descr
@@ -0,0 +1,7 @@
+KTX (Kombat Teams eXtreme) is a popular QuakeWorld server modification,
+adding numerous features to the core features of the server.
+
+Although it had been developed to be QuakeWorld server agnostic, it
+has over the years been developed very close to MVDSV to which it has
+become an extent, thus compatibility with other QuakeWorld servers
+might not have been maintained.
diff --git a/games/ktx/pkg-message b/games/ktx/pkg-message
new file mode 100644
index 000000000000..112a2b8c3e04
--- /dev/null
+++ b/games/ktx/pkg-message
@@ -0,0 +1,17 @@
+[
+{ type: install
+ message: <<EOM
+ Example configureation in rc.conf:
+ ktx_enable="YES"
+ ktx_randommaps="aerowalk bravado catalyst monsoon shifter toxicity ztndm3 dm2 dm3 e1m2 spinev2 pkeg1 ztndm6"
+ ktx_args="-mem 64"
+ ktx_instances="ffa 1 2 3 4"
+ ktx_ffa_args="+exec matchless.cfg"
+ ktx_ffa_randommaps="aerowalk bravado catalyst monsoon shifter toxicity ztndm3 spinev2 pkeg1 ztndm2 ztndm4 ztndm5 ztndm6 skyhigh"
+ ktx_1_port="27501"
+ ktx_2_port="27502"
+ ktx_3_port="27503"
+ ktx_4_port="27504"
+EOM
+}
+]
diff --git a/games/ktx/pkg-plist b/games/ktx/pkg-plist
new file mode 100644
index 000000000000..883706a66600
--- /dev/null
+++ b/games/ktx/pkg-plist
@@ -0,0 +1,282 @@
+quake/id1/maps
+quake/id1/pak0.pak
+quake/id1/pak1.pak
+quake/ktx/bots/maps/2towers.bot
+quake/ktx/bots/maps/aerowalk.bot
+quake/ktx/bots/maps/amphi2.bot
+quake/ktx/bots/maps/arenazap.bot
+quake/ktx/bots/maps/bravado.bot
+quake/ktx/bots/maps/cmt1b.bot
+quake/ktx/bots/maps/cmt3.bot
+quake/ktx/bots/maps/cmt4.bot
+quake/ktx/bots/maps/dm2dmm4.bot
+quake/ktx/bots/maps/dm3.bot
+quake/ktx/bots/maps/dm3hill.bot
+quake/ktx/bots/maps/dm4.bot
+quake/ktx/bots/maps/dm6.bot
+quake/ktx/bots/maps/dmm4_1.bot
+quake/ktx/bots/maps/dmm4_3.bot
+quake/ktx/bots/maps/e1m2.bot
+quake/ktx/bots/maps/endif.bot
+quake/ktx/bots/maps/frobodm2.bot
+quake/ktx/bots/maps/marena2.bot
+quake/ktx/bots/maps/marena3.bot
+quake/ktx/bots/maps/monsoon.bot
+quake/ktx/bots/maps/nacmidair.bot
+quake/ktx/bots/maps/noentry.bot
+quake/ktx/bots/maps/oldcrat.bot
+quake/ktx/bots/maps/pkeg1.bot
+quake/ktx/bots/maps/povdmm4.bot
+quake/ktx/bots/maps/povdmm4b.bot
+quake/ktx/bots/maps/ptucket.bot
+quake/ktx/bots/maps/pushdmm4.bot
+quake/ktx/bots/maps/q1q3monsoon#td.bot
+quake/ktx/bots/maps/rarena3.bot
+quake/ktx/bots/maps/ravageqwb8.bot
+quake/ktx/bots/maps/schloss.bot
+quake/ktx/bots/maps/shifter.bot
+quake/ktx/bots/maps/skull.bot
+quake/ktx/bots/maps/spinev2.bot
+quake/ktx/bots/maps/subterfuge.bot
+quake/ktx/bots/maps/ukooldm2.bot
+quake/ktx/bots/maps/ukooldm3.bot
+quake/ktx/bots/maps/ukooldm6.bot
+quake/ktx/bots/maps/ukooldm8.bot
+quake/ktx/bots/maps/ukpak2.bot
+quake/ktx/bots/maps/ultrav.bot
+quake/ktx/bots/maps/ztndm1.bot
+quake/ktx/bots/maps/ztndm2.bot
+quake/ktx/bots/maps/ztndm3.bot
+quake/ktx/bots/maps/ztndm4.bot
+quake/ktx/bots/maps/ztndm5.bot
+quake/ktx/bots/maps/ztndm6.bot
+quake/ktx/configs/reset.cfg
+quake/ktx/configs/usermodes/10on10/default.cfg
+quake/ktx/configs/usermodes/1on1/default.cfg
+quake/ktx/configs/usermodes/1on1/ra/default.cfg
+quake/ktx/configs/usermodes/2on2/aerowalk.cfg
+quake/ktx/configs/usermodes/2on2/default.cfg
+quake/ktx/configs/usermodes/2on2/dm4.cfg
+quake/ktx/configs/usermodes/2on2/dm6.cfg
+quake/ktx/configs/usermodes/2on2on2/default.cfg
+quake/ktx/configs/usermodes/3on3/default.cfg
+quake/ktx/configs/usermodes/3on3on3/default.cfg
+quake/ktx/configs/usermodes/4on4/default.cfg
+quake/ktx/configs/usermodes/4on4on4/default.cfg
+quake/ktx/configs/usermodes/XonX/default.cfg
+quake/ktx/configs/usermodes/amphi.cfg
+quake/ktx/configs/usermodes/amphi2.cfg
+quake/ktx/configs/usermodes/aqui.cfg
+quake/ktx/configs/usermodes/arena3.cfg
+quake/ktx/configs/usermodes/ca/default.cfg
+quake/ktx/configs/usermodes/ctf/default.cfg
+quake/ktx/configs/usermodes/ctf/qwq3wcp9.cfg
+quake/ktx/configs/usermodes/default.cfg
+quake/ktx/configs/usermodes/dm2dmm4.cfg
+quake/ktx/configs/usermodes/dm2dmm4_2.cfg
+quake/ktx/configs/usermodes/dmm4_1.cfg
+quake/ktx/configs/usermodes/dmm4_2.cfg
+quake/ktx/configs/usermodes/dmm4_3.cfg
+quake/ktx/configs/usermodes/dmm4_4.cfg
+quake/ktx/configs/usermodes/dmm4_5.cfg
+quake/ktx/configs/usermodes/dmm4_6.cfg
+quake/ktx/configs/usermodes/dmm4_7.cfg
+quake/ktx/configs/usermodes/dmm4_8.cfg
+quake/ktx/configs/usermodes/dmm4_9.cfg
+quake/ktx/configs/usermodes/end.cfg
+@comment quake/ktx/configs/usermodes/end2.cfg
+quake/ktx/configs/usermodes/endif.cfg
+quake/ktx/configs/usermodes/ffa/default.cfg
+quake/ktx/configs/usermodes/hammer.cfg
+quake/ktx/configs/usermodes/hammerv2.cfg
+quake/ktx/configs/usermodes/hammerv3.cfg
+quake/ktx/configs/usermodes/matchless/arena3.cfg
+quake/ktx/configs/usermodes/matchless/arena5.cfg
+quake/ktx/configs/usermodes/matchless/barrel.cfg
+quake/ktx/configs/usermodes/matchless/bloodfest.cfg
+quake/ktx/configs/usermodes/matchless/ctf.cfg
+quake/ktx/configs/usermodes/matchless/default.cfg
+quake/ktx/configs/usermodes/matchless/dm4ish.cfg
+quake/ktx/configs/usermodes/matchless/e1m7.cfg
+quake/ktx/configs/usermodes/matchless/genocide.cfg
+quake/ktx/configs/usermodes/matchless/hohoho.cfg
+quake/ktx/configs/usermodes/matchless/kenya.cfg
+quake/ktx/configs/usermodes/matchless/q1dm17.cfg
+quake/ktx/configs/usermodes/matchless/slaug.cfg
+quake/ktx/configs/usermodes/nacmidair.cfg
+quake/ktx/configs/usermodes/noentry.cfg
+quake/ktx/configs/usermodes/oldcrat.cfg
+quake/ktx/configs/usermodes/outpost.cfg
+quake/ktx/configs/usermodes/povdmm4.cfg
+quake/ktx/configs/usermodes/povdmm4a.cfg
+quake/ktx/configs/usermodes/povdmm4b.cfg
+quake/ktx/configs/usermodes/pushdmm4.cfg
+@comment quake/ktx/configs/usermodes/schlossdmm4.cfg
+quake/ktx/configs/usermodes/sewer.cfg
+quake/ktx/configs/usermodes/tearena.cfg
+@comment quake/ktx/configs/usermodes/tot/dm4.cfg
+@comment quake/ktx/configs/usermodes/tot/e1m2.cfg
+@comment quake/ktx/configs/usermodes/tot/schloss.cfg
+@dir(,%%QWGROUP%%,0775) quake/ktx/demos
+@sample quake/ktx/ktx.cfg.sample
+@sample quake/ktx/listip.cfg.sample
+@sample quake/ktx/matchless.cfg.sample
+@sample quake/ktx/mvdfinish.qws.sample
+@sample quake/ktx/mvdsv.cfg.sample
+@sample quake/ktx/port1.cfg.sample
+@sample quake/ktx/port2.cfg.sample
+@sample quake/ktx/port3.cfg.sample
+@sample quake/ktx/problem.cfg.sample
+quake/ktx/progs/bit.mdl
+quake/ktx/progs/check.mdl
+quake/ktx/progs/finish.mdl
+quake/ktx/progs/flag.mdl
+quake/ktx/progs/spawn.mdl
+quake/ktx/progs/star.mdl
+quake/ktx/progs/start.mdl
+quake/ktx/progs/v_coil.mdl
+quake/ktx/progs/v_star.mdl
+quake/ktx/progs/vwplayer.mdl
+quake/ktx/progs/w_axe.mdl
+quake/ktx/progs/w_coil.mdl
+quake/ktx/progs/w_light.mdl
+quake/ktx/progs/w_nail.mdl
+quake/ktx/progs/w_nail2.mdl
+quake/ktx/progs/w_rock.mdl
+quake/ktx/progs/w_rock2.mdl
+quake/ktx/progs/w_shot.mdl
+quake/ktx/progs/w_shot2.mdl
+@sample(,%%QWGROUP%%,0640) quake/ktx/pwd.cfg.sample
+quake/ktx/qwprogs.so
+@dir(,%%QWGROUP%%,0775) quake/ktx/race
+quake/ktx/race/routes/2bfree.route
+quake/ktx/race/routes/aerowalk.route
+quake/ktx/race/routes/bravado.route
+quake/ktx/race/routes/catalyst.route
+quake/ktx/race/routes/cmt3.route
+quake/ktx/race/routes/dm1.route
+quake/ktx/race/routes/dm2.route
+quake/ktx/race/routes/dm3.route
+quake/ktx/race/routes/dm4.route
+quake/ktx/race/routes/dm5.route
+quake/ktx/race/routes/dm6.route
+quake/ktx/race/routes/dungeonsurf.route
+quake/ktx/race/routes/e1m2.route
+quake/ktx/race/routes/escape2a.route
+quake/ktx/race/routes/hoppa2.route
+quake/ktx/race/routes/jqdf1.route
+quake/ktx/race/routes/jqdf3.route
+quake/ktx/race/routes/monsoon.route
+quake/ktx/race/routes/mvdsv-kg.route
+quake/ktx/race/routes/q1q3toxicity.route
+quake/ktx/race/routes/race1.route
+quake/ktx/race/routes/race10.route
+quake/ktx/race/routes/race2.route
+quake/ktx/race/routes/race3.route
+quake/ktx/race/routes/race4.route
+quake/ktx/race/routes/race5.route
+quake/ktx/race/routes/race6.route
+quake/ktx/race/routes/race7.route
+quake/ktx/race/routes/race8.route
+quake/ktx/race/routes/race9.route
+quake/ktx/race/routes/rampcity.route
+quake/ktx/race/routes/rawspeed.route
+quake/ktx/race/routes/rj3.route
+quake/ktx/race/routes/shifter.route
+quake/ktx/race/routes/slide1.route
+quake/ktx/race/routes/slide2.route
+quake/ktx/race/routes/slide3.route
+quake/ktx/race/routes/slide4.route
+quake/ktx/race/routes/slide5.route
+quake/ktx/race/routes/slide6.route
+quake/ktx/race/routes/slide7.route
+quake/ktx/race/routes/slide8.route
+quake/ktx/race/routes/slide9.route
+quake/ktx/race/routes/slidefox.route
+quake/ktx/race/routes/slstart.route
+quake/ktx/race/routes/speedrush.route
+quake/ktx/race/routes/subslide.route
+quake/ktx/race/routes/toxicity_ql.route
+quake/ktx/race/routes/toxicity_test.route
+quake/ktx/race/routes/toxicity_test2.route
+quake/ktx/race/routes/zjumps.route
+quake/ktx/race/routes/ztndm3.route
+quake/ktx/race/routes/ztricks.route
+quake/ktx/race/routes/ztricks2.route
+@sample quake/ktx/server.cfg.sample
+quake/ktx/sound/misc/flagcap.wav
+quake/ktx/sound/misc/flagtk.wav
+quake/ktx/sound/ra/1.wav
+quake/ktx/sound/ra/2.wav
+quake/ktx/sound/ra/3.wav
+quake/ktx/sound/ra/excelent.wav
+quake/ktx/sound/ra/fight.wav
+quake/ktx/sound/ra/flawless.wav
+quake/ktx/sound/rune/rune1.wav
+quake/ktx/sound/rune/rune2.wav
+quake/ktx/sound/rune/rune22.wav
+quake/ktx/sound/rune/rune3.wav
+quake/ktx/sound/rune/rune4.wav
+quake/ktx/sound/weapons/bounce2.wav
+quake/ktx/sound/weapons/chain1.wav
+quake/ktx/sound/weapons/chain2.wav
+quake/ktx/sound/weapons/chain3.wav
+quake/ktx/sound/weapons/coilgun.wav
+@sample quake/ktx/vip_ip.cfg.sample
+@sample quake/port1.sample
+@sample quake/port2.sample
+@sample quake/port3.sample
+@sample quake/portffa.sample
+@sample quake/servers.sample
+quake/qw/maps/arena3.ent
+quake/qw/maps/arena5.ent
+quake/qw/maps/barrel.ent
+quake/qw/maps/bloodfest.ent
+quake/qw/maps/ctf/dm1.ent
+quake/qw/maps/ctf/dm2.ent
+quake/qw/maps/ctf/dm3.ent
+quake/qw/maps/ctf/dm4.ent
+quake/qw/maps/ctf/dm5.ent
+quake/qw/maps/ctf/dm6.ent
+quake/qw/maps/ctf/e1m1.ent
+quake/qw/maps/ctf/e1m2.ent
+quake/qw/maps/ctf/e1m3.ent
+quake/qw/maps/ctf/e1m4.ent
+quake/qw/maps/ctf/e1m5.ent
+quake/qw/maps/ctf/e1m6.ent
+quake/qw/maps/ctf/e1m7.ent
+quake/qw/maps/ctf/e1m8.ent
+quake/qw/maps/ctf/e2m1.ent
+quake/qw/maps/ctf/e2m2.ent
+quake/qw/maps/ctf/e2m3.ent
+quake/qw/maps/ctf/e2m4.ent
+quake/qw/maps/ctf/e2m5.ent
+quake/qw/maps/ctf/e2m6.ent
+quake/qw/maps/ctf/e2m7.ent
+quake/qw/maps/ctf/e3m1.ent
+quake/qw/maps/ctf/e3m2.ent
+quake/qw/maps/ctf/e3m3.ent
+quake/qw/maps/ctf/e3m4.ent
+quake/qw/maps/ctf/e3m5.ent
+quake/qw/maps/ctf/e3m6.ent
+quake/qw/maps/ctf/e3m7.ent
+quake/qw/maps/ctf/e4m1.ent
+quake/qw/maps/ctf/e4m2.ent
+quake/qw/maps/ctf/e4m3.ent
+quake/qw/maps/ctf/e4m4.ent
+quake/qw/maps/ctf/e4m5.ent
+quake/qw/maps/ctf/e4m6.ent
+quake/qw/maps/ctf/e4m7.ent
+quake/qw/maps/ctf/e4m8.ent
+quake/qw/maps/ctf/fcast.ent
+quake/qw/maps/death6.ent
+quake/qw/maps/dm4ish.ent
+quake/qw/maps/e1m7.ent
+quake/qw/maps/fragyard.ent
+quake/qw/maps/genocide.ent
+quake/qw/maps/hohoho.ent
+quake/qw/maps/kenya.ent
+quake/qw/maps/pillar.ent
+quake/qw/maps/q1dm17.ent
+quake/qw/maps/rz1pondb.ent
+quake/qw/maps/slaug.ent