forked from MinoMino/minqlx-plugins
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathqueue.py
374 lines (314 loc) · 14.7 KB
/
queue.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# queue.py - a plugin for minqlxtended to allow players to queue politely for fair and even gameplay.
# Copyright (C) 2016 mattiZed (github)
# Copyright (C) 2016 Melodeiro (github)
# 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.
# You should have received a copy of the GNU General Public License
# along with minqlxtended. If not, see <http://www.gnu.org/licenses/>.
# Updated 31/07/2024 to make compatible with minqlxtended.
import minqlxtended
import time
TEAM_BASED_GAMETYPES = ("ca", "ctf", "dom", "ft", "tdm", "ad", "1f", "har")
NONTEAM_BASED_GAMETYPES = ("ffa", "race", "rr", "duel")
CS_PLAYERS = 529
_tag_key = "minqlx:players:{}:clantag"
class queue(minqlxtended.Plugin):
def __init__(self):
self.add_hook("new_game", self.handle_new_game)
self.add_hook("game_end", self.handle_game_end)
self.add_hook("player_loaded", self.handle_player_loaded)
self.add_hook("player_disconnect", self.handle_player_disconnect)
self.add_hook("team_switch", self.handle_team_switch)
self.add_hook("team_switch_attempt", self.handle_team_switch_attempt)
self.add_hook("set_configstring", self.handle_configstring, priority=minqlxtended.PRI_HIGH)
self.add_hook("client_command", self.handle_client_command)
self.add_hook("vote_ended", self.handle_vote_ended)
self.add_hook("console_print", self.handle_console_print)
self.add_command(("q", "queue", "que"), self.cmd_lq)
self.add_command("afk", self.cmd_afk, usage="<optional player ID>")
self.add_command("here", self.cmd_playing)
self.add_command(("teamsize", "ts"), self.cmd_teamsize, priority=minqlxtended.PRI_HIGH)
self._queue = []
self._afk = []
self._tags = {}
self.initialize()
self.is_red_locked = False
self.is_blue_locked = False
self.is_push_pending = False
self.is_endscreen = False ######## TODO: replace for something better, because
######## loading during the endgame screen might cause bugs
self.set_cvar_once("qlx_queueSetAfkPermission", "2")
self.set_cvar_once("qlx_queueAFKTag", "^3AFK")
self.test_logger = minqlxtended.get_logger()
self._cache_variables()
def _cache_variables(self):
""" we do this to prevent lots of unnecessary engine calls """
self._qlx_queueSetAfkPermission = self.get_cvar("qlx_queueSetAfkPermission", int)
self._qlx_queueAFKTag = self.get_cvar("qlx_queueAFKTag")
self._sv_maxclients = self.get_cvar("sv_maxclients", int)
def initialize(self):
for p in self.players():
self.updTag(p)
self.unlock()
def center_print(self, *args, **kwargs):
pass
## Basic List Handling (Queue and AFK)
@minqlxtended.thread
def addToQueue(self, player, pos=-1):
"""Safely adds players to the queue"""
if player not in self._queue:
if pos == -1:
self._queue.append(player)
else:
self._queue.insert(pos, player)
for p in self._queue:
self.updTag(p)
for p in self.teams()['spectator']:
p.center_print(f"{player.name}^7 joined the queue")
if player in self._queue:
player.center_print("You are in the queue to play")
self.updTag(player)
self.pushFromQueue()
def remFromQueue(self, player, update=True):
"""Safely removes player from the queue"""
if player in self._queue:
self._queue.remove(player)
for p in self._queue:
self.updTag(p)
if update:
self.updTag(player)
@minqlxtended.thread
def pushFromQueue(self, delay=0):
"""Check if there is the place and players in queue, and put them in the game"""
@minqlxtended.next_frame
def pushToTeam(amount, team):
"""Safely put certain amout of players to the selected team"""
if not self.is_endscreen:
for count, player in enumerate(self._queue, start=1):
if player in self.teams()['spectator'] and player.connection_state == 'active':
self._queue.pop(0).put(team)
elif player.connection_state not in ['connected', 'primed']:
self.remFromQueue(player)
if count == amount:
self.pushFromQueue(0.5)
return
@minqlxtended.next_frame
def pushToBoth():
### TODO ###
if len(self._queue) > 1 and not self.is_endscreen:
spectators = self.teams()['spectator']
if self._queue[0] in spectators and self._queue[0].connection_state == 'active':
if self._queue[1] in spectators and self._queue[1].connection_state == 'active':
self._queue.pop(0).put("red")
self._queue.pop(0).put("blue")
elif self._queue[1].connection_state not in ['connected', 'primed']:
self.remFromQueue(self._queue[1])
elif self._queue[0].connection_state not in ['connected', 'primed']:
self.remFromQueue(self._queue[0])
self.pushFromQueue(0.5)
@minqlxtended.next_frame
def checkForPlace():
maxplayers = self.get_maxplayers()
teams = self.teams()
red_amount = len(teams["red"])
blue_amount = len(teams["blue"])
free_amount = len(teams["free"])
if self.game.type_short in TEAM_BASED_GAMETYPES:
diff = red_amount - blue_amount
if diff > 0 and not self.is_blue_locked:
pushToTeam(diff, "blue")
elif diff < 0 and not self.is_red_locked:
pushToTeam(-diff, "red")
elif red_amount + blue_amount < maxplayers:
if len(self._queue) > 1 and not self.is_blue_locked and not self.is_red_locked:
pushToBoth() ################ add elo here for those, who want
elif self.game.state == 'warmup': # for the case if there is 1 player in queue
if not self.is_red_locked and red_amount < int(self.game.teamsize):
pushToTeam(1, "red")
elif not self.is_blue_locked and blue_amount < int(self.game.teamsize):
pushToTeam(1, "blue")
elif self.game.type_short in NONTEAM_BASED_GAMETYPES:
if free_amount < maxplayers:
pushToTeam(maxplayers - free_amount, "free")
if self.is_push_pending:
return
self.is_push_pending = True
time.sleep(delay)
self.is_push_pending = False
if len(self._queue) == 0:
return
if self.game.state != 'in_progress' and self.game.state != 'warmup':
return
if self.is_endscreen:
return
checkForPlace()
@minqlxtended.thread
def remAFK(self, player, update=True):
"""Safely removes players from afk list"""
if player in self._afk:
self._afk.remove(player)
if update:
self.updTag(player)
def posInQueue(self, player):
"""Returns position of the player in queue"""
try:
return self._queue.index(player)
except ValueError:
return -1
## AFK Handling
def setAFK(self, player):
"""Returns True if player's state could be set to AFK"""
if player in self.teams()['spectator'] and player not in self._afk:
self._afk.append(player)
self.remFromQueue(player)
return True
return False
@minqlxtended.thread
def remTag(self, player):
if player.steam_id in self._tags:
del self._tags[player.steam_id]
#@minqlxtended.thread
def updTag(self, player):
"""Update the tags dictionary and start the set_configstring event for tag to apply"""
@minqlxtended.next_frame
def upd():
if player in self.players():
player.clan = player.clan
if player in self.players():
addition = ""
position = self.posInQueue(player)
if position > -1:
addition = f'({position + 1})'
elif player in self._afk:
addition = f'({self._qlx_queueAFKTag})'
elif self.game.type_short not in TEAM_BASED_GAMETYPES + NONTEAM_BASED_GAMETYPES:
addition = ""
elif player in self.teams()['spectator']:
addition = '(s)'
self._tags[player.steam_id] = addition
upd()
def get_maxplayers(self):
maxplayers = int(self.game.teamsize)
if self.game.type_short in TEAM_BASED_GAMETYPES:
maxplayers = maxplayers * 2
if maxplayers == 0:
maxplayers = self._sv_maxclients
return maxplayers
## Plugin Handles and Commands
def handle_player_disconnect(self, player, reason):
self.remAFK(player, False)
self.remFromQueue(player, False)
self.remTag(player)
self.pushFromQueue(0.5)
def handle_player_loaded(self, player):
self.updTag(player)
def handle_team_switch(self, player, old_team, new_team):
if new_team != "spectator":
self.remFromQueue(player)
self.remAFK(player)
else:
self.updTag(player)
self.pushFromQueue(0.5)
def handle_team_switch_attempt(self, player, old_team, new_team):
if self.game.type_short not in TEAM_BASED_GAMETYPES + NONTEAM_BASED_GAMETYPES:
return
if new_team != "spectator" and old_team == "spectator":
teams = self.teams();
maxplayers = self.get_maxplayers()
if len(teams["red"]) + len(teams["blue"]) == maxplayers or len(teams["free"]) == maxplayers or self.game.state == 'in_progress' or len(self._queue) > 0 or self.is_red_locked or self.is_blue_locked:
self.remAFK(player)
self.addToQueue(player)
return minqlxtended.RET_STOP_ALL
def handle_client_command(self, player, command):
if (command.lower().strip() == "team s") and (player.team == "spectator"):
@minqlxtended.thread
def handler():
self.remFromQueue(player)
if player not in self._queue:
self.center_print(player, "You are set to spectate only")
handler()
def handle_vote_ended(self, votes, vote, args, passed):
if vote.lower().strip() == "teamsize":
self.pushFromQueue(4)
def handle_configstring(self, index, value):
if not value:
return
elif CS_PLAYERS <= index < CS_PLAYERS + 64:
try:
player = self.player(index - CS_PLAYERS)
except minqlxtended.NonexistentPlayerError:
return
if player.steam_id in self._tags:
tag = self._tags[player.steam_id]
tag_key = _tag_key.format(player.steam_id)
if tag_key in self.db:
if len(tag) > 0:
tag += ' '
tag += self.db[tag_key]
cs = minqlxtended.parse_variables(value)
cs["xcn"] = tag
cs["cn"] = tag
new_cs = "".join([f"\\{key}\\{cs[key]}" for key in cs])
return new_cs
def handle_new_game(self):
self.is_endscreen = False
self.is_red_locked = False
self.is_blue_locked = False
if self.game.type_short not in TEAM_BASED_GAMETYPES + NONTEAM_BASED_GAMETYPES:
self._queue = []
for p in self.players():
self.updTag(p)
else:
self.pushFromQueue()
def handle_game_end(self, data):
self.is_endscreen = True
def cmd_lq(self, player, msg, channel):
""" Display the current queue. """
msg = "^7No one in queue."
if self._queue:
msg = "^1Queue^7: "
count = 1
for p in self._queue:
msg += f'{p.name}^7({count}) '
count += 1
channel.reply(msg)
if self._afk:
msg = "^3Away^7 >> "
for p in self._afk:
msg += p.name + " "
channel.reply(msg)
def cmd_afk(self, player, msg, channel):
""" Marks the calling player as AFK (or the player specified.) """
if len(msg) > 1:
if self.db.has_permission(player, self._qlx_queueSetAfkPermission):
guy = self.find_player(msg[1])[0]
if self.setAFK(guy):
player.tell(f"Status for {guy.name}^7 has been set to ^3AFK^7.")
return minqlxtended.RET_STOP_ALL
else:
player.tell(f"Couldn't set status for {guy.name}^7 to ^3AFK^7.")
return minqlxtended.RET_STOP_ALL
if self.setAFK(player):
player.tell("^7Your status has been set to ^3AFK^7.")
else:
player.tell("^7Couldn't set your status to ^3AFK^7.")
def cmd_playing(self, player, msg, channel):
""" Marks the calling player as available. """
self.remAFK(player)
self.updTag(player)
player.tell("Your status has been set to ^2AVAILABLE^7.")
def cmd_teamsize(self, playing, msg, channel):
self.pushFromQueue(0.5)
def handle_console_print(self, text):
if text.find('broadcast: print "The RED team is now locked') != -1:
self.is_red_locked = True
elif text.find('broadcast: print "The BLUE team is now locked') != -1:
self.is_blue_locked = True
elif text.find('broadcast: print "The RED team is now unlocked') != -1:
self.is_red_locked = False
self.pushFromQueue(0.5) ################ if cause errors maybe call that in next_frame
elif text.find('broadcast: print "The BLUE team is now unlocked') != -1:
self.is_blue_locked = False
self.pushFromQueue(0.5)
# type: ignore