Split the gamelog code out of miscfunctions and into its own file
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / minigames / sv_minigames.qc
1 #include "sv_minigames.qh"
2 #include "minigames.qh"
3
4 #include <server/gamelog.qh>
5
6 void player_clear_minigame(entity player)
7 {
8         CS(player).active_minigame = NULL;
9         player.minigame_players = NULL;
10         if ( IS_PLAYER(player) )
11                 set_movetype(player, MOVETYPE_WALK);
12         else
13                 set_movetype(player, MOVETYPE_FLY_WORLDONLY);
14         Player_SetForcedTeamIndex(player, TEAM_FORCE_DEFAULT);
15 }
16
17 void 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 );
34                 player_clear_minigame(player);
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);
47                                 player_clear_minigame(player);
48                                 return;
49                         }
50                         p = e;
51                 }
52         }
53 }
54
55
56 #define FIELD(Flags, Type,Name) if ( sf & (Flags) ) Write##Type(MSG_ENTITY, this.Name);
57 #define MSLE(Name,Fields) \
58         else if ( this.classname == #Name ) { \
59                 if ( sf & MINIG_SF_CREATE ) WriteString(MSG_ENTITY,this.owner.netname); \
60                 Fields }
61
62 // Send an entity to a client
63 // only use on minigame entities or entities with a minigame owner
64 bool minigame_SendEntity(entity this, entity to, int sf)
65 {
66         WriteHeader(MSG_ENTITY, ENT_CLIENT_MINIGAME);
67         WriteByte(MSG_ENTITY, sf);
68
69         if ( sf & MINIG_SF_CREATE )
70         {
71                 WriteShort(MSG_ENTITY,msle_id(this.classname));
72                 WriteString(MSG_ENTITY,this.netname);
73         }
74
75         entity minigame_ent = this.owner;
76
77         if ( this.classname == "minigame" )
78         {
79                 minigame_ent = this;
80
81                 if ( sf & MINIG_SF_CREATE )
82                         WriteString(MSG_ENTITY,this.descriptor.netname);
83
84                 if ( sf & MINIG_SF_UPDATE )
85                         WriteLong(MSG_ENTITY,this.minigame_flags);
86         }
87         else if ( this.classname == "minigame_player" )
88         {
89                 if ( sf & MINIG_SF_CREATE )
90                 {
91                         WriteString(MSG_ENTITY,this.owner.netname);
92                         WriteLong(MSG_ENTITY,etof(this.minigame_players));
93                 }
94                 if ( sf & MINIG_SF_UPDATE )
95                         WriteByte(MSG_ENTITY,this.team);
96         }
97         MINIGAME_SIMPLELINKED_ENTITIES
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
108 void minigame_resend(entity minigame)
109 {
110         minigame.SendFlags = MINIG_SF_ALL;
111         entity e = NULL;
112         while (( e = findentity(e,owner,minigame) ))
113         {
114                 e.SendFlags = MINIG_SF_ALL;
115         }
116 }
117
118 bool minigame_CheckSend(entity this, entity client)
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
127 int 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.owner = minigame_session;
141                 player_pointer.minigame_players = player;
142                 player_pointer.team = mgteam;
143                 player_pointer.list_next = minigame_session.minigame_players;
144                 minigame_session.minigame_players = player_pointer;
145                 CS(player).active_minigame = minigame_session;
146                 player.minigame_players = player_pointer;
147                 setcefc(player_pointer, minigame_CheckSend);
148                 Net_LinkEntity(player_pointer, false, 0, minigame_SendEntity);
149
150                 if ( !IS_OBSERVER(player) && autocvar_sv_minigames_observer )
151                 {
152                         PutObserverInServer(player);
153                 }
154                 if ( autocvar_sv_minigames_observer == 2 )
155                         Player_SetForcedTeamIndex(player, TEAM_FORCE_SPECTATOR);
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
166 entity 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.netname = strzone(strcat(e.netname,"_",ftos(etof(minig))));
176                 minig.descriptor = e;
177                 minig.minigame_event = e.minigame_event;
178                 minig.minigame_event(minig,"start");
179                 GameLogEcho(strcat(":minigame:start:",minig.netname));
180                 if ( ! minigame_addplayer(minig,player) )
181                 {
182                         LOG_TRACE("Minigame ",minig.netname," rejected the first player join!");
183                         end_minigame(minig);
184                         return NULL;
185                 }
186                 Net_LinkEntity(minig, false, 0, minigame_SendEntity);
187
188                 if ( !minigame_sessions )
189                         minigame_sessions = minig;
190                 else
191                 {
192                         minigame_sessions.owner = minig;
193                         minig.list_next = minigame_sessions;
194                         minigame_sessions = minig;
195                 }
196                 return minig;
197         }
198
199         return NULL;
200 }
201
202 entity join_minigame(entity player, string game_id )
203 {
204         if ( !autocvar_sv_minigames || !IS_REAL_CLIENT(player) )
205                 return NULL;
206
207         entity minig;
208         for ( minig = minigame_sessions; minig != NULL; minig = minig.list_next )
209         {
210                 if ( minig.netname == game_id )
211                 if ( minigame_addplayer(minig,player) )
212                         return minig;
213         }
214
215         return NULL;
216 }
217
218 void part_minigame(entity player )
219 {
220         entity minig = CS(player).active_minigame;
221
222         if ( minig && minig.classname == "minigame" )
223                 minigame_rmplayer(minig,player);
224 }
225
226 void end_minigame(entity minigame_session)
227 {
228         if ( minigame_session.owner )
229                 minigame_session.owner.list_next = minigame_session.list_next;
230         else
231                 minigame_sessions = minigame_session.list_next;
232
233         minigame_session.minigame_event(minigame_session,"end");
234         GameLogEcho(strcat(":minigame:end:",minigame_session.netname));
235
236
237         entity e = NULL;
238         while( (e = findentity(e, owner, minigame_session)) )
239                 if ( e.minigame_autoclean )
240                 {
241                         LOG_TRACE("SV Auto-cleaned: ",ftos(etof(e)), " (",e.classname,")");
242                         delete(e);
243                 }
244
245         entity p;
246         for ( e = minigame_session.minigame_players; e != NULL; e = p )
247         {
248                 p = e.list_next;
249                 player_clear_minigame(e.minigame_players);
250                 delete(e);
251         }
252
253         strfree(minigame_session.netname);
254         delete(minigame_session);
255 }
256
257 void end_minigames()
258 {
259         while ( minigame_sessions )
260         {
261                 end_minigame(minigame_sessions);
262         }
263 }
264
265 string invite_minigame(entity inviter, entity player)
266 {
267         if ( !inviter || !CS(inviter).active_minigame )
268                 return "Invalid minigame";
269         if ( VerifyClientEntity(player, true, false) <= 0 )
270                 return "Invalid player";
271         if ( inviter == player )
272                 return "You can't invite yourself";
273         if ( CS(player).active_minigame == CS(inviter).active_minigame )
274                 return strcat(player.netname," is already playing");
275
276         Send_Notification(NOTIF_ONE, player, MSG_INFO, INFO_MINIGAME_INVITE,
277                 CS(inviter).active_minigame.netname, inviter.netname );
278
279         GameLogEcho(strcat(":minigame:invite:",CS(inviter).active_minigame.netname,":",
280                 ftos(etof(player)),":",player.netname));
281
282         return "";
283 }
284
285 entity minigame_find_player(entity client)
286 {
287         if ( ! CS(client).active_minigame )
288                 return NULL;
289         entity e;
290         for ( e = CS(client).active_minigame.minigame_players; e; e = e.list_next )
291                 if ( e.minigame_players == client )
292                         return e;
293         return NULL;
294 }
295
296 bool MinigameImpulse(entity this, int imp)
297 {
298         if (!CS(this).active_minigame) return false;
299         entity e = minigame_find_player(this);
300         if ( imp && CS(this).active_minigame && e )
301         {
302                 return CS(this).active_minigame.minigame_event(CS(this).active_minigame,"impulse",e,imp);
303         }
304         return false;
305 }
306
307
308
309 void ClientCommand_minigame(entity caller, int request, int argc, string command)
310 {
311         if ( !autocvar_sv_minigames )
312         {
313                 sprint(caller,"Minigames are not enabled!\n");
314                 return;
315         }
316
317         if (request == CMD_REQUEST_COMMAND )
318         {
319                 string minig_cmd = argv(1);
320                 if ( minig_cmd == "create" && argc > 2 )
321                 {
322                         entity minig = start_minigame(caller, argv(2));
323                         if ( minig )
324                                 sprint(caller,"Created minigame session: ",minig.netname,"\n");
325                         else
326                                 sprint(caller,"Cannot start minigame session!\n");
327                         return;
328                 }
329                 else if ( minig_cmd == "join" && argc > 2 )
330                 {
331                         entity minig = join_minigame(caller, argv(2));
332                         if ( minig )
333                                 sprint(caller,"Joined: ",minig.netname,"\n");
334                         else
335                         {
336                                 Send_Notification(NOTIF_ONE, caller, MSG_CENTER, CENTER_JOIN_PREVENT_MINIGAME);
337                                 sprint(caller,"Cannot join given minigame session!\n");
338                         }
339                         return;
340                 }
341                 else if ( minig_cmd == "list" )
342                 {
343                         FOREACH(Minigames, true, sprint(caller, it.netname, " (", it.message, ") ", "\n"));
344                         return;
345                 }
346                 else if ( minig_cmd == "list-sessions" )
347                 {
348                         entity e;
349                         for ( e = minigame_sessions; e != NULL; e = e.list_next )
350                                 sprint(caller,e.netname,"\n");
351                         return;
352                 }
353                 else if ( minig_cmd == "end" || minig_cmd == "part" )
354                 {
355                         if ( CS(caller).active_minigame )
356                         {
357                                 part_minigame(caller);
358                                 sprint(caller,"Left minigame session\n");
359                         }
360                         else
361                                 sprint(caller,"You aren't playing any minigame...\n");
362                         return;
363                 }
364                 else if ( minig_cmd == "invite" && argc > 2 )
365                 {
366                         if ( CS(caller).active_minigame )
367                         {
368                                 entity client = GetIndexedEntity(argc, 2);
369                                 string error = invite_minigame(caller,client);
370                                 if ( error == "" )
371                                 {
372                                         sprint(caller,"You have invited ",client.netname,
373                                                 " to join your game of ", CS(caller).active_minigame.descriptor.message, "\n");
374                                 }
375                                 else
376                                         sprint(caller,"Could not invite: ", error, ".\n");
377                         }
378                         else
379                                 sprint(caller,"You aren't playing any minigame...\n");
380                         return;
381                 }
382                 else if ( CS(caller).active_minigame )
383                 {
384                         entity e = minigame_find_player(caller);
385                         string subcommand = substring(command,argv_end_index(0),-1);
386                         int arg_c = tokenize_console(subcommand);
387                         if ( CS(caller).active_minigame.minigame_event(CS(caller).active_minigame,"cmd",e,arg_c,subcommand) )
388                                 return;
389
390                 }
391                 else sprint(caller,strcat("Wrong command:^1 ",command,"\n"));
392         }
393
394         sprint(caller, "\nUsage:^3 cmd minigame create <minigame>\n");
395         sprint(caller, "  Start a new minigame session\n");
396         sprint(caller, "Usage:^3 cmd minigame join <session>\n");
397         sprint(caller, "  Join an exising minigame session\n");
398         sprint(caller, "Usage:^3 cmd minigame list\n");
399         sprint(caller, "  List available minigames\n");
400         sprint(caller, "Usage:^3 cmd minigame list-sessions\n");
401         sprint(caller, "  List available minigames sessions\n");
402         sprint(caller, "Usage:^3 cmd minigame part|end\n");
403         sprint(caller, "  Leave the current minigame\n");
404         sprint(caller, "Usage:^3 cmd minigame invite <player>\n");
405         sprint(caller, "  Invite the given player to join you in a minigame\n");
406 }