Skip to content

Commit

Permalink
Merge branch 'release/v0.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
MinoMino committed Nov 26, 2015
2 parents ca6b2df + f6bc180 commit 4db8c88
Show file tree
Hide file tree
Showing 16 changed files with 1,297 additions and 190 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ see the [plugins repository](https://github.com/MinoMino/minqlx-plugins).
- `qlx_owner`: The SteamID64 of the server owner. This is should be set, otherwise minqlx
can't tell who the owner is and will refuse to execute admin commands.
- `qlx_plugins`: A comma-separated list of plugins that should be loaded at launch.
- Default: `plugin_manager, essentials, motd, permission, ban, clan`.
- Default: `plugin_manager, essentials, motd, permission, ban, clan, names`.
- `qlx_pluginsPath`: The path (either relative or absolute) to the directory with the plugins.
- Default: `minqlx-plugins`
- `qlx_database`: The default database to use. You should not change this unless you know what you're doing.
Expand Down Expand Up @@ -122,6 +122,17 @@ for commands like `!ban` where the target player might not currently be connecte

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

Updating
========
Since this and plugins use different repositories, they will also be updated separately. However, the latest master
branch of both repositories should always be compatible. If you want to try out the develop branch, make sure you use
the develop branch of both repositories too, otherwise you might run into issues.

To update the core, just use `wget` to get the latest binary tarball and put it in your QLDS directory, then simply
extract it with `tar -xvf <tarball>`. To update the plugins, use `cd` to change the working directory to `qlds/minqlx-plugins`
and do `git pull origin` and you should be good to go. Git should not remove any untracked files, so you can have your
own custom plugins there and still keep your local copy of the repo up to date.

Compiling
=========
**NOTE**: This is *not* required if you are using binaries.
Expand Down
3 changes: 3 additions & 0 deletions commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,8 @@ void __cdecl RestartPython(void) {
if (PyMinqlx_IsInitialized())
PyMinqlx_Finalize();
PyMinqlx_Initialize();
// minqlx initializes after the first new game starts, but since the game already
// start, we manually trigger the event to make it initialize properly.
NewGameDispatcher(0);
}
#endif
9 changes: 9 additions & 0 deletions dllmain.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ G_InitGame_ptr G_InitGame;
CheckPrivileges_ptr CheckPrivileges;
ClientConnect_ptr ClientConnect;
ClientDisconnect_ptr ClientDisconnect;
ClientSpawn_ptr ClientSpawn;

// VM global variables.
gentity_t* g_entities;
Expand Down Expand Up @@ -293,6 +294,14 @@ void SearchVmFunctions(void) {
}
else DebugPrint("ClientDisconnect: %p\n", ClientDisconnect);

ClientSpawn = (ClientSpawn_ptr)PatternSearch((void*)((pint)qagame + 0xB000),
0xB0000, PTRN_CLIENTSPAWN, MASK_CLIENTSPAWN);
if (ClientSpawn == NULL) {
DebugPrint("ERROR: Unable to find ClientSpawn.\n");
failed = 1;
}
else DebugPrint("ClientSpawn: %p\n", ClientSpawn);

if (failed) {
DebugPrint("Exiting.\n");
exit(1);
Expand Down
15 changes: 15 additions & 0 deletions hooks.c
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,15 @@ char* __cdecl My_ClientConnect(int clientNum, qboolean firstTime, qboolean isBot

return ClientConnect(clientNum, firstTime, isBot);
}

void __cdecl My_ClientSpawn(gentity_t* ent) {
ClientSpawn(ent);

// Since we won't ever stop the real function from being called,
// we trigger the event after calling the real one. This will allow
// us to set weapons and such without it getting overriden later.
ClientSpawnDispatcher(ent - g_entities);
}
#endif

// Hook static functions. Can be done before program even runs.
Expand Down Expand Up @@ -268,6 +277,12 @@ void HookVm(void) {
failed = 1;
}

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

if (failed) {
DebugPrint("Exiting.\n");
exit(1);
Expand Down
2 changes: 2 additions & 0 deletions patterns.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
#define MASK_SYS_SETMODULEOFFSET "XXXXXXXXXXXXXXXXX----XXX-X----X----X----XXXXXX-"
#define PTRN_VA "\x53\x48\x81\xec\x00\x00\x00\x00\x84\xc0\x48\x89\x74\x24\x00\x48\x89\x54\x24\x00\x48\x89\x4c\x24\x00\x4c\x89\x44\x24\x00\x4c\x89\x4c\x24\x00\x74\x00\x0f\x29\x44\x24\x00\x0f\x29\x4c\x24\x00"
#define MASK_VA "XXXX----XXXXXX-XXXX-XXXX-XXXX-XXXX-X-XXXX-XXXX-"
#define PTRN_CLIENTSPAWN "\x41\x57\x41\x56\x49\x89\xfe\x41\x55\x41\x54\x55\x53\x48\x81\xec\x00\x00\x00\x00\x4c\x8b\xbf\x00\x00\x00\x00\x64\x48\x8b\x04\x25\x00\x00\x00\x00\x48\x89\x84\x24\x00\x00\x00\x00\x31\xc0"
#define MASK_CLIENTSPAWN "XXXXXXXXXXXXXXXX----XXX----XXXXX----XXXX----XX"

// Generated by minfuncfind64. qagame functions.
#define PTRN_G_RUNFRAME "\x8b\x05\x00\x00\x00\x00\x85\xc0\x74\x00\xf3\xc3"
Expand Down
2 changes: 2 additions & 0 deletions pyminqlx.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ extern PyObject* new_game_handler;
extern PyObject* set_configstring_handler;
extern PyObject* rcon_handler;
extern PyObject* console_print_handler;
extern PyObject* client_spawn_handler;

// Custom console command handler. These are commands added through Python that can be used
// from the console or using RCON.
Expand All @@ -83,5 +84,6 @@ void NewGameDispatcher(int restart);
char* SetConfigstringDispatcher(int index, char* value);
void RconDispatcher(const char* cmd);
char* ConsolePrintDispatcher(char* cmd);
void ClientSpawnDispatcher(int client_id);

#endif /* PYMINQLX_H */
67 changes: 44 additions & 23 deletions python/minqlx/_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import minqlx
import re

MAX_MSG_LENGTH = 1000
re_color_tag = re.compile(r"\^[^\^]")

# ====================================================================
Expand Down Expand Up @@ -237,28 +238,34 @@ def name(self):
def reply(self, msg):
raise NotImplementedError()

def split_long_msg(self, msg, limit=100, delimiter=" "):
"""Split a message into several pieces for channels with limtations."""
if len(msg) < limit:
return [msg]
out = []
index = limit
for i in reversed(range(limit)):
if msg[i:i + len(delimiter)] == delimiter:
index = i
out.append(msg[0:index])
# Keep going, but skip the delimiter.
rest = msg[index + len(delimiter):]
if rest:
out.extend(self.split_long_msg(rest, limit, delimiter))
return out

out.append(msg[0:index])
# Keep going.
rest = msg[index:]
if rest:
out.extend(self.split_long_msg(rest, limit, delimiter))
return out
def split_long_lines(self, msg, limit=100, delimiter=" "):
res = []

while msg:
i = msg.find("\n")
if 0 <= i <= limit:
res.append(msg[:i])
msg = msg[i+1:]
continue

if len(msg) < limit:
if msg:
res.append(msg)
break

length = 0
while True:
i = msg[length:].find(delimiter)
if i == -1 or i+length > limit:
if not length:
length = limit+1
res.append(msg[:length-1])
msg = msg[length+len(delimiter)-1:]
break
else:
length += i+1

return res

class ChatChannel(AbstractChannel):
"""A channel for chat to and from the server."""
Expand All @@ -285,7 +292,21 @@ def reply(self, msg, limit=100, delimiter=" "):
if p.team == self.team:
targets.append(p.id)

for s in self.split_long_msg(msg, limit, delimiter):
split_msgs = self.split_long_lines(msg, limit, delimiter)
# We've split messages, but we can still just join them up to 1000-ish
# bytes before we need to send multiple server cmds.
joined_msgs = []
for s in split_msgs:
if not len(joined_msgs):
joined_msgs.append(s)
else:
s_new = joined_msgs[-1] + "\n" + s
if len(s_new.encode(errors="replace")) > MAX_MSG_LENGTH:
joined_msgs.append(s)
else:
joined_msgs[-1] = s_new

for s in joined_msgs:
if not targets:
minqlx.send_server_command(None, self.fmt.format(last_color + s))
else:
Expand Down
22 changes: 12 additions & 10 deletions python/minqlx/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,24 @@
import shlex
import sys
import os
import re

from logging.handlers import RotatingFileHandler

# Team number -> string
TEAMS = dict(enumerate(("free", "red", "blue", "spectator")))
TEAMS = collections.OrderedDict(enumerate(("free", "red", "blue", "spectator")))

# Game type number -> string
GAMETYPES = dict(enumerate(("Free for All", "Duel", "Race", "Team Deathmatch", "Clan Arena",
GAMETYPES = collections.OrderedDict(enumerate(("Free for All", "Duel", "Race", "Team Deathmatch", "Clan Arena",
"Capture the Flag", "Overload", "Harvester", "Freeze Tag", "Domination", "Attack and Defend", "Red Rover")))

# Game type number -> short string
GAMETYPES_SHORT = dict(enumerate(("ffa", "duel", "race", "tdm", "ca", "ctf", "ob", "har", "ft", "dom", "ad", "rr")))
GAMETYPES_SHORT = collections.OrderedDict(enumerate(("ffa", "duel", "race", "tdm", "ca", "ctf", "ob", "har", "ft", "dom", "ad", "rr")))

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

_re_varsplit = re.compile(r"\\*")
WEAPONS = collections.OrderedDict(enumerate(("_none", "g", "mg", "sg", "gl", "rl", "lg", "rg",
"pg", "bfg", "gh", "ng", "pl", "cg", "hmg", "hands")))

# ====================================================================
# HELPERS
Expand All @@ -74,12 +74,14 @@ def parse_variables(varstr, ordered=False):
if not varstr.strip():
return res

vars = _re_varsplit.split(varstr.lstrip("\\"))
vars = varstr.lstrip("\\").split("\\")
try:
for i in range(0, len(vars), 2):
res[vars[i]] = vars[i + 1]
except:
raise ValueError("Uneven number of keys and values: {}".format(varstr))
except IndexError:
# Log and return incomplete dict.
logger = minqlx.get_logger()
logger.warning("Uneven number of keys and values: {}".format(varstr))

return res

Expand Down Expand Up @@ -210,7 +212,7 @@ def set_map_subtitles():

def next_frame(func):
def f(*args, **kwargs):
minqlx.frame_tasks.enter(0, 0, func, args, kwargs)
minqlx.next_frame_tasks.append((func, args, kwargs))

return f

Expand Down
17 changes: 11 additions & 6 deletions python/minqlx/_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import minqlx
import re

_re_vote = re.compile(r"^(?P<cmd>[^ ]+)(?: \"?(?P<args>.+?)\"?)?$")
_re_vote = re.compile(r"^(?P<cmd>[^ ]+)(?: \"?(?P<args>.*?)\"?)?$")

# ====================================================================
# EVENTS
Expand Down Expand Up @@ -375,6 +375,13 @@ class PlayerDisonnectDispatcher(EventDispatcher):
def dispatch(self, player, reason):
return super().dispatch(player, reason)

class PlayerSpawnDispatcher(EventDispatcher):
"""Event that triggers when a player spawns. Cannot be cancelled."""
name = "player_spawn"

def dispatch(self, player):
return super().dispatch(player)

class StatsDispatcher(EventDispatcher):
"""Event that triggers whenever the server sends stats over ZMQ."""
name = "stats"
Expand All @@ -394,20 +401,17 @@ class VoteEndedDispatcher(EventDispatcher):
name = "vote_ended"

def dispatch(self, passed):
super().dispatch(passed)

def cancel(self):
# Check if there's a current vote in the first place.
cs = minqlx.get_configstring(9)
if not cs:
minqlx.get_logger().warning("vote_ended went off without configstring 9.")
return

res = _re_vote.match(cs)
vote = res.group("cmd")
args = res.group("args") if res.group("args") else ""
votes = (int(minqlx.get_configstring(10)), int(minqlx.get_configstring(11)))
# Return None if the vote's cancelled (like if the round starts before vote's over).
super().trigger(votes, vote, args, None)
super().dispatch(votes, vote, args, passed)

class VoteDispatcher(EventDispatcher):
"""Event that goes off whenever someone tries to vote either yes or no."""
Expand Down Expand Up @@ -525,6 +529,7 @@ def dispatch(self, victim, killer, data):
EVENT_DISPATCHERS.add_dispatcher(PlayerConnectDispatcher)
EVENT_DISPATCHERS.add_dispatcher(PlayerLoadedDispatcher)
EVENT_DISPATCHERS.add_dispatcher(PlayerDisonnectDispatcher)
EVENT_DISPATCHERS.add_dispatcher(PlayerSpawnDispatcher)
EVENT_DISPATCHERS.add_dispatcher(StatsDispatcher)
EVENT_DISPATCHERS.add_dispatcher(VoteCalledDispatcher)
EVENT_DISPATCHERS.add_dispatcher(VoteEndedDispatcher)
Expand Down
Loading

0 comments on commit 4db8c88

Please sign in to comment.