Skip to content

Commit

Permalink
Merge branch 'release/v0.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
MinoMino committed Nov 12, 2015
2 parents c981cbc + 44babb9 commit 645a070
Show file tree
Hide file tree
Showing 13 changed files with 1,181 additions and 859 deletions.
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,35 @@ can't tell who the owner is and will refuse to execute admin commands.
- Default: `0`
- `qlx_redisPassword`: The password to the Redis server, if any.
- Default: None
- `qlx_logs`: The maximum number of logs the server keeps. 0 means no limit.
- Default: `5`
- `qlx_logsSize`: The maximum size in bytes of a log before it backs it up and starts on a fresh file. 0 means no limit.
- Default: `5000000` (5 MB)

Usage
=====
Once you've configured the above cvars and launched the server, you will quickly recognize if for instance
your database configuration is wrong, as it will start printing a bunch of errors in the server console
when someone connects. If you only see stuff like the following, then you know it's working like it should:

```
[minqlx.late_init] INFO: Loading preset plugins...
[minqlx.load_plugin] INFO: Loading plugin 'plugin_manager'...
[minqlx.load_plugin] INFO: Loading plugin 'essentials'...
[minqlx.load_plugin] INFO: Loading plugin 'motd'...
[minqlx.load_plugin] INFO: Loading plugin 'permission'...
[minqlx.late_init] INFO: Stats listener started on tcp://127.0.0.1:64550.
[minqlx.late_init] INFO: We're good to go!
```

To confirm minqlx recognizes you as the owner, try connecting to the server and type `!myperm` in chat.
If it tells you that you have permission level 0, the `qlx_owner` cvar has not been set properly. Otherwise
you should be good to go. As the owner, you are allowed to type commands directly into the console instead
of having to use chat. You can now go ahead and add other admins too with `!setperm`. To use commands such
as `!kick` you need to use client IDs. Look them up with `!id` first. You can also use full SteamID64s
for commands like `!ban` where the target player might not currently be connected.

[See here for a full command list.](https://github.com/MinoMino/minqlx/wiki/Command-List)

Compiling
=========
Expand All @@ -110,7 +139,9 @@ Contribute
==========
If you'd like to contribute with code, you can fork this or the plugin repository and create pull requests for changes. If you found a bug, please open an issue here on Github and include the relevant part from either the
server's console output or from `minqlx.log` which is in your `fs_homepath`, preferably the latter as it is
more verbose. Note that `minqlx.log` starts with a new file every run, but saves the last run as `minqlx.log.bak`.
more verbose. Note that `minqlx.log` by default becomes `minqlx.log.1` whenever it goes above 5 MB, and keeps doing
that until it goes to `minqlx.log.5`, at which point the 5th one gets deleted if the current one goes over
the limit again. In other words, your logs will keep the last 30 MB of data, but won't exceed that.

Both when compiling and when using binaries, the core module is in a zip file. If you want to modify
the code, simply unzip the contents of it in the same directory and then delete the zip file. minqlx will
Expand Down
32 changes: 26 additions & 6 deletions hooks.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,25 +73,26 @@ void __cdecl My_G_InitGame(int levelTime, int randomSeed, int restart) {
// USED FOR PYTHON

#ifndef NOPY
void __cdecl My_SV_ExecuteClientCommand(client_t *cl, const char *s, qboolean clientOK) {
void __cdecl My_SV_ExecuteClientCommand(client_t *cl, char *s, qboolean clientOK) {
char* res = s;
if (clientOK && cl->gentity) {
int res = ClientCommandDispatcher(cl - svs->clients, s);
res = ClientCommandDispatcher(cl - svs->clients, s);
if (!res)
return;
}

SV_ExecuteClientCommand(cl, s, clientOK);
SV_ExecuteClientCommand(cl, res, clientOK);
}

void __cdecl My_SV_SendServerCommand(client_t* cl, const char* fmt, ...) {
int res = 1;
void __cdecl My_SV_SendServerCommand(client_t* cl, char* fmt, ...) {
va_list argptr;
char buffer[MAX_MSGLEN];

va_start(argptr, fmt);
vsnprintf((char *)buffer, sizeof(buffer), fmt, argptr);
va_end(argptr);

char* res = buffer;
if (cl && cl->gentity)
res = ServerCommandDispatcher(cl - svs->clients, buffer);
else if (cl == NULL)
Expand All @@ -100,7 +101,7 @@ void __cdecl My_SV_SendServerCommand(client_t* cl, const char* fmt, ...) {
if (!res)
return;

SV_SendServerCommand(cl, buffer);
SV_SendServerCommand(cl, res);
}

void __cdecl My_SV_ClientEnterWorld(client_t* client, usercmd_t* cmd) {
Expand Down Expand Up @@ -137,6 +138,19 @@ void __cdecl My_SV_DropClient(client_t* drop, const char* reason) {
SV_DropClient(drop, reason);
}

void __cdecl My_Com_Printf(char* fmt, ...) {
char buf[4096];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);

char* res = ConsolePrintDispatcher(buf);
// NULL means stop the event.
if (res)
Com_Printf(buf);
}

void __cdecl My_G_RunFrame(int time) {
// Dropping frames is probably not a good idea, so we don't allow cancelling.
FrameDispatcher();
Expand Down Expand Up @@ -205,6 +219,12 @@ void HookStatic(void) {
DebugPrint("ERROR: Failed to hook SV_DropClient: %d\n", res);
failed = 1;
}

res = Hook((void*)Com_Printf, My_Com_Printf, (void*)&Com_Printf);
if (res) {
DebugPrint("ERROR: Failed to hook Com_Printf: %d\n", res);
failed = 1;
}
#endif

if (failed) {
Expand Down
10 changes: 6 additions & 4 deletions pyminqlx.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ extern PyObject* frame_handler;
extern PyObject* new_game_handler;
extern PyObject* set_configstring_handler;
extern PyObject* rcon_handler;
extern PyObject* console_print_handler;

// Custom console command handler. These are commands added through Python that can be used
// from the console or using RCON.
Expand All @@ -64,22 +65,23 @@ extern PyObject* custom_command_handler;
// We need to explicitly tell player_info to not return None in the case where
// we are inside My_ClientConnect, because we want to call Python code before
// the real ClientConnect is called, which is where it sets the connection
// state from CS_FREE to CS_CONNECTED.
extern int in_clientconnect;
// state from CS_FREE to CS_CONNECTED. Same thing with My_SV_DropClient.
extern int allow_free_client;

/* Dispatchers. These are called by hooks or whatever and should dispatch events to Python handlers.
* The return values will often determine what is passed on to the engine. You could for instance
* implement a chat filter by returning 0 whenever bad words are said through the client_command event.
* Hell, it could even be used to fix bugs in the server or client (e.g. a broken userinfo command or
* broken UTF sequences that could crash clients). */
int ClientCommandDispatcher(int client_id, const char* cmd);
int ServerCommandDispatcher(int client_id, const char* cmd);
char* ClientCommandDispatcher(int client_id, char* cmd);
char* ServerCommandDispatcher(int client_id, char* cmd);
void FrameDispatcher(void);
char* ClientConnectDispatcher(int client_id, int is_bot);
int ClientLoadedDispatcher(int client_id);
void ClientDisconnectDispatcher(int client_id, const char* reason);
void NewGameDispatcher(int restart);
char* SetConfigstringDispatcher(int index, char* value);
void RconDispatcher(const char* cmd);
char* ConsolePrintDispatcher(char* cmd);

#endif /* PYMINQLX_H */
7 changes: 4 additions & 3 deletions python/minqlx/_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,10 @@ def execute(self, player, msg, channel):

def is_eligible_name(self, name):
if self.prefix:
if name[0] != minqlx.get_cvar("qlx_commandPrefix"):
prefix = minqlx.get_cvar("qlx_commandPrefix")
if not name.startswith(prefix):
return False
name = name[1:]
name = name[len(prefix):]

return name.lower() in self.name

Expand Down Expand Up @@ -154,7 +155,7 @@ def handle_input(self, player, msg, channel):
if not msg.strip():
return

name = msg.split(" ", 1)[0].lower()
name = msg.strip().split(" ", 1)[0].lower()
is_client_cmd = channel == "client_command"
pass_through = True

Expand Down
24 changes: 17 additions & 7 deletions python/minqlx/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
import shlex
import sys
import os
import re

from logging.handlers import RotatingFileHandler

# Team number -> string
TEAMS = dict(enumerate(("free", "red", "blue", "spectator")))
Expand All @@ -45,6 +48,11 @@
# Game type number -> short string
GAMETYPES_SHORT = dict(enumerate(("ffa", "duel", "race", "tdm", "ca", "ctf", "ob", "har", "ft", "dom", "ad", "rr")))

# Connection states.
STATES = dict(enumerate(("free", "zombie", "connected", "primed", "active")))

_re_varsplit = re.compile(r"\\*")

# ====================================================================
# HELPERS
# ====================================================================
Expand All @@ -67,7 +75,7 @@ def parse_variables(varstr, ordered=False):
if not varstr.strip():
return res

vars = varstr.lstrip("\\").split("\\")
vars = _re_varsplit.split(varstr.lstrip("\\"))
try:
for i in range(0, len(vars), 2):
res[vars[i]] = vars[i + 1]
Expand Down Expand Up @@ -99,20 +107,20 @@ def _configure_logger():

# File
file_path = os.path.join(minqlx.get_cvar("fs_homepath"), "minqlx.log")
if os.path.isfile(file_path):
# If the file already exists, we back it up before we start logging.
shutil.move(file_path, file_path + ".bak")
maxlogs = minqlx.Plugin.get_cvar("qlx_logs", int)
maxlogsize = minqlx.Plugin.get_cvar("qlx_logsSize", int)
file_fmt = logging.Formatter("(%(asctime)s) [%(levelname)s @ %(name)s.%(funcName)s] %(message)s", "%H:%M:%S")
file_handler = logging.FileHandler(file_path, mode="w", encoding="utf-8")
file_handler = RotatingFileHandler(file_path, encoding="utf-8", maxBytes=maxlogsize, backupCount=maxlogs)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(file_fmt)
logger.addHandler(file_handler)
logger.info("File logger initialized!")
logger.info("============================= minqlx run @ {} ============================="
.format(datetime.datetime.now()))

# Console
console_fmt = logging.Formatter("[%(name)s.%(funcName)s] %(levelname)s: %(message)s", "%H:%M:%S")
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG if minqlx.DEBUG else logging.INFO)
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(console_fmt)
logger.addHandler(console_handler)

Expand Down Expand Up @@ -357,6 +365,8 @@ def initialize_cvars():
minqlx.set_cvar_once("qlx_pluginsPath", "minqlx-plugins")
minqlx.set_cvar_once("qlx_database", "Redis")
minqlx.set_cvar_once("qlx_commandPrefix", "!")
minqlx.set_cvar_once("qlx_logs", "5")
minqlx.set_cvar_once("qlx_logsSize", str(5*10**6)) # 5 MB
# Redis
minqlx.set_cvar_once("qlx_redisAddress", "127.0.0.1")
minqlx.set_cvar_once("qlx_redisDatabase", "0")
Expand Down
Loading

0 comments on commit 645a070

Please sign in to comment.