diff --git a/luascripts/wolfadmin/commands/admin/fling.lua b/luascripts/wolfadmin/commands/admin/fling.lua
new file mode 100644
index 0000000..76268c8
--- /dev/null
+++ b/luascripts/wolfadmin/commands/admin/fling.lua
@@ -0,0 +1,80 @@
+
+-- WolfAdmin module for Wolfenstein: Enemy Territory servers.
+-- Copyright (C) 2015-2019 Timo 'Timothy' Smit
+
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- at your option any later version.
+
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+-- GNU General Public License for more details.
+
+-- You should have received a copy of the GNU General Public License
+-- along with this program. If not, see .
+
+local auth = wolfa_requireModule("auth.auth")
+
+local commands = wolfa_requireModule("commands.commands")
+
+local players = wolfa_requireModule("players.players")
+
+local constants = wolfa_requireModule("util.constants")
+local settings = wolfa_requireModule("util.settings")
+local vectors = wolfa_requireModule("util.vectors")
+
+function commandFling(clientId, command, victim)
+ local cmdClient
+
+ if victim == nil then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dfling usage: "..commands.getadmin("fling")["syntax"].."\";")
+
+ return true
+ elseif tonumber(victim) == nil or tonumber(victim) < 0 or tonumber(victim) > tonumber(et.trap_Cvar_Get("sv_maxclients")) then
+ cmdClient = et.ClientNumberFromString(victim)
+ else
+ cmdClient = tonumber(victim)
+ end
+
+ if cmdClient == -1 or cmdClient == nil then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dfling: ^9no or multiple matches for '^7"..victim.."^9'.\";")
+
+ return true
+ elseif not et.gentity_get(cmdClient, "pers.netname") then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dfling: ^9no connected player by that name or slot #\";")
+
+ return true
+ end
+
+ if auth.isPlayerAllowed(cmdClient, "!") then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dfling: ^7"..et.gentity_get(cmdClient, "pers.netname").." ^9is immune to this command.\";")
+
+ return true
+ elseif auth.getPlayerLevel(cmdClient) > auth.getPlayerLevel(clientId) then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dfling: ^9sorry, but your intended victim has a higher admin level than you do.\";")
+
+ return true
+ elseif et.gentity_get(cmdClient, "sess.sessionTeam") ~= constants.TEAM_AXIS and et.gentity_get(cmdClient, "sess.sessionTeam") ~= constants.TEAM_ALLIES then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dfling: ^7"..et.gentity_get(cmdClient, "pers.netname").." ^9is not playing.\";")
+
+ return true
+ elseif et.gentity_get(cmdClient, "health") <= 0 then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dfling: ^7"..et.gentity_get(cmdClient, "pers.netname").." ^9is not alive.\";")
+
+ return true
+ end
+
+ -- set the seed
+ math.randomseed(et.trap_Milliseconds())
+
+ local velocity = et.gentity_get(cmdClient, "ps.velocity")
+ local modifier = vectors.scale({ math.random(-50, 50), math.random(-50, 50), math.random(0, 5) }, 1500)
+ et.gentity_set(cmdClient, "ps.velocity", vectors.add(velocity, modifier))
+
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "cchat -1 \"^dfling: ^7"..players.getName(cmdClient).." ^9was flung.\";")
+
+ return true
+end
+commands.addadmin("fling", commandFling, auth.PERM_THROW, "flings a player in a random direction", "^9[^3name|slot#^9]", nil, (settings.get("g_standalone") == 0))
diff --git a/luascripts/wolfadmin/commands/admin/launch.lua b/luascripts/wolfadmin/commands/admin/launch.lua
new file mode 100644
index 0000000..6391e32
--- /dev/null
+++ b/luascripts/wolfadmin/commands/admin/launch.lua
@@ -0,0 +1,77 @@
+
+-- WolfAdmin module for Wolfenstein: Enemy Territory servers.
+-- Copyright (C) 2015-2019 Timo 'Timothy' Smit
+
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- at your option any later version.
+
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+-- GNU General Public License for more details.
+
+-- You should have received a copy of the GNU General Public License
+-- along with this program. If not, see .
+
+local auth = wolfa_requireModule("auth.auth")
+
+local commands = wolfa_requireModule("commands.commands")
+
+local players = wolfa_requireModule("players.players")
+
+local constants = wolfa_requireModule("util.constants")
+local settings = wolfa_requireModule("util.settings")
+local vectors = wolfa_requireModule("util.vectors")
+
+function commandLaunch(clientId, command, victim)
+ local cmdClient
+
+ if victim == nil then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dlaunch usage: "..commands.getadmin("launch")["syntax"].."\";")
+
+ return true
+ elseif tonumber(victim) == nil or tonumber(victim) < 0 or tonumber(victim) > tonumber(et.trap_Cvar_Get("sv_maxclients")) then
+ cmdClient = et.ClientNumberFromString(victim)
+ else
+ cmdClient = tonumber(victim)
+ end
+
+ if cmdClient == -1 or cmdClient == nil then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dlaunch: ^9no or multiple matches for '^7"..victim.."^9'.\";")
+
+ return true
+ elseif not et.gentity_get(cmdClient, "pers.netname") then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dlaunch: ^9no connected player by that name or slot #\";")
+
+ return true
+ end
+
+ if auth.isPlayerAllowed(cmdClient, "!") then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dlaunch: ^7"..et.gentity_get(cmdClient, "pers.netname").." ^9is immune to this command.\";")
+
+ return true
+ elseif auth.getPlayerLevel(cmdClient) > auth.getPlayerLevel(clientId) then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dlaunch: ^9sorry, but your intended victim has a higher admin level than you do.\";")
+
+ return true
+ elseif et.gentity_get(cmdClient, "sess.sessionTeam") ~= constants.TEAM_AXIS and et.gentity_get(cmdClient, "sess.sessionTeam") ~= constants.TEAM_ALLIES then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dlaunch: ^7"..et.gentity_get(cmdClient, "pers.netname").." ^9is not playing.\";")
+
+ return true
+ elseif et.gentity_get(cmdClient, "health") <= 0 then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dlaunch: ^7"..et.gentity_get(cmdClient, "pers.netname").." ^9is not alive.\";")
+
+ return true
+ end
+
+ local velocity = et.gentity_get(cmdClient, "ps.velocity")
+ local modifier = vectors.scale({0, 0, 10}, 1500)
+ et.gentity_set(cmdClient, "ps.velocity", vectors.add(velocity, modifier))
+
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "cchat -1 \"^dlaunch: ^7"..players.getName(cmdClient).." ^9was launched.\";")
+
+ return true
+end
+commands.addadmin("launch", commandLaunch, auth.PERM_THROW, "launch a player vertically", "^9[^3name|slot#^9]", nil, (settings.get("g_standalone") == 0))
diff --git a/luascripts/wolfadmin/commands/admin/throw.lua b/luascripts/wolfadmin/commands/admin/throw.lua
new file mode 100644
index 0000000..fd2ab59
--- /dev/null
+++ b/luascripts/wolfadmin/commands/admin/throw.lua
@@ -0,0 +1,80 @@
+
+-- WolfAdmin module for Wolfenstein: Enemy Territory servers.
+-- Copyright (C) 2015-2019 Timo 'Timothy' Smit
+
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- at your option any later version.
+
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+-- GNU General Public License for more details.
+
+-- You should have received a copy of the GNU General Public License
+-- along with this program. If not, see .
+
+local auth = wolfa_requireModule("auth.auth")
+
+local commands = wolfa_requireModule("commands.commands")
+
+local players = wolfa_requireModule("players.players")
+
+local constants = wolfa_requireModule("util.constants")
+local settings = wolfa_requireModule("util.settings")
+local vectors = wolfa_requireModule("util.vectors")
+
+function commandThrow(clientId, command, victim)
+ local cmdClient
+
+ if victim == nil then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dthrow usage: "..commands.getadmin("throw")["syntax"].."\";")
+
+ return true
+ elseif tonumber(victim) == nil or tonumber(victim) < 0 or tonumber(victim) > tonumber(et.trap_Cvar_Get("sv_maxclients")) then
+ cmdClient = et.ClientNumberFromString(victim)
+ else
+ cmdClient = tonumber(victim)
+ end
+
+ if cmdClient == -1 or cmdClient == nil then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dthrow: ^9no or multiple matches for '^7"..victim.."^9'.\";")
+
+ return true
+ elseif not et.gentity_get(cmdClient, "pers.netname") then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dthrow: ^9no connected player by that name or slot #\";")
+
+ return true
+ end
+
+ if auth.isPlayerAllowed(cmdClient, "!") then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dthrow: ^7"..et.gentity_get(cmdClient, "pers.netname").." ^9is immune to this command.\";")
+
+ return true
+ elseif auth.getPlayerLevel(cmdClient) > auth.getPlayerLevel(clientId) then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dthrow: ^9sorry, but your intended victim has a higher admin level than you do.\";")
+
+ return true
+ elseif et.gentity_get(cmdClient, "sess.sessionTeam") ~= constants.TEAM_AXIS and et.gentity_get(cmdClient, "sess.sessionTeam") ~= constants.TEAM_ALLIES then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dthrow: ^7"..et.gentity_get(cmdClient, "pers.netname").." ^9is not playing.\";")
+
+ return true
+ elseif et.gentity_get(cmdClient, "health") <= 0 then
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "csay "..clientId.." \"^dthrow: ^7"..et.gentity_get(cmdClient, "pers.netname").." ^9is not alive.\";")
+
+ return true
+ end
+
+ local velocity = et.gentity_get(cmdClient, "ps.velocity")
+ local viewAngles = et.gentity_get(cmdClient, "ps.viewangles")
+ local dir = vectors.angle(viewAngles)
+ dir[3] = 0.25
+ local modifier = vectors.scale(dir, 1500)
+ et.gentity_set(cmdClient, "ps.velocity", vectors.add(velocity, modifier))
+
+ et.trap_SendConsoleCommand(et.EXEC_APPEND, "cchat -1 \"^dthrow: ^7"..players.getName(cmdClient).." ^9was thrown.\";")
+
+ return true
+end
+commands.addadmin("throw", commandThrow, auth.PERM_THROW, "throws a player forward", "^9[^3name|slot#^9]", nil, (settings.get("g_standalone") == 0))
diff --git a/luascripts/wolfadmin/main.lua b/luascripts/wolfadmin/main.lua
index 4fa2123..dc5dd29 100644
--- a/luascripts/wolfadmin/main.lua
+++ b/luascripts/wolfadmin/main.lua
@@ -49,6 +49,7 @@ local pagination
local settings
local tables
local timers
+local vectors
local util
local version = "1.3.0-dev"
@@ -140,6 +141,7 @@ function et_InitGame(levelTime, randomSeed, restartMap)
settings = wolfa_requireModule("util.settings")
tables = wolfa_requireModule("util.tables")
timers = wolfa_requireModule("util.timers")
+ vectors = wolfa_requireModule("util.vectors")
util = wolfa_requireModule("util.util")
-- register the module
diff --git a/luascripts/wolfadmin/util/vectors.lua b/luascripts/wolfadmin/util/vectors.lua
new file mode 100644
index 0000000..8de5e51
--- /dev/null
+++ b/luascripts/wolfadmin/util/vectors.lua
@@ -0,0 +1,60 @@
+
+-- WolfAdmin module for Wolfenstein: Enemy Territory servers.
+-- Copyright (C) 2015-2019 Timo 'Timothy' Smit
+
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- at your option any later version.
+
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+-- GNU General Public License for more details.
+
+-- You should have received a copy of the GNU General Public License
+-- along with this program. If not, see .
+
+local vectors = {}
+
+function vectors.add(vector1, vector2)
+ return { vector1[1] + vector2[1], vector1[2] + vector2[2], vector1[3] + vector2[3] }
+end
+
+function vectors.scale(vector, factor)
+ return { vector[1] * factor, vector[2] * factor, vector[3] * factor }
+end
+
+function vectors.angle(angles)
+ local angle;
+ local sr, sp, sy, cr, cp, cy;
+ local forward, right, up = {}, {}, {}
+
+ angle = angles[2] * ((math.pi*2) / 360);
+ sy = math.sin(angle);
+ cy = math.cos(angle);
+
+ angle = angles[1] * ((math.pi*2) / 360);
+ sp = math.sin(angle);
+ cp = math.cos(angle);
+
+ angle = angles[3] * ((math.pi*2) / 360);
+ sr = math.sin(angle);
+ cr = math.cos(angle);
+
+ forward[1] = cp * cy;
+ forward[2] = cp * sy;
+ forward[3] = -sp;
+
+ right[1] = (-1 * sr * sp * cy + -1 * cr * -sy);
+ right[2] = (-1 * sr * sp * sy + -1 * cr * cy);
+ right[3] = -1 * sr * cp;
+
+ up[1] = (cr * sp * cy + -sr * -sy);
+ up[2] = (cr * sp * sy + -sr * cy);
+ up[3] = cr * cp;
+
+ return forward, right, up
+end
+
+return vectors