]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/minigames/minigame/c4.qc
Add an infection mode to zombie apocalypse, which allows playing as monsters
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / minigames / minigame / c4.qc
1 const float C4_TURN_PLACE = 0x0100; // player has to place a piece on the board
2 const float C4_TURN_WIN   = 0x0200; // player has won
3 const float C4_TURN_DRAW  = 0x0400; // no moves are possible
4 const float C4_TURN_NEXT  = 0x0800; // a player wants to start a new match
5 const float C4_TURN_TYPE  = 0x0f00; // turn type mask
6
7 const float C4_TURN_TEAM1 = 0x0001;
8 const float C4_TURN_TEAM2 = 0x0002;
9 const float C4_TURN_TEAM  = 0x000f; // turn team mask
10
11 const int C4_LET_CNT = 7;
12 const int C4_NUM_CNT = 6;
13 const int C4_WIN_CNT = 4;
14
15 const int C4_MAX_TILES = 42;
16
17 const int C4_TILE_SIZE = 8;
18
19 const int C4_TEAMS = 2;
20
21 // send flags
22 const float C4_SF_PLAYERSCORE  = MINIG_SF_CUSTOM;   // send minigame_player scores (won matches)
23
24 .float c4_npieces; // (minigame) number of pieces on the board (simplifies checking a draw)
25 .float c4_nexteam; // (minigame) next team (used to change the starting team on following matches)
26
27 // find connect 4 piece given its tile name
28 entity c4_find_piece(entity minig, string tile)
29 {
30         entity e = world;
31         while ( ( e = findentity(e,owner,minig) ) )
32                 if ( e.classname == "minigame_board_piece" && e.netname == tile )
33                         return e;
34         return world;
35 }
36
37 // Checks if the given piece completes a row
38 bool c4_winning_piece(entity piece)
39 {
40         int number = minigame_tile_number(piece.netname);
41         int letter = minigame_tile_letter(piece.netname);
42
43         int i;
44         entity top = piece;
45         entity left = piece;
46         entity topleft = piece;
47         entity botleft = piece;
48         for(i = number; i < C4_NUM_CNT; ++i)
49         {
50                 entity p = c4_find_piece(piece.owner,minigame_tile_buildname(letter, i));
51                 if(p.team == piece.team)
52                         top = p;
53                 else break;
54         }
55
56         for(i = letter; i >= 0; --i)
57         {
58                 entity p = c4_find_piece(piece.owner,minigame_tile_buildname(i, number));
59                 if(p.team == piece.team)
60                         left = p;
61                 else break;
62         }
63
64         int j;
65         for(i = letter, j = number; i >= 0, j >= 0; --i, --j)
66         {
67                 entity p = c4_find_piece(piece.owner,minigame_tile_buildname(i, j));
68                 if(p.team == piece.team)
69                         botleft = p;
70                 else break;
71         }
72         for(i = letter, j = number; i >= 0, j < C4_NUM_CNT; --i, ++j)
73         {
74                 entity p = c4_find_piece(piece.owner,minigame_tile_buildname(i, j));
75                 if(p.team == piece.team)
76                         topleft = p;
77                 else break;
78         }
79
80         // down
81         int found = 0;
82         for(i = minigame_tile_number(top.netname); i >= 0; --i)
83         {
84                 if(c4_find_piece(piece.owner,minigame_tile_buildname(letter, i)).team == piece.team)
85                         ++found;
86                 else break;
87         }
88
89         if(found >= C4_WIN_CNT)
90                 return true;
91
92         // right
93         found = 0;
94         for(i = minigame_tile_letter(left.netname); i < C4_LET_CNT; ++i)
95         {
96                 if(c4_find_piece(piece.owner,minigame_tile_buildname(i, number)).team == piece.team)
97                         ++found;
98                 else break;
99         }
100
101         if(found >= C4_WIN_CNT)
102                 return true;
103
104         // diagright down
105         found = 0;
106         for(i = minigame_tile_letter(topleft.netname), j = minigame_tile_number(topleft.netname); i < C4_LET_CNT, j >= 0; ++i, --j)
107         {
108                 if(c4_find_piece(piece.owner,minigame_tile_buildname(i, j)).team == piece.team)
109                         ++found;
110                 else break;
111         }
112
113         if(found >= C4_WIN_CNT)
114                 return true;
115
116         // diagright up
117         found = 0;
118         for(i = minigame_tile_letter(botleft.netname), j = minigame_tile_number(botleft.netname); i < C4_LET_CNT, j < C4_NUM_CNT; ++i, ++j)
119         {
120                 if(c4_find_piece(piece.owner,minigame_tile_buildname(i, j)).team == piece.team)
121                         ++found;
122                 else break;
123         }
124
125         if(found >= C4_WIN_CNT)
126                 return true;
127         
128         return false;
129 }
130
131 // check if the tile name is valid (3x3 grid)
132 float c4_valid_tile(string tile)
133 {
134         if ( !tile )
135                 return 0;
136         float number = minigame_tile_number(tile);
137         float letter = minigame_tile_letter(tile);
138         return 0 <= number && number < C4_NUM_CNT && 0 <= letter && letter < C4_LET_CNT;
139 }
140
141 // make a move
142 void c4_move(entity minigame, entity player, string pos )
143 {
144         if ( minigame.minigame_flags & C4_TURN_PLACE )
145         if ( pos && player.team == (minigame.minigame_flags & C4_TURN_TEAM) )
146         {
147                 if ( c4_valid_tile(pos) )
148                 if ( !c4_find_piece(minigame,pos) )
149                 {
150                         entity piece = msle_spawn(minigame,"minigame_board_piece");
151                         piece.team = player.team;
152                         piece.netname = strzone(pos);
153                         minigame_server_sendflags(piece,MINIG_SF_ALL);
154                         minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
155                         minigame.c4_npieces++;
156                         minigame.c4_nexteam = minigame_next_team(player.team,C4_TEAMS);
157                         if ( c4_winning_piece(piece) )
158                         {
159                                 player.minigame_flags++;
160                                 minigame_server_sendflags(player, C4_SF_PLAYERSCORE);
161                                 minigame.minigame_flags = C4_TURN_WIN | player.team;
162                         }
163                         else if ( minigame.c4_npieces >= C4_MAX_TILES )
164                                 minigame.minigame_flags = C4_TURN_DRAW;
165                         else
166                                 minigame.minigame_flags = C4_TURN_PLACE | minigame.c4_nexteam;
167                 }
168         }
169 }
170
171 // request a new match
172 void c4_next_match(entity minigame, entity player)
173 {
174 #ifdef SVQC
175         // on multiplayer matches, wait for both players to agree
176         if ( minigame.minigame_flags & (C4_TURN_WIN|C4_TURN_DRAW) )
177         {
178                 minigame.minigame_flags = C4_TURN_NEXT | player.team;
179                 minigame.SendFlags |= MINIG_SF_UPDATE;
180         }
181         else if ( (minigame.minigame_flags & C4_TURN_NEXT) &&
182                         !( minigame.minigame_flags & player.team ) )
183 #endif
184         {
185                 minigame.minigame_flags = C4_TURN_PLACE | minigame.c4_nexteam;
186                 minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
187                 minigame.c4_npieces = 0;
188                 entity e = world;
189                 while ( ( e = findentity(e,owner,minigame) ) )
190                         if ( e.classname == "minigame_board_piece" )
191                                 remove(e);
192         }
193 }
194
195 #ifdef SVQC
196
197
198 // required function, handle server side events
199 float c4_server_event(entity minigame, string event, ...)
200 {
201         switch(event)
202         {
203                 case "start":
204                 {
205                         minigame.minigame_flags = (C4_TURN_PLACE | C4_TURN_TEAM1);
206                         return true;
207                 }
208                 case "end":
209                 {
210                         entity e = world;
211                         while( (e = findentity(e, owner, minigame)) )
212                         if(e.classname == "minigame_board_piece")
213                         {
214                                 if(e.netname) { strunzone(e.netname); }
215                                 remove(e);
216                         }
217                         return false;
218                 }
219                 case "join":
220                 {
221                         float pl_num = minigame_count_players(minigame);
222
223                         // Don't allow more than 2 players
224                         if(pl_num >= C4_TEAMS) { return false; }
225
226                         // Get the right team
227                         if(minigame.minigame_players)
228                                 return minigame_next_team(minigame.minigame_players.team, C4_TEAMS);
229
230                         // Team 1 by default
231                         return 1;
232                 }
233                 case "cmd":
234                 {
235                         switch(argv(0))
236                         {
237                                 case "move": 
238                                         c4_move(minigame, ...(0,entity), ...(1,float) == 2 ? argv(1) : string_null ); 
239                                         return true;
240                                 case "next":
241                                         c4_next_match(minigame,...(0,entity));
242                                         return true;
243                         }
244
245                         return false;
246                 }
247                 case "network_send":
248                 {
249                         entity sent = ...(0,entity);
250                         float sf = ...(1,float);
251                         if ( sent.classname == "minigame_player" && (sf & C4_SF_PLAYERSCORE ) )
252                         {
253                                 WriteByte(MSG_ENTITY,sent.minigame_flags);
254                         }
255                         return false;
256                 }
257         }
258         
259         return false;
260 }
261
262
263 #elif defined(CSQC)
264
265 string c4_curr_pos; // identifier of the tile under the mouse
266 vector c4_boardpos; // HUD board position
267 vector c4_boardsize;// HUD board size
268 .float c4_checkwin; // Used to optimize checks to display a win
269
270 // Required function, draw the game board
271 void c4_hud_board(vector pos, vector mySize)
272 {
273         minigame_hud_fitsqare(pos, mySize);
274         c4_boardpos = pos;
275         c4_boardsize = mySize;
276         
277         minigame_hud_simpleboard(pos,mySize,minigame_texture("c4/board"));
278
279         vector tile_size = minigame_hud_denormalize_size('1 1 0' / C4_TILE_SIZE,pos,mySize);
280         vector tile_pos;
281
282         if ( (active_minigame.minigame_flags & C4_TURN_TEAM) == minigame_self.team )
283         if ( c4_valid_tile(c4_curr_pos) )
284         {
285                 tile_pos = minigame_tile_pos(c4_curr_pos,C4_NUM_CNT,C4_LET_CNT);
286                 tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize);
287                 minigame_drawpic_centered( tile_pos,  
288                                 minigame_texture(strcat("c4/piece",ftos(minigame_self.team))),
289                                 tile_size, '1 1 1', panel_fg_alpha/2, DRAWFLAG_NORMAL );
290         }
291         
292         entity e;
293         FOREACH_MINIGAME_ENTITY(e)
294         {
295                 if ( e.classname == "minigame_board_piece" )
296                 {
297                         tile_pos = minigame_tile_pos(e.netname,C4_NUM_CNT,C4_LET_CNT);
298                         tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize);
299                         
300                         if ( active_minigame.minigame_flags & C4_TURN_WIN )
301                         if ( !e.c4_checkwin )
302                                 e.c4_checkwin = c4_winning_piece(e) ? 1 : -1;
303                         
304                         float icon_color = 1;
305                         if ( e.c4_checkwin == -1 )
306                                 icon_color = 0.4;
307                         else if ( e.c4_checkwin == 1 )
308                         {
309                                 icon_color = 2;
310                                 minigame_drawpic_centered( tile_pos, minigame_texture("c4/winglow"),
311                                                 tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_ADDITIVE );
312                         }
313                                 
314                         minigame_drawpic_centered( tile_pos,  
315                                         minigame_texture(strcat("c4/piece",ftos(e.team))),
316                                         tile_size, '1 1 1'*icon_color, panel_fg_alpha, DRAWFLAG_NORMAL );
317                 }
318         }
319 }
320
321
322 // Required function, draw the game status panel
323 void c4_hud_status(vector pos, vector mySize)
324 {
325         HUD_Panel_DrawBg(1);
326         vector ts;
327         ts = minigame_drawstring_wrapped(mySize_x,pos,active_minigame.descriptor.message,
328                 hud_fontsize * 2, '0.25 0.47 0.72', panel_fg_alpha, DRAWFLAG_NORMAL,0.5);
329         
330         pos_y += ts_y;
331         mySize_y -= ts_y;
332         
333         vector player_fontsize = hud_fontsize * 1.75;
334         ts_y = ( mySize_y - 2*player_fontsize_y ) / 2;
335         ts_x = mySize_x;
336         vector mypos;
337         vector tile_size = '24 24 0';
338
339         entity e;
340         FOREACH_MINIGAME_ENTITY(e)
341         {
342                 if ( e.classname == "minigame_player" )
343                 {
344                         mypos = pos;
345                         if ( e.team == 2 )
346                                 mypos_y  += player_fontsize_y + ts_y;
347                         minigame_drawcolorcodedstring_trunc(mySize_x,mypos,
348                                 GetPlayerName(e.minigame_playerslot-1),
349                                 player_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
350                         
351                         mypos_y += player_fontsize_y;
352                         drawpic( mypos,  
353                                         minigame_texture(strcat("c4/piece",ftos(e.team))),
354                                         tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL );
355                         
356                         mypos_x += tile_size_x;
357                         
358                         drawstring(mypos,ftos(e.minigame_flags),tile_size,
359                                            '0.7 0.84 1', panel_fg_alpha, DRAWFLAG_NORMAL);
360                 }
361         }
362 }
363
364 // Turn a set of flags into a help message
365 string c4_turn_to_string(float turnflags)
366 {
367         if ( turnflags & C4_TURN_DRAW )
368                 return _("Draw");
369         
370         if ( turnflags & C4_TURN_WIN )
371         {
372                 if ( (turnflags&C4_TURN_TEAM) != minigame_self.team )
373                         return _("You lost the game!\nSelect \"^1Next Match^7\" on the menu for a rematch!");
374                 return _("You win!\nSelect \"^1Next Match^7\" on the menu to start a new match!");
375         }
376         
377         if ( turnflags & C4_TURN_NEXT )
378         {
379                 if ( (turnflags&C4_TURN_TEAM) != minigame_self.team )
380                         return _("Select \"^1Next Match^7\" on the menu to start a new match!");
381                 return _("Wait for your opponent to confirm the rematch");
382         }
383         
384         if ( (turnflags & C4_TURN_TEAM) != minigame_self.team )
385                 return _("Wait for your opponent to make their move");
386         
387         if ( turnflags & C4_TURN_PLACE )
388                 return _("Click on the game board to place your piece");
389         
390         return "";
391 }
392
393 // Make the correct move
394 void c4_make_move(entity minigame)
395 {
396         if ( minigame.minigame_flags == (C4_TURN_PLACE|minigame_self.team) )
397         {
398                 minigame_cmd("move ",c4_curr_pos);
399         }
400 }
401
402 void c4_set_curr_pos(string s)
403 {
404         if ( c4_curr_pos )
405                 strunzone(c4_curr_pos);
406         if ( s )
407                 s = strzone(s);
408         c4_curr_pos = s;
409 }
410
411 string c4_get_lowest_tile(entity minigame, string s)
412 {
413         int i;
414         int end = 0;
415         for(i = C4_NUM_CNT; i >= 0; --i)
416         {
417                 if(c4_find_piece(minigame,minigame_tile_buildname(minigame_tile_letter(s), i - 1)).team)
418                 {
419                         end = i;
420                         break;
421                 }
422         }
423         return minigame_tile_buildname(minigame_tile_letter(s), end);
424 }
425
426 // Required function, handle client events
427 float c4_client_event(entity minigame, string event, ...)
428 {
429         switch(event)
430         {
431                 case "activate":
432                 {
433                         c4_set_curr_pos("");
434                         minigame.message = c4_turn_to_string(minigame.minigame_flags);
435                         return false;
436                 }
437                 case "key_pressed":
438                 {
439                         if((minigame.minigame_flags & C4_TURN_TEAM) == minigame_self.team)
440                         {
441                                 switch ( ...(0,float) )
442                                 {
443                                         case K_RIGHTARROW:
444                                         case K_KP_RIGHTARROW:
445                                                 if ( ! c4_curr_pos )
446                                                         c4_set_curr_pos(c4_get_lowest_tile(minigame, "a3"));
447                                                 else
448                                                         c4_set_curr_pos(c4_get_lowest_tile(minigame, minigame_relative_tile(c4_curr_pos,1,0,C4_NUM_CNT,C4_LET_CNT)));
449                                                 return true;
450                                         case K_LEFTARROW:
451                                         case K_KP_LEFTARROW:
452                                                 if ( ! c4_curr_pos )
453                                                         c4_set_curr_pos(c4_get_lowest_tile(minigame, "c3"));
454                                                 else
455                                                         c4_set_curr_pos(c4_get_lowest_tile(minigame, minigame_relative_tile(c4_curr_pos,-1,0,C4_NUM_CNT,C4_LET_CNT)));
456                                                 return true;
457                                         /*case K_UPARROW:
458                                         case K_KP_UPARROW:
459                                                 if ( ! c4_curr_pos )
460                                                         c4_set_curr_pos("a1");
461                                                 else
462                                                         c4_set_curr_pos(minigame_relative_tile(c4_curr_pos,0,1,6,7));
463                                                 return true;
464                                         case K_DOWNARROW:
465                                         case K_KP_DOWNARROW:
466                                                 if ( ! c4_curr_pos )
467                                                         c4_set_curr_pos("a3");
468                                                 else
469                                                         c4_set_curr_pos(minigame_relative_tile(c4_curr_pos,0,-1,6,7));
470                                                 return true;*/
471                                         case K_ENTER:
472                                         case K_KP_ENTER:
473                                         case K_SPACE:
474                                                 c4_make_move(minigame);
475                                                 return true;
476                                 }
477                         }
478
479                         return false;
480                 }
481                 case "mouse_pressed":
482                 {
483                         if(...(0,float) == K_MOUSE1)
484                         {
485                                 c4_make_move(minigame);
486                                 return true;
487                         }
488
489                         return false;
490                 }
491                 case "mouse_moved":
492                 {
493                         vector mouse_pos = minigame_hud_normalize(mousepos,c4_boardpos,c4_boardsize);
494                         if ( minigame.minigame_flags == (C4_TURN_PLACE|minigame_self.team) )
495                         {
496                                 c4_set_curr_pos(c4_get_lowest_tile(minigame, minigame_tile_name(mouse_pos,C4_NUM_CNT,C4_LET_CNT)));
497                         }
498                         if ( ! c4_valid_tile(c4_curr_pos) )
499                                 c4_set_curr_pos("");
500
501                         return true;
502                 }
503                 case "network_receive":
504                 {
505                         entity sent = ...(0,entity);
506                         float sf = ...(1,float);
507                         if ( sent.classname == "minigame" )
508                         {
509                                 if ( sf & MINIG_SF_UPDATE )
510                                 {
511                                         sent.message = c4_turn_to_string(sent.minigame_flags);
512                                         if ( sent.minigame_flags & minigame_self.team )
513                                                 minigame_prompt();
514                                 }
515                         }
516                         else if ( sent.classname == "minigame_player" && (sf & C4_SF_PLAYERSCORE ) )
517                         {
518                                 sent.minigame_flags = ReadByte();
519                         }
520
521                         return false;
522                 }
523                 case "menu_show":
524                 {
525                         HUD_MinigameMenu_CustomEntry(...(0,entity),_("Next Match"),"next");
526                         return false;
527                 }
528                 case "menu_click":
529                 {
530                         if(...(0,string) == "next")
531                         {
532                                 minigame_cmd("next");
533                         }
534                         return false;
535                 }
536         }
537
538         return false;
539 }
540
541 #endif