Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
sv_minigames.qc
Go to the documentation of this file.
1#include "sv_minigames.qh"
2
4#include <server/gamelog.qh>
5
7{
8 CS(player).active_minigame = NULL;
9 player.minigame_players = NULL;
10 if ( IS_PLAYER(player) )
12 else
15}
16
17void minigame_rmplayer(entity minigame_session, entity player)
18{
19 entity e;
20 entity p = minigame_session.minigame_players;
21
22 if ( p.minigame_players == player )
23 {
24 if ( p.list_next == NULL )
25 {
26 end_minigame(minigame_session);
27 return;
28 }
29 minigame_session.minigame_event(minigame_session,"part",player);
30 GameLogEcho(strcat(":minigame:part:",minigame_session.netname,":",
31 ftos(etof(player)),":",player.netname));
32 minigame_session.minigame_players = p.list_next;
33 delete( p );
35 }
36 else
37 {
38 for ( e = p.list_next; e != NULL; e = e.list_next )
39 {
40 if ( e.minigame_players == player )
41 {
42 minigame_session.minigame_event(minigame_session,"part",player);
43 GameLogEcho(strcat(":minigame:part:",minigame_session.netname,":",
44 ftos(etof(player)),":",player.netname));
45 p.list_next = e.list_next;
46 delete(e);
48 return;
49 }
50 p = e;
51 }
52 }
53}
54
55
56#define FIELD(Flags, Type, Name) \
57 if (sf & (Flags)) \
58 Write##Type(MSG_ENTITY, this.Name);
59
60#define MSLE(Name, Fields) \
61 else if (this.classname == #Name) \
62 { \
63 Fields \
64 }
65
66// Send an entity to a client
67// only use on minigame entities or entities with a minigame owner
68bool minigame_SendEntity(entity this, entity to, int sf)
69{
70 WriteHeader(MSG_ENTITY, ENT_CLIENT_MINIGAME);
72
73 if ( sf & MINIG_SF_CREATE )
74 {
79 }
80
81 entity minigame_ent = this.owner;
82 if ( this.classname == "minigame" )
83 {
84 minigame_ent = this;
85 WriteByte(MSG_ENTITY, (CS(to).active_minigame == this));
86
87 if ( sf & MINIG_SF_UPDATE )
89 }
90 else if ( this.classname == "minigame_player" )
91 {
92 if ( sf & MINIG_SF_CREATE )
94 if ( sf & MINIG_SF_UPDATE )
96 }
98
99 minigame_ent.minigame_event(minigame_ent,"network_send",this,sf);
100
101 return true;
102
103}
104#undef FIELD
105#undef MSLE
106
107// Force resend all minigame entities
109{
110 minigame.SendFlags = MINIG_SF_ALL;
111 entity e = NULL;
112 while (( e = findfloat(e,minigame_id,minigame.minigame_id) ))
113 {
114 e.SendFlags = MINIG_SF_ALL;
115 }
116}
117
119{
120 entity e;
121 for ( e = this.owner.minigame_players; e != NULL; e = e.list_next )
122 if ( e.minigame_players == client )
123 return true;
124 return false;
125}
126
127int minigame_addplayer(entity minigame_session, entity player)
128{
129 if ( CS(player).active_minigame )
130 {
131 if ( CS(player).active_minigame == minigame_session )
132 return 0;
133 minigame_rmplayer(CS(player).active_minigame,player);
134 }
135 entity player_pointer = new(minigame_player);
136 int mgteam = minigame_session.minigame_event(minigame_session,"join",player,player_pointer);
137
138 if ( mgteam )
139 {
140 player_pointer.minigame_id = minigame_session.minigame_id;
141 player_pointer.descriptor = minigame_session.descriptor;
142 player_pointer.owner = minigame_session;
143 player_pointer.minigame_players = player;
144 player_pointer.team = mgteam;
145 player_pointer.list_next = minigame_session.minigame_players;
146 minigame_session.minigame_players = player_pointer;
147 CS(player).active_minigame = minigame_session;
148 player.minigame_players = player_pointer;
149 setcefc(player_pointer, minigame_CheckSend);
150 Net_LinkEntity(player_pointer, false, 0, minigame_SendEntity);
151
153 PutObserverInServer(player, true, true);
156
157 minigame_resend(minigame_session);
158 }
159 else { delete(player_pointer); }
160 GameLogEcho(strcat(":minigame:join",(mgteam?"":"fail"),":",minigame_session.netname,":",
161 ftos(etof(player)),":",player.netname));
162
163 return mgteam;
164}
165
166entity start_minigame(entity player, string minigame )
167{
168 if ( !autocvar_sv_minigames || !IS_REAL_CLIENT(player) )
169 return NULL;
170
171 entity e = minigame_get_descriptor(minigame);
172 if ( e )
173 {
174 entity minig = new(minigame);
175 minig.minigame_id = etof(minig);
176 minig.netname = strzone(strcat(e.netname,"_",ftos(minig.minigame_id)));
177 minig.descriptor = e;
178 minig.minigame_event = e.minigame_event;
179 minig.minigame_event(minig,"start");
180 GameLogEcho(strcat(":minigame:start:",minig.netname));
181 if ( ! minigame_addplayer(minig,player) )
182 {
183 LOG_TRACE("Minigame ",minig.netname," rejected the first player join!");
184 end_minigame(minig);
185 return NULL;
186 }
187 Net_LinkEntity(minig, false, 0, minigame_SendEntity);
188
189 if ( !minigame_sessions )
190 minigame_sessions = minig;
191 else
192 {
193 minigame_sessions.owner = minig;
194 minig.list_next = minigame_sessions;
195 minigame_sessions = minig;
196 }
197 return minig;
198 }
199
200 return NULL;
201}
202
203entity join_minigame(entity player, string game_id )
204{
205 if ( !autocvar_sv_minigames || !IS_REAL_CLIENT(player) )
206 return NULL;
207
208 entity minig;
209 for ( minig = minigame_sessions; minig != NULL; minig = minig.list_next )
210 {
211 if ( minig.netname == game_id )
212 if ( minigame_addplayer(minig,player) )
213 return minig;
214 }
215
216 return NULL;
217}
218
220{
221 entity minig = CS(player).active_minigame;
222
223 if ( minig && minig.classname == "minigame" )
224 minigame_rmplayer(minig,player);
225}
226
227void end_minigame(entity minigame_session)
228{
229 if ( minigame_session.owner )
230 minigame_session.owner.list_next = minigame_session.list_next;
231 else
232 minigame_sessions = minigame_session.list_next;
233
234 minigame_session.minigame_event(minigame_session,"end");
235 GameLogEcho(strcat(":minigame:end:",minigame_session.netname));
236
237
238 entity e = NULL;
239 while( (e = findfloat(e, minigame_id, minigame_session.minigame_id)) )
240 if ( e.minigame_autoclean )
241 {
242 LOG_TRACE("SV Auto-cleaned: ",ftos(etof(e)), " (",e.classname,")");
243 strfree(e.netname);
244 delete(e);
245 }
246
247 entity p;
248 for ( e = minigame_session.minigame_players; e != NULL; e = p )
249 {
250 p = e.list_next;
251 player_clear_minigame(e.minigame_players);
252 delete(e);
253 }
254
255 strfree(minigame_session.netname);
256 delete(minigame_session);
257}
258
260{
261 while ( minigame_sessions )
262 {
264 }
265}
266
267string invite_minigame(entity inviter, entity player)
268{
269 if ( !inviter || !CS(inviter).active_minigame )
270 return "Invalid minigame";
271 if ( VerifyClientEntity(player, true, false) <= 0 )
272 return "Invalid player";
273 if ( inviter == player )
274 return "You can't invite yourself";
276 return "You can't invite a banned player";
277 if ( CS(player).active_minigame == CS(inviter).active_minigame )
278 return strcat(player.netname," is already playing");
279
280 Send_Notification(NOTIF_ONE, player, MSG_INFO, INFO_MINIGAME_INVITE,
281 CS(inviter).active_minigame.netname, inviter.netname );
282
283 GameLogEcho(strcat(":minigame:invite:",CS(inviter).active_minigame.netname,":",
284 ftos(etof(player)),":",player.netname));
285
286 return "";
287}
288
290{
291 if ( ! CS(client).active_minigame )
292 return NULL;
293 entity e;
294 for ( e = CS(client).active_minigame.minigame_players; e; e = e.list_next )
295 if ( e.minigame_players == client )
296 return e;
297 return NULL;
298}
299
301{
302 if (!CS(this).active_minigame) return false;
304 if ( imp && CS(this).active_minigame && e )
305 {
306 return CS(this).active_minigame.minigame_event(CS(this).active_minigame,"impulse",e,imp);
307 }
308 return false;
309}
310
311// this macro exists only to shorten code lines
312#define MINIGAME_CMD(cmd_id) MINIGAME_COMMON_CMD[MINIGAME_COMMON_CMD_ID_##cmd_id]
313
314void ClientCommand_minigame(entity caller, int request, int argc, string command)
315{
317 {
318 sprint(caller,"Minigames are not enabled!\n");
319 return;
320 }
321
323 {
324 Send_Notification(NOTIF_ONE_ONLY, caller, MSG_CENTER, CENTER_JOIN_PLAYBAN);
325 sprint(caller, "You aren't allowed to play minigames because you are banned from them in this server.\n");
326 return;
327 }
328
329 if (request == CMD_REQUEST_COMMAND )
330 {
331 string minig_cmd = argv(1);
332 if ( minig_cmd == MINIGAME_CMD(CREATE) && argc > 2 )
333 {
334 entity minig = start_minigame(caller, argv(2));
335 if ( minig )
336 sprint(caller,"Created minigame session: ",minig.netname,"\n");
337 else
338 sprint(caller,"Cannot start minigame session!\n");
339 return;
340 }
341 else if ( minig_cmd == MINIGAME_CMD(JOIN) && argc > 2 )
342 {
343 entity minig = join_minigame(caller, argv(2));
344 if ( minig )
345 sprint(caller,"Joined: ",minig.netname,"\n");
346 else
347 {
348 Send_Notification(NOTIF_ONE, caller, MSG_CENTER, CENTER_JOIN_PREVENT_MINIGAME);
349 sprint(caller,"Cannot join given minigame session!\n");
350 }
351 return;
352 }
353 else if ( minig_cmd == MINIGAME_CMD(LIST) )
354 {
355 FOREACH(Minigames, true, sprint(caller, it.netname, " (", it.message, ") ", "\n"));
356 return;
357 }
358 else if ( minig_cmd == MINIGAME_CMD(LIST_SESSIONS) )
359 {
360 entity e;
361 for ( e = minigame_sessions; e != NULL; e = e.list_next )
362 sprint(caller,e.netname,"\n");
363 return;
364 }
365 else if ( minig_cmd == MINIGAME_CMD(END) || minig_cmd == MINIGAME_CMD(PART) )
366 {
367 if ( CS(caller).active_minigame )
368 {
369 part_minigame(caller);
370 sprint(caller,"Left minigame session\n");
371 }
372 else
373 sprint(caller,"You aren't playing any minigame...\n");
374 return;
375 }
376 else if ( minig_cmd == MINIGAME_CMD(INVITE) && argc > 2 )
377 {
378 if ( CS(caller).active_minigame )
379 {
380 entity client = GetIndexedEntity(argc, 2);
381 string error = invite_minigame(caller,client);
382 if ( error == "" )
383 {
384 sprint(caller,"You have invited ",client.netname,
385 " to join your game of ", CS(caller).active_minigame.descriptor.message, "\n");
386 }
387 else
388 sprint(caller,"Could not invite: ", error, ".\n");
389 }
390 else
391 sprint(caller,"You aren't playing any minigame...\n");
392 return;
393 }
394 else if ( CS(caller).active_minigame )
395 {
396 entity e = minigame_find_player(caller);
397 string subcommand = substring(command,argv_end_index(0),-1);
398 int arg_c = tokenize_console(subcommand);
399 if ( CS(caller).active_minigame.minigame_event(CS(caller).active_minigame,"cmd",e,arg_c,subcommand) )
400 return;
401
402 }
403 else sprint(caller,strcat("Wrong command:^1 ",command,"\n"));
404 }
405
406 sprint(caller, "\nUsage:^3 cmd minigame create <minigame>\n");
407 sprint(caller, " Start a new minigame session\n");
408 sprint(caller, "Usage:^3 cmd minigame join <session>\n");
409 sprint(caller, " Join an exising minigame session\n");
410 sprint(caller, "Usage:^3 cmd minigame list\n");
411 sprint(caller, " List available minigames\n");
412 sprint(caller, "Usage:^3 cmd minigame list-sessions\n");
413 sprint(caller, " List available minigames sessions\n");
414 sprint(caller, "Usage:^3 cmd minigame part|end\n");
415 sprint(caller, " Leave the current minigame\n");
416 sprint(caller, "Usage:^3 cmd minigame invite <player>\n");
417 sprint(caller, " Invite the given player to join you in a minigame\n");
418}
string autocvar_g_playban_list
Definition banning.qh:14
bool autocvar_g_playban_minigames
Definition banning.qh:15
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
entity active_minigame
string netname
Definition powerups.qc:20
entity owner
Definition main.qh:87
int team
Definition main.qh:188
const int CMD_REQUEST_COMMAND
Definition command.qh:3
#define IS_PLAYER(s)
Definition player.qh:242
string classname
#define argv_end_index
#define tokenize_console
WriteString(chan, ent.netname)
WriteByte(chan, ent.angles.y/DEC_FACTOR)
void GameLogEcho(string s)
Definition gamelog.qc:15
#define FOREACH(list, cond, body)
Definition iter.qh:19
const int MSG_ENTITY
Definition net.qh:156
#define WriteHeader(to, id)
Definition net.qh:265
void Net_LinkEntity(entity e, bool docull, float dt, bool(entity this, entity to, int sendflags) sendfunc)
Definition net.qh:167
#define WriteRegistered(r, to, it)
Definition net.qh:292
#define LOG_TRACE(...)
Definition log.qh:74
string substring(string s, float start, float length)
void WriteLong(float data, float dest, float desto)
void sprint(float clientnum, string text,...)
void WriteShort(float data, float dest, float desto)
void WriteEntity(entity data, float dest, float desto)
string ftos(float f)
string strzone(string s)
string argv(float n)
entity findfloat(entity start,.float field, float match)
#define MINIGAME_SIMPLELINKED_ENTITIES
Set up automatic entity read/write functionality To ensure that everything is handled automatically,...
Definition all.qh:100
int msle_id(string class_name)
Definition minigames.qc:110
entity minigame_get_descriptor(string id)
Definition minigames.qc:5
int minigame_id
Unique ID for the minigame session, associated with all objects currently the result of etof(minigame...
Definition minigames.qh:49
int minigame_flags
Definition minigames.qh:107
const int MINIG_SF_UPDATE
Definition minigames.qh:113
const int MINIG_SF_ALL
Definition minigames.qh:116
const int MINIG_SF_CREATE
Definition minigames.qh:112
entity descriptor
For minigame sessions: minigame descriptor object.
Definition minigames.qh:45
#define etof(e)
Definition misc.qh:25
void set_movetype(entity this, int mt)
Definition movetypes.qc:4
const int MOVETYPE_WALK
Definition movetypes.qh:136
const int MOVETYPE_FLY_WORLDONLY
Definition movetypes.qh:147
strcat(_("^F4Countdown stopped!"), "\n^BG", _("Teams are too unbalanced."))
void Send_Notification(NOTIF broadcast, entity client, MSG net_type, Notification net_name,...count)
Definition all.qc:1500
#define NULL
Definition post.qh:14
#define error
Definition pre.qh:6
#define setcefc(e, f)
void PutObserverInServer(entity this, bool is_forced, bool use_spawnpoint)
putting a client as observer in the server
Definition client.qc:261
bool PlayerInList(entity player, string list)
Definition client.qc:1048
entity GetIndexedEntity(int argc, int start_index)
Definition common.qc:82
int VerifyClientEntity(entity client, bool must_be_real, bool must_be_bots)
Definition common.qc:47
int int int imp
Definition impulse.qc:90
ClientState CS(Client this)
Definition state.qh:47
#define strfree(this)
Definition string.qh:57
#define MINIGAME_CMD(cmd_id)
int minigame_addplayer(entity minigame_session, entity player)
bool MinigameImpulse(entity this, int imp)
void player_clear_minigame(entity player)
bool minigame_CheckSend(entity this, entity client)
void minigame_rmplayer(entity minigame_session, entity player)
entity join_minigame(entity player, string game_id)
Join an existing minigame session.
void end_minigames()
bool minigame_SendEntity(entity this, entity to, int sf)
void part_minigame(entity player)
void end_minigame(entity minigame_session)
entity start_minigame(entity player, string minigame)
Create a new minigame session.
string invite_minigame(entity inviter, entity player)
Invite a player to join in a minigame.
entity minigame_find_player(entity client)
void ClientCommand_minigame(entity caller, int request, int argc, string command)
void minigame_resend(entity minigame)
entity minigame_players
For minigame sessions: list of players For minigame_player: client entity.
entity minigame_sessions
bool autocvar_sv_minigames
bool autocvar_sv_minigames_observer
void Player_SetForcedTeamIndex(entity player, int team_index)
Sets the index of the forced team of the given player.
Definition teamplay.qc:345
@ TEAM_FORCE_DEFAULT
Don't force any team.
Definition teamplay.qh:153
@ TEAM_FORCE_SPECTATOR
Force the player to spectator team.
Definition teamplay.qh:152
#define IS_OBSERVER(v)
Definition utils.qh:11
#define IS_REAL_CLIENT(v)
Definition utils.qh:17