ETc|Custom is a modular Lua framework for Wolfenstein: Enemy Territory running on N!tmod. It extends the stock gameplay and administration with quality-of-life features and guard rails for public clan servers. You are allowed to remove the etc from all files. You can modify what you want. You even can tell others its yours.
Main Feature Blocks
- Custom health, regeneration and ammo for human players.
- Time-based bot control with persistent on/off state.
- Public rules + internal member rules with persistent acceptance tracking.
- XP control tools:
/addxpand/setxpfor allowed admins. - Configurable ClanTag protection (rename or kick tag stealers).
- Admin password / login layer on top of dangerous shrubbot commands.
- Ingame donation list via
/donate. - Modular help system:
/cmds+/h <command>. - Central debug and command logging, viewable ingame by
/logs.
Goals
- Keep Lua code modular and easy to extend.
- Make rule enforcement automatic and persistent via flat-files.
- Improve security for critical admin commands via
/etclogin. - Remain compatible with typical N!tmod ET public servers.
- Wolfenstein: Enemy Territory (ET 2.60b or ETLegacy recommended).
- N!tmod with Lua support enabled.
- File system write access to:
nitmod/luas/etc_custom/nitmod/luas/etc_custom/logs/nitmod/luas/etc_custom/db/
3.1 Directory layout
<fs_homepath>/
nitmod/
luas/
etc_custom/
mod_main.lua (entry point)
config.lua (central configuration)
core.lua (core hooks and shared state)
regen.lua (health regeneration)
commands.lua (toggle commands & public commands)
rules.lua (rules & acceptance system)
botcontrol.lua (time-based bot control)
xp.lua (XP tools /addxp /setxp)
help.lua (help system /cmds /h)
tag.lua (ClanTag protection)
rconpm.lua (RCON private messages: m <slot|name> <msg>)
logs.lua (/logs viewer for debug.log & commands.log)
donate.lua (/donate โ ingame donation list)
adminpassword.lua (/etclogin, /etclogout & command protection)
db/
etcrules.db (rule acceptance tracking)
botcontrol.db (timed botcontrol on/off state)
adminlogin.db (persistent AdminPassword login state)
logs/
debug.log (debug messages)
commands.log (command usage + important actions)
donation_list.txt (data source for /donate)
3.2 Loading via N!tmod
Add the Lua entry point to your N!tmod configuration, for example in
nitmod.cfg or your main server.cfg:
// Load ETc|Custom Lua
set lua_modules "luas/etc_custom/mod_main.lua"
On server start you should see a console line similar to:
[ETc|Custom] Version: 1.4 Loaded
config.lua is where you configure nearly everything:
logging behaviour, health & ammo values, rule levels, bot schedule,
ClanTag protection and admin passwords.
4.1 Logging configuration
Modname = "ETc|Custom"
Version = "1.4"
ENABLE_DEBUG_LOG = false -- set to false in production
ENABLE_COMMANDS_LOG = true
LOG_DIR_MODE = "keep" -- reserved for log maintenance strategies
- debug.log receives internal messages via
logDebug(). - commands.log receives structured admin actions via
logCommand().
4.2 Health, regen & ammo
et.MAX_HEALTH = 175 -- custom max health on spawn
et.REGEN_AMOUNT = 2 -- HP per regen tick
et.REGEN_INTERVAL = 1000 -- milliseconds between ticks
et.REGEN_START = 156 -- minimum health where regen starts
et.COMBAT_COOLDOWN = 5000 -- ms without combat before regen
et.MAX_AMMO_MP40 = 500
et.MAX_AMMO_THOMPSON = 500
These values are only active if the corresponding CVars are enabled:
etc_customhealth and etc_regen for health-related features,
etc_customammo for ammo.
4.3 Rules & member levels
-- Level range that counts as "member"
et.minLvL = 7
et.maxLvL = 999
-- Admin levels allowed to use admin commands
allowedAdminLevels = {
[10] = true,
[11] = true,
[999] = true
}
Members within [minLvL, maxLvL] are required to:
- Read member rules via
/etcrules. - Confirm them via
/accept. - Only then they can join a team (otherwise forced to spectator).
All rule acceptance data is stored in:
rulesDBPath = "nitmod/luas/etc_custom/db/etcrules.db"
4.4 Bot schedule
BotSchedule = {
{ from = 0, to = 2, bots = 8 },
{ from = 2, to = 4, bots = 32 },
{ from = 4, to = 8, bots = 30 },
{ from = 8, to = 10, bots = 20 },
{ from = 10, to = 18, bots = 14 },
{ from = 18, to = 24, bots = 8 }
}
DefaultBotCount = 8
botControlFile = "nitmod/luas/etc_custom/db/botcontrol.db"
The schedule uses the serverโs local time (0โ23 hours). When
BotControlEnabled is on, the script regularly sets:
bot maxbots <bots> based on this table.
4.5 ClanTag protection
ClanTagProtection = {
enabled = true,
tags = { "ETc|", "ETo|", "ETe|" },
minLevel = 7, -- exempt players with level >= minLevel
maxWarnings = 2,
interval = 120, -- seconds between checks
mode = "rename", -- or "kick"
warningMessage = "^1Warning:^7 Please remove the protected clantag: %s",
renameNotice = "^3You have been renamed for using protected clantag. %s",
kickMessage = "Protected clantag %s used. You have been kicked."
}
See section 9 for detailed behaviour.
4.6 Admin password configuration
AdminPassword = {
passwords = {
[0] = "password1",
[1] = "password2",
[2] = "password3",
[3] = "password4",
[4] = "password5",
[5] = "password6",
[6] = "password7",
[7] = "password8",
[8] = "password9",
[9] = "password10",
[10] = "password11",
[11] = "password12",
[999] = "password13"
},
authTimeout = 0, -- 0 = no timeout (active until disconnect or /etclogout)
blockAfter = 3,
blockTime = 300,
kickAfter = 10,
protectedCmds = {
["setlevel"] = true,
["setleve"] = true,
["setlev"] = true,
["setle"] = true,
["setl"] = true,
["ban"] = true,
["unban"] = true,
["unba"] = true,
["resetxp"] = true,
["resetx"] = true,
["reset"] = true,
["levlist"] = true,
["levlis"] = true,
["levli"] = true,
["levl"] = true,
["levinfo"] = true,
["levinf"] = true,
["levin"] = true,
["levi"] = true,
["levedit"] = true,
["levedi"] = true,
["leved"] = true,
["leve"] = true,
["banguid"] = true,
["bangui"] = true,
["bangu"] = true,
["bang"] = true,
["useredit"] = true,
["useredi"] = true,
["usered"] = true,
["usere"] = true,
["levadd"] = true,
["levad"] = true,
["leva"] = true,
["levdelete"] = true,
["levdelet"] = true,
["levdele"] = true,
["levdel"] = true,
["levde"] = true,
["levd"] = true,
["userdelete"] = true,
["userdelet"] = true,
["userdele"] = true,
["userdel"] = true,
["userde"] = true,
["userd"] = true,
["dbsave"] = true,
["dbsav"] = true,
["dbsa"] = true,
["dbs"] = true,
["db"] = true,
["delrecords"] = true,
["delrecord"] = true,
["delrecor"] = true,
["delreco"] = true,
["delrec"] = true,
["delre"] = true,
["delr"] = true,
["del"] = true,
["del_bot_xp"] = true,
["del_bot_x"] = true,
["del_bot_"] = true,
["del_bot"] = true,
["del_bo"] = true,
["del_b"] = true,
["del_"] = true,
["rcon"] = true,
["rco"] = true,
["rc"] = true,
["devmap"] = true,
["devma"] = true,
["devm"] = true,
["dev"] = true,
["readconfig"] = true,
["readconfi"] = true,
["readconf"] = true,
["readcon"] = true,
["readco"] = true,
["readc"] = true,
["read"] = true,
["rea"] = true
},
sayOnlyCmds = {
["levinfo"] = true,
["levinf"] = true,
["levin"] = true,
["levi"] = true,
["levlist"] = true,
["levlis"] = true,
["levli"] = true,
["levl"] = true
}
}
Protected commands require a valid /etclogin <password>
and the correct level password. Based on authTimeout, the login
can persist until disconnect or expire automatically. Persistent login state
is stored in adminlogin.db.
4.7 Persistent feature CVars (server.cfg)
To keep the feature toggles persistent across server restarts, set the following CVars
in your main server.cfg (or any config that is executed on every boot):
// ETc|Custom persistent feature toggles
set etc_customhealth "1" // 1 = enable custom spawn health
set etc_customammo "1" // 1 = enable custom ammo
set etc_regen "1" // 1 = enable health regeneration
set etc_rules "1" // 1 = enable /rules
set etc_website "1" // 1 = enable /website
set etc_etcrules "1" // 1 = enable /etcrules (member rules)
set etc_notifyrules "1" // 1 = enable member rule reminder on join
The mod reads these CVars on start inside mod_main.lua, applies them to the
internal flags (USE_CUSTOM_HEALTH, USE_CUSTOM_AMMO, etc.) and
writes them back once to ensure they are defined.
core.lua provides the central ET hook implementation and shared state.
regen.lua attaches regeneration and combat delay logic on top.
5.1 Shared state
lastRegen[client]โ last regen tick time (ms).lastCombat[client]โ last damage time (ms) as attacker.clientIsBot[client]โ flag to exclude bots from custom logic.rulesNotified[client]โ whether a member has seen the reminder.
5.2 Member rule notification
In Core_ClientBegin, for non-bots in the member level range:
- Show center-print reminder to read
/etcrules. - Show console text with the same information.
- Only once per map per player (tracked via
rulesNotified).
5.3 Custom spawn health and ammo
In Core_ClientSpawn (non-bots):
- If
USE_CUSTOM_HEALTH:- Health is set to
et.MAX_HEALTH.
- Health is set to
- If
USE_CUSTOM_AMMO:- Ammo for MP40/Thompson is set according to config values.
- Some static ammo tweaks are applied (e.g. grenades / specific slots).
5.4 Regeneration logic (regen.lua)
Regen_RunFrame(levelTime):
- Skips regeneration if
ENABLE_REGENis false. - For each non-bot, non-spectator:
- If time since last regen โฅ
REGEN_INTERVALand - time since last combat โฅ
COMBAT_COOLDOWNand REGEN_START โค health < MAX_HEALTHโ addREGEN_AMOUNTHP.
- If time since last regen โฅ
Regen_ClientDamage(taker, attacker):
- Updates
lastCombat[attacker]for valid player indices. - Used to block regen while the player is actively in combat.
The rules system enforces a โread and accept firstโ flow for members before
they can join a team. It uses a persistent flat-file database:
etcrules.db.
6.1 Database format
<NGUID>|<PlayerName>|<flag>
flag = 0 -> rules viewed, not accepted
flag = 1 -> rules fully accepted
On Rules_InitGame, all entries are loaded into:
acceptedRead[nguid]โ player has viewed /etcrules at least once.acceptedPlayers[nguid]โ player has accepted the rules.
6.2 Blocking team join
Rules_ClientUserinfoChanged(clientNum):
- Checks shrubbot level. If within
[et.minLvL, et.maxLvL]: - If rules are not both read and accepted:
- Force the client to spectator (team = 3).
- Show center-print explaining they must run
/etcrulesand/accept.
6.3 Player commands
/etcrules
Show member rules & mark as read
- Only available to players in the member level range.
- Prints all member rules from
etcrulestable. -
Marks
acceptedRead[nguid] = trueand writes an entry toetcrules.dbif none existed (flag 0).
/accept
Confirm member rules
- Only available to member level range.
- Requires the player to have run
/etcrulesfirst. - Updates
etcrules.dbwith flag1for their NGUID. - Sets
acceptedPlayers[nguid] = true. - If already accepted, the player is informed.
/rules
Public server rules
- Available to everybody if
USE_RULESis enabled. - Prints the general rule set from the
rulestable.
6.4 Admin rule tools
All restricted to allowedAdminLevels:
- /addaccept <player> โ mark player as accepted.
- Target can be slot, name fragment or DB-backed name fragment.
- No argument โ applies to yourself.
- If already accepted, the admin is informed and no duplicate entry is created.
- /forgetaccept <player> โ reset acceptance.
- Clears flags and sets database entry to
flag = 0if present. - If there is no entry, or already reset, a message explains this.
- Clears flags and sets database entry to
- /checkaccept <player> โ status overview.
- Works on online players and DB-only entries.
- Shows
Read: yes/noandAccepted: yes/no. - No argument โ checks your own status.
7.1 Feature toggles (admin)
All restricted to allowedAdminLevels:
/customhealth
Toggle custom spawn health
- Toggles CVar
etc_customhealth. - Controls whether
et.MAX_HEALTHis applied to human players on spawn. - State persists via server CVars.
/customammo
Toggle custom ammo
- Toggles CVar
etc_customammo. - Controls whether configured ammo values are applied on spawn.
/regen
Toggle health regeneration
- Toggles CVar
etc_regen. - Enables/disables the regeneration logic in
regen.lua.
/notifierules
Toggle member rule reminders
- Toggles CVar
etc_notifyrules. - Controls whether members get a reminder to read
/etcruleswhen joining.
7.2 XP Tools
- /addxp <id|name> <amount>
- Adds XP across all 7 skills.
- Amount is distributed evenly, remainder spread over first skills.
- Valid range: 1โ5,000,000.
- /setxp <id|name> <amount>
- Sets total XP across all 7 skills to the given amount.
- Existing XP is overwritten (mode = โsetโ).
7.3 Log viewer
The /logs command (admin only) allows viewing the debug.log and
commands.log from inside the game.
/logs [debug|commands] [1-30]
- No first argument โ show from both logs.
debugโ only debug.log.commandsโ only commands.log.- Line count defaults to 5, max 30.
Use /h logs ingame for an explanation targeted at players/admins.
The bot control module dynamically adapts bot maxbots based on the time of day.
This helps keep the server active when population is low, and less crowded when real players
are online.
8.1 How it works
- On
et_InitGame:- Reads
botcontrol.dbto know if BotControl is enabled. - If disabled, applies
DefaultBotCountonce.
- Reads
- In
BotControl_RunFrame:- Every 60 seconds:
- Read current hour (
os.date("%H")). - Find matching schedule entry.
- Execute
bot maxbots <bots>.
- Read current hour (
- Every 60 seconds:
8.2 Admin toggle: /botcontrol
/botcontrol (admin only) toggles the entire system on/off and stores the setting
in botcontrol.db (1/0).
The ClanTag protection module prevents non-members from using protected tags like
ETc|, even when combined with color codes.
9.1 Detection
- Runs every
ClanTagProtection.intervalseconds. - Skips players with shrubbot level โฅ
minLevel. - For others:
- Strips ET color codes and checks if the plain name contains one of the tags.
9.2 Warnings & actions
- First
maxWarningsdetections:- Show a center warning using
warningMessage. - Play a referee sound.
- Show a center warning using
- After that:
- If
mode = "rename":- Remove only the tag portion from the visible name (keeping colors).
- Apply the new name and show the
renameNotice.
- If
mode = "kick":- Kick the player with a message based on
kickMessage.
- Kick the player with a message based on
- If
The AdminPassword module introduces an additional login layer above shrubbot, focusing on
high-risk commands such as !setlevel, !ban, !unban,
!rcon, and similar.
10.1 Login & logout
- /etclogin <password>
- Checks if the player has a valid NGUID and level.
- Looks up the expected password for that level.
- On match:
- Marks the user as logged in (runtime + in
adminlogin.db). - Resets failed attempt counters.
- Informs the user that login was successful.
- If already logged in, the user is informed instead of silently re-logging.
- Marks the user as logged in (runtime + in
- /etclogout
- Clears login state for this player/NGUID (runtime and in
adminlogin.db). - Protected commands will again be blocked until next login.
- Clears login state for this player/NGUID (runtime and in
10.2 Protected shrubbot commands
When a player types a command like !setlevel in chat, adminpassword.lua:
- Checks if the command name is listed in
protectedCmds. - If not logged in:
- Shows a warning that
/etcloginis required. - Does not execute the command.
- Shows a warning that
- If logged in:
- Rebuilds the full chat command including arguments.
- Executes it server-side.
- Confirms execution in a private chat to the admin.
10.3 Blocking & kicks on wrong passwords
- After
blockAfterfailed attempts in a row:- Player is temporarily blocked for
blockTimeseconds.
- Player is temporarily blocked for
- After
kickAftertotal failed attempts:- Player is kicked from the server.
- All attempts are logged with name, IP, MAC (if available) and NGUID.
10.4 Chat masking
To prevent accidental exposure of level passwords:
AdminPassword_ClientSayscans say messages for:/etclogin <password>patterns.- Known password strings from
AdminPassword.passwords.
- Any such occurrence is replaced with
******before storing/logging.
10.5 Why this protection really matters
The AdminPassword module adds a second factor: even if someone steals the n_key,
they still need the level-specific password that only trusted admins know. Without this extra password, all protected commands are blocked.
10.6 adminlogin.db โ persistent login state
To make sure that login state survives map changes, the module stores authenticated admins
in adminlogin.db. This file links NGUID and level with a simple โlogged inโ
flag and optional timestamps. When the map restarts, the module reloads the file, so admins
do not have to re-enter their password every round (unless they explicitly use
/etclogout, or you change the configuration).
The help system exposes a simple, discoverable interface so players and admins can look up commands ingame without external documentation.
11.1 /cmds โ list available commands
- Lists only commands relevant to the current player:
- Public Commands โ everyone sees these.
- Member Commands โ only for
et.minLvL..et.maxLvL. - Admin Commands โ only for
allowedAdminLevels.
- Each entry shows a short description (e.g.
/rules โ Show general rules).
11.2 /h <command> โ detailed help
- Example:
/h addxp,/h logs,/h etclogin. - Shows a longer usage block, including:
- Parameters.
- Examples.
- Notes and access requirements.
- Respects access:
- If a player has no access to a command, help is also denied.
/donate gives players a quick overview of recent donations as a text-only list,
suitable for console output.
12.1 Data source
- Reads
donation_list.txtfrom theetc_customdirectory. - Typical line format:
15 EUR SomeNickname 11/24 50 EUR AnotherDonor 12/03 - The script:
- Parses amount, name and date.
- Prints them in responsive columns with ET color codes.
12.2 Output structure
- ASCII ETc| logo header.
- A progress bar based on fixed
current/goalvalues (adjust in code). - Formatted donation rows per line.
rconpm.lua adds a tiny helper for server operators to send private messages
to players via the console or RCON.
13.1 Usage
m <slot|name> <message>
- slot โ numeric client slot ID.
- name โ name fragment (color codes stripped for matching).
If a slot is valid and in use, the message is sent only to that player. If a name fragment is given:
- All matching players are listed in the server console.
- The message is sent to each of them.
Messages appear in the recipient chat as:
^1[^7Rcon PM^1]^7:^O <message>
- Disable debug logging on live servers
- Set
ENABLE_DEBUG_LOG = falseto reduce disk I/O. - Enable it temporarily when debugging specific issues.
- Set
- Use /logs wisely
- Check
debug.logduring development or when something behaves oddly. - Inspect
commands.logto review admin activity and security-related events.
- Check
- Keep ClanTagProtection in rename mode first
- Start with
mode = "rename"to avoid aggressive kicks. - Switch to
"kick"only if you really need that behaviour.
- Start with
- Maintain strong admin passwords
- Use unique, non-trivial passwords per level.
- Only share them via secure, private channels (e.g. Discord DMs).
- Backup your flat-file DBs regularly
etcrules.dbโ member rule acceptance history.botcontrol.dbโ state of the timed bot system.adminlogin.dbโ persistent login states.donation_list.txtโ donors and amounts.
- Combine with your website / Discord
- Link
/websiteand/donatetext to your existing web pages. - Use external scripts (PHP/Python) to update
donation_list.txt.
- Link
config.lua and
flat-files, so you always know exactly what the server is doing.
Quick overview of all commands provided or modified by the mod. Access is still restricted by shrubbot levels and configuration (see sections above).
/rules
Public
All players
Show general server rules.
/website
Public
All players
Show server website / Discord info.
/donate
Public
All players
Show ingame donation list from donation_list.txt.
/etcrules
Member
Level 7โ999
Display internal member rules and mark them as โreadโ.
/accept
Member
Level 7โ999
Confirm member rules; required before joining a team as member.
/cmds
Helper
All players
List commands available to your level (public/member/admin separated).
/h <command>
Helper
All players
Show detailed help for a command (usage, examples, notes).
/customhealth
Admin toggle
Allowed levels
Toggle custom spawn health (CVar etc_customhealth).
/customammo
Admin toggle
Allowed levels
Toggle custom ammo on spawn (CVar etc_customammo).
/regen
Admin toggle
Allowed levels
Toggle health regeneration (CVar etc_regen).
/notifierules
Admin toggle
Allowed levels
Toggle member rule reminder messages (CVar etc_notifyrules).
/botcontrol
Admin toggle
Allowed levels
Enable/disable time-based bot system (stored in botcontrol.db).
/addaccept [<player>]
Admin
Allowed levels
Mark a player (or yourself) as rules-accepted in etcrules.db.
/forgetaccept [<player>]
Admin
Allowed levels
Reset rule acceptance state for a player.
/checkaccept [<player>]
Admin
Allowed levels
Show read/accept status for a player (online or DB-only).
/addxp <id|name> <amount>
Admin
Allowed levels
Add XP across all 7 skills (1โ5,000,000, no bots, no higher/equal level).
/setxp <id|name> <amount>
Admin
Allowed levels
Set total XP across all 7 skills to amount (1โ5,000,000).
/logs [debug|commands] [1โ30]
Admin
Allowed levels
Show last lines from debug.log and/or commands.log.
/etclogin <password>
Security
Valid level + NGUID
Authenticate for protected !-commands (second factor on top of n_key).
/etclogout
Security
Logged-in admins
Clear login state and remove access to protected commands.
m <slot|name> <message>
RCON
Server console / RCON
Send a private message to one or multiple players (via rconpm.lua).
!setlevel, !ban, !unban, ...
Protected
Admins with /etclogin
High-risk shrubbot commands like !setlevel, !ban,
!unban, !resetxp, !levlist, !levinfo,
!levedit, !banguid, !useredit, !levadd,
!levdelete, !userdelete, !dbsave,
!delrecords, !del_bot_xp, !rcon, !devmap,
!readconfig. All of them require a successful /etclogin when
listed in AdminPassword.protectedCmds.
This section lets you preview the Lua scripts and flat-file databases directly from the web
server. For this to work, upload the etc_custom directory from your mod package
next to this PHP file, like:
Preview of mod-files
// Select a file above to preview its contents here.
// Hint: make sure the etc_custom folder is deployed next to this PHP.