]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/minigames/minigame/bd.qc
Remove a comment that is no longer true
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / minigames / minigame / bd.qc
1 #include "bd.qh"
2 REGISTER_MINIGAME(bd, "Bulldozer");
3
4 const int BD_TURN_MOVE  = 0x0100; // player must move the bulldozer
5 const int BD_TURN_WIN   = 0x0200; // victory
6 const int BD_TURN_LOSS  = 0x0400; // they did it?!
7 const int BD_TURN_EDIT  = 0x0800; // editing mode
8 const int BD_TURN_TYPE  = 0x0f00; // turn type mask
9
10 const int BD_SF_PLAYERMOVES = MINIG_SF_CUSTOM;
11
12 // 240 tiles...
13 const int BD_LET_CNT = 20;
14 const int BD_NUM_CNT = 20;
15
16 const int BD_TILE_SIZE = 20;
17
18 const int BD_TEAMS = 1;
19
20 .int bd_dir;
21
22 .int bd_moves;
23
24 .string bd_levelname;
25 .string bd_nextlevel;
26
27 #ifdef SVQC
28 .bool bd_canedit;
29 #endif
30
31 .int bd_tiletype;
32 const int BD_TILE_DOZER = 1;
33 const int BD_TILE_TARGET = 2;
34 const int BD_TILE_BOULDER = 3;
35 const int BD_TILE_BRICK1 = 4;
36 const int BD_TILE_BRICK2 = 5;
37 const int BD_TILE_BRICK3 = 6;
38 const int BD_TILE_BRICK4 = 7;
39 const int BD_TILE_BRICK5 = 8;
40 const int BD_TILE_BRICK6 = 9;
41 const int BD_TILE_BRICK7 = 10;
42 const int BD_TILE_BRICK8 = 11;
43 const int BD_TILE_LAST = 11;
44
45 const int BD_DIR_UP = 0;
46 const int BD_DIR_DN = 1;
47 const int BD_DIR_LF = 2;
48 const int BD_DIR_RT = 3;
49
50 string autocvar_sv_minigames_bulldozer_startlevel = "level1";
51
52 // find same game piece given its tile name
53 entity bd_find_piece(entity minig, string tile, bool check_target)
54 {
55         entity e = NULL;
56         while ( ( e = findentity(e,owner,minig) ) )
57                 if ( e.classname == "minigame_board_piece" && e.netname == tile && ((check_target) ? e.bd_tiletype == BD_TILE_TARGET : e.bd_tiletype != BD_TILE_TARGET) )
58                         return e;
59         return NULL;
60 }
61
62 // check if the tile name is valid (15x15 grid)
63 bool bd_valid_tile(string tile)
64 {
65         if ( !tile )
66                 return false;
67         int number = minigame_tile_number(tile);
68         int letter = minigame_tile_letter(tile);
69         return 0 <= number && number < BD_NUM_CNT && 0 <= letter && letter < BD_LET_CNT;
70 }
71
72 entity bd_find_dozer(entity minig)
73 {
74         entity e = NULL;
75         while ( ( e = findentity(e,owner,minig) ) )
76                 if ( e.classname == "minigame_board_piece" && e.bd_tiletype == BD_TILE_DOZER )
77                         return e;
78         return NULL;
79 }
80
81 void bd_check_winner(entity minig)
82 {
83         int total = 0, valid = 0;
84         entity e = NULL;
85         while ( ( e = findentity(e,owner,minig) ) )
86                 if ( e.classname == "minigame_board_piece" && e.bd_tiletype == BD_TILE_TARGET )
87                 {
88                         ++total;
89                         if(bd_find_piece(minig, e.netname, false).bd_tiletype == BD_TILE_BOULDER)
90                                 ++valid;
91                 }
92
93         if(valid >= total)
94         {
95                 minig.minigame_flags = BD_TURN_WIN;
96                 minigame_server_sendflags(minig,MINIG_SF_UPDATE);
97         }
98 }
99
100 vector bd_get_dir(int bdir)
101 {
102         switch(bdir)
103         {
104                 case BD_DIR_UP: return '0 1 0'; // up
105                 default:
106                 case BD_DIR_DN: return '0 -1 0'; // down
107                 case BD_DIR_LF: return '-1 0 0'; // left
108                 case BD_DIR_RT: return '1 0 0'; // right
109         }
110 }
111
112 string bd_get_dir_name(int bdir)
113 {
114         switch(bdir)
115         {
116                 case BD_DIR_UP: return "u"; // up
117                 default:
118                 case BD_DIR_DN: return "d"; // down
119                 case BD_DIR_LF: return "l"; // left
120                 case BD_DIR_RT: return "r"; // right
121         }
122 }
123
124 int bd_dir_fromname(string bdir)
125 {
126         if(bdir == "up" || bdir == "u")
127                 return BD_DIR_UP; // up
128         if(bdir == "down" || bdir == "dn" || bdir == "d")
129                 return BD_DIR_DN; /// down
130         if(bdir == "left" || bdir == "lt" || bdir == "l")
131                 return BD_DIR_LF; // left
132         if(bdir == "right" || bdir == "rt" || bdir == "r")
133                 return BD_DIR_RT; // right
134
135         return BD_DIR_DN; // down
136 }
137
138 bool bd_canfill(int ttype)
139 {
140         switch(ttype)
141         {
142                 case BD_TILE_BRICK8:
143                 case BD_TILE_BRICK7:
144                 case BD_TILE_BRICK6:
145                 case BD_TILE_BRICK5:
146                 case BD_TILE_BRICK4:
147                 case BD_TILE_BRICK3:
148                 case BD_TILE_BRICK2:
149                 case BD_TILE_BRICK1: return true;
150         }
151
152         return false;
153 }
154
155 bool bd_move_dozer(entity minigame, entity dozer)
156 {
157         //if(!dozer.bd_dir)
158                 //return false; // nope!
159
160         int myx = minigame_tile_letter(dozer.netname);
161         int myy = minigame_tile_number(dozer.netname);
162
163         vector dir = bd_get_dir(dozer.bd_dir);
164
165         myx += dir.x;
166         myy += dir.y;
167
168         string newpos = minigame_tile_buildname(myx, myy);
169         entity hit = bd_find_piece(minigame, newpos, false);
170
171         if(!bd_valid_tile(newpos))
172                 return false;
173
174         if(hit)
175         switch(hit.bd_tiletype)
176         {
177                 case BD_TILE_DOZER: // wtf, but let's do this incase
178                 case BD_TILE_BRICK8:
179                 case BD_TILE_BRICK7:
180                 case BD_TILE_BRICK6:
181                 case BD_TILE_BRICK5:
182                 case BD_TILE_BRICK4:
183                 case BD_TILE_BRICK3:
184                 case BD_TILE_BRICK2:
185                 case BD_TILE_BRICK1: return false;
186                 case BD_TILE_BOULDER:
187                 {
188                         string testpos;
189                         int tx = minigame_tile_letter(hit.netname);
190                         int ty = minigame_tile_number(hit.netname);
191
192                         tx += dir.x;
193                         ty += dir.y;
194
195                         testpos = minigame_tile_buildname(tx, ty);
196                         entity testhit = bd_find_piece(minigame, testpos, false);
197
198                         if(!bd_valid_tile(testpos) || testhit)
199                                 return false;
200
201                         if(hit.netname) { strunzone(hit.netname); }
202                         hit.netname = strzone(testpos);
203                         minigame_server_sendflags(hit,MINIG_SF_UPDATE);
204                         break;
205                 }
206         }
207
208         if(dozer.netname) { strunzone(dozer.netname); }
209         dozer.netname = strzone(newpos);
210
211         return true;
212 }
213
214 // make a move
215 void bd_move(entity minigame, entity player, string dir)
216 {
217         if ( minigame.minigame_flags & BD_TURN_MOVE )
218         if ( dir )
219         {
220                 //if ( bd_valid_tile(pos) )
221                 //if ( bd_find_piece(minigame, pos, false) )
222                 {
223                         entity dozer = bd_find_dozer(minigame);
224                         if(!dozer)
225                         {
226                                 LOG_INFO("Dozer wasn't found!\n");
227                                 return; // should not happen... TODO: end match?
228                         }
229
230                         string thedir = strtolower(dir);
231                         int bdir = bd_dir_fromname(thedir);
232
233                         int moved = 0;
234                         entity e = NULL;
235                         while ( ( e = findentity(e,owner,minigame) ) )
236                                 if ( e.classname == "minigame_board_piece" && e.bd_tiletype == BD_TILE_DOZER )
237                                 {
238                                         e.bd_dir = bdir;
239
240                                         if(bd_move_dozer(minigame, e))
241                                                 ++moved;
242
243                                         minigame_server_sendflags(e,MINIG_SF_UPDATE); // update anyway
244                                 }
245
246                         if(moved)
247                                 player.bd_moves++;
248
249                         bd_check_winner(minigame);
250
251                         minigame_server_sendflags(player,BD_SF_PLAYERMOVES);
252                         minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
253                 }
254         }
255 }
256
257 // editor
258 void bd_editor_place(entity minigame, entity player, string pos, int thetile, string thedir)
259 {
260         if ( minigame.minigame_flags & BD_TURN_EDIT )
261         if ( pos && thetile )
262         {
263                 if ( bd_valid_tile(pos) )
264                 {
265                         entity found_piece = bd_find_piece(minigame, pos, false);
266                         entity targ = bd_find_piece(minigame, pos, true);
267
268                         if(found_piece.bd_tiletype == BD_TILE_DOZER && thedir != "")
269                         {
270                                 string newdir = strtolower(thedir);
271                                 int bdir = bd_dir_fromname(newdir);
272
273                                 found_piece.bd_dir = bdir;
274                                 minigame_server_sendflags(found_piece,MINIG_SF_UPDATE); // update anyway
275                                 return;
276                         }
277
278                         //entity dozer = bd_find_dozer(minigame);
279                         //if(dozer && thetile == BD_TILE_DOZER && pos != dozer.netname)
280                                 //return; // nice try
281
282                         if(found_piece || (targ && thetile != BD_TILE_BOULDER))
283                         {
284                                 entity piece = bd_find_piece(minigame, pos, false);
285                                 if(!piece) piece = bd_find_piece(minigame, pos, true);
286                                 if(!piece)
287                                         return; // how?!
288
289                                 if(piece.netname) { strunzone(piece.netname); }
290                                 delete(piece);
291                                 minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
292                                 return;
293                         }
294
295                         entity piece = msle_spawn(minigame,"minigame_board_piece");
296                         piece.team = 1;
297                         piece.netname = strzone(pos);
298                         piece.bd_tiletype = thetile;
299                         piece.bd_dir = 0;
300                         minigame_server_sendflags(piece,MINIG_SF_UPDATE);
301
302                         minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
303                 }
304         }
305 }
306
307 void bd_do_move(entity minigame, entity player, string dir, string thetile, string thedir)
308 {
309         if(minigame.minigame_flags & BD_TURN_MOVE)
310                 bd_move(minigame, player, dir);
311
312         if(minigame.minigame_flags & BD_TURN_EDIT)
313                 bd_editor_place(minigame, player, dir, stof(thetile), thedir);
314 }
315
316 void bd_fill_recurse(entity minigame, entity player, int thetype, int letter, int number)
317 {
318         string pos = minigame_tile_buildname(letter,number);
319         if(!bd_valid_tile(pos))
320                 return;
321         if(bd_find_piece(minigame, pos, false) || bd_find_piece(minigame, pos, true))
322                 return;
323
324         bd_editor_place(minigame, player, pos, thetype, "");
325
326         bd_fill_recurse(minigame, player, thetype, letter - 1, number);
327         bd_fill_recurse(minigame, player, thetype, letter + 1, number);
328         bd_fill_recurse(minigame, player, thetype, letter, number - 1);
329         bd_fill_recurse(minigame, player, thetype, letter, number + 1);
330 }
331
332 void bd_unfill_recurse(entity minigame, entity player, int thetype, int letter, int number)
333 {
334         string pos = minigame_tile_buildname(letter,number);
335         if(!bd_valid_tile(pos))
336                 return;
337
338         entity targ = bd_find_piece(minigame, pos, true);
339         entity piece = bd_find_piece(minigame, pos, false);
340
341         if(targ && thetype == targ.bd_tiletype)
342         {
343                 if(targ.netname) { strunzone(targ.netname); }
344                 delete(targ);
345         }
346         else if(piece && thetype == piece.bd_tiletype)
347         {
348                 if(piece.netname) { strunzone(piece.netname); }
349                 delete(piece);
350         }
351         else return;
352
353         bd_unfill_recurse(minigame, player, thetype, letter - 1, number);
354         bd_unfill_recurse(minigame, player, thetype, letter + 1, number);
355         bd_unfill_recurse(minigame, player, thetype, letter, number - 1);
356         bd_unfill_recurse(minigame, player, thetype, letter, number + 1);
357 }
358
359 void bd_do_fill(entity minigame, entity player, string dir, string thetile)
360 {
361 #ifdef SVQC
362         if(!player.minigame_players.bd_canedit)
363         {
364                 sprint(player.minigame_players, "You're not allowed to edit levels, sorry!\n");
365                 return;
366         }
367 #endif
368
369         if(minigame.minigame_flags & BD_TURN_EDIT)
370         {
371                 int thetype = stof(thetile);
372
373                 entity targ = bd_find_piece(minigame, dir, true);
374                 entity piece = bd_find_piece(minigame, dir, false);
375
376                 if(!bd_canfill(thetype) || (piece || targ))
377                 {
378                         int killtype = 0;
379
380                         if(targ) { killtype = targ.bd_tiletype; }
381                         if(piece) { killtype = piece.bd_tiletype; }
382
383                         if(killtype)
384                         {
385                                 int letter = minigame_tile_letter(dir);
386                                 int number = minigame_tile_number(dir);
387                                 bd_unfill_recurse(minigame, player, killtype, letter, number);
388                         }
389
390                         return;
391                 }
392
393                 int letter = minigame_tile_letter(dir);
394                 int number = minigame_tile_number(dir);
395
396                 bd_fill_recurse(minigame, player, thetype, letter, number);
397         }
398 }
399
400 void bd_reset_moves(entity minigame)
401 {
402         entity e;
403 #ifdef SVQC
404         for(e = minigame.minigame_players; e; e = e.list_next)
405 #elif defined(CSQC)
406         e = NULL;
407         while( (e = findentity(e,owner,minigame)) )
408                 if ( e.classname == "minigame_player" )
409 #endif
410                 {
411                         e.bd_moves = 0;
412                         minigame_server_sendflags(e,BD_SF_PLAYERMOVES);
413                 }
414 }
415
416 void bd_load_level(entity minigame);
417 void bd_setup_pieces(entity minigame)
418 {
419         entity e = NULL;
420         while( (e = findentity(e, owner, minigame)) )
421                 if(e.classname == "minigame_board_piece")
422                 {
423                         if(e.netname) { strunzone(e.netname); }
424                         delete(e);
425                 }
426
427         bd_load_level(minigame);
428 }
429
430 void bd_do_next_match(entity minigame, entity player)
431 {
432         minigame.minigame_flags = BD_TURN_MOVE;
433         minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
434
435         if(minigame.bd_nextlevel && minigame.bd_nextlevel != "")
436         {
437                 if(minigame.bd_levelname) { strunzone(minigame.bd_levelname); }
438                 minigame.bd_levelname = strzone(minigame.bd_nextlevel);
439         }
440
441         bd_setup_pieces(minigame);
442
443         bd_reset_moves(minigame);
444 }
445
446 void bd_set_next_match(entity minigame, string next)
447 {
448         if(minigame.bd_nextlevel) { strunzone(minigame.bd_nextlevel); }
449         minigame.bd_nextlevel = strzone(next);
450 }
451
452 void bd_next_match(entity minigame, entity player, string next)
453 {
454         if(minigame.minigame_flags & BD_TURN_WIN)
455                 bd_do_next_match(minigame, player);
456         if(minigame.minigame_flags & BD_TURN_EDIT)
457                 bd_set_next_match(minigame, next);
458 }
459
460 // request a new match
461 void bd_restart_match(entity minigame, entity player)
462 {
463         minigame.minigame_flags = BD_TURN_MOVE;
464         minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
465
466         bd_setup_pieces(minigame);
467
468         bd_reset_moves(minigame);
469 }
470
471 void bd_activate_editor(entity minigame, entity player)
472 {
473 #ifdef SVQC
474         if(!player.minigame_players.bd_canedit)
475         {
476                 sprint(player.minigame_players, "You're not allowed to edit levels, sorry!\n");
477                 return;
478         }
479 #endif
480
481         minigame.minigame_flags = BD_TURN_EDIT;
482         minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
483
484         bd_reset_moves(minigame);
485
486         bd_setup_pieces(minigame);
487 }
488
489 string bd_save_piece(entity minigame, entity e)
490 {
491         string bd_string = "";
492
493         bd_string = strcat(bd_string, "\"", e.netname, "\" ");
494         bd_string = strcat(bd_string, ftos(e.bd_tiletype), " ");
495         bd_string = strcat(bd_string, ftos(e.bd_dir));
496
497         return bd_string;
498 }
499
500 void bd_set_nextlevel(entity minigame, string s)
501 {
502         tokenize_console(s);
503
504         if(minigame.bd_nextlevel) { strunzone(minigame.bd_nextlevel); }
505         minigame.bd_nextlevel = strzone(argv(2));
506 }
507
508 int bd_fix_dir(vector dir)
509 {
510         if(dir.x == 0 && dir.y == 1) { return BD_DIR_UP; } // up
511         if(dir.x == 0 && dir.y == -1) { return BD_DIR_DN; } // down
512         if(dir.x == -1 && dir.y == 0) { return BD_DIR_LF; } // left
513         if(dir.x == 1 && dir.y == 0) { return BD_DIR_RT; } // right
514
515         return BD_DIR_DN; // down if all else fails
516 }
517
518 entity bd_load_piece(entity minigame, string s)
519 {
520         // separate pieces between the ; symbols
521         string bd_string = s;
522
523         tokenize_console(bd_string);
524
525         entity e = msle_spawn(minigame,"minigame_board_piece");
526         e.team = 1;
527         e.bd_dir = 0;
528
529         int argv_num = 0;
530         e.netname = strzone(argv(argv_num)); ++argv_num;
531         e.bd_tiletype = stof(argv(argv_num)); ++argv_num;
532         e.bd_dir = stoi(argv(argv_num)); ++argv_num;
533
534         minigame_server_sendflags(e,MINIG_SF_ALL);
535
536         return e;
537 }
538
539 bool bd_save_level(entity minigame)
540 {
541         if(minigame.bd_levelname && minigame.bd_levelname != "")
542         {
543                 int target_count = 0, boulder_count = 0;
544                 entity piece = NULL;
545                 while((piece = findentity(piece,owner,minigame)))
546                 if(piece.classname == "minigame_board_piece")
547                         if(piece.bd_tiletype == BD_TILE_BOULDER)
548                                 ++boulder_count;
549                         else if(piece.bd_tiletype == BD_TILE_TARGET)
550                                 ++target_count;
551
552                 if(boulder_count != target_count)
553                 {
554                         LOG_INFO("Not enough targets or boulders, fix your level!\n");
555                         return false;
556                 }
557
558                 // saves all objects to the database file
559                 string file_name;
560                 float file_get;
561
562                 file_name = strcat("minigames/bulldozer/storage_", minigame.bd_levelname, ".txt");
563                 file_get = fopen(file_name, FILE_WRITE);
564                 fputs(file_get, strcat("// bulldozer storage \"", minigame.bd_levelname, "\" last updated ", strftime(true, "%d-%m-%Y %H:%M:%S"), "\n"));
565
566                 if(minigame.bd_nextlevel && minigame.bd_nextlevel != "" && fexists(strcat("minigames/bulldozer/storage_", minigame.bd_nextlevel, ".txt")))
567                         fputs(file_get, strcat("nextlevel = \"", minigame.bd_nextlevel, "\"\n"));
568
569                 entity e = NULL;
570                 while ( ( e = findentity(e,owner,minigame) ) )
571                 if ( e.classname == "minigame_board_piece" )
572                 {
573                         // use a line of text for each object, listing all properties
574                         fputs(file_get, strcat(bd_save_piece(minigame, e), "\n"));
575                 }
576                 fclose(file_get);
577
578                 return true;
579         }
580
581         return false;
582 }
583
584 void bd_load_level(entity minigame)
585 {
586         // loads all items from the database file
587         string file_read, file_name;
588         float file_get;
589
590         file_name = strcat("minigames/bulldozer/storage_", minigame.bd_levelname, ".txt");
591         file_get = fopen(file_name, FILE_READ);
592         if(file_get < 0)
593         {
594                 LOG_INFO("^3BULLDOZER: ^7could not find storage file ^3", file_name, "^7, no items were loaded\n");
595         }
596         else
597         {
598                 for(;;)
599                 {
600                         file_read = fgets(file_get);
601                         if(file_read == "")
602                                 break;
603                         if(substring(file_read, 0, 2) == "//")
604                                 continue;
605                         if(substring(file_read, 0, 1) == "#")
606                                 continue;
607                         if(substring(file_read, 0, 9) == "nextlevel")
608                         {
609                                 bd_set_nextlevel(minigame, file_read);
610                                 continue;
611                         }
612
613                         entity e;
614                         e = bd_load_piece(minigame, file_read);
615                 }
616         }
617         fclose(file_get);
618 }
619
620 void bd_close_editor(entity minigame, entity player)
621 {
622 #ifdef SVQC
623         if(!player.minigame_players.bd_canedit)
624         {
625                 sprint(player.minigame_players, "You're not allowed to edit levels, sorry!\n");
626                 return;
627         }
628 #endif
629
630         entity dozer = bd_find_dozer(minigame);
631         if(!dozer)
632         {
633                 LOG_INFO("You need to place a bulldozer on the level to save it!\n");
634                 return;
635         }
636
637         if(bd_save_level(minigame))
638         {
639                 minigame.minigame_flags = BD_TURN_MOVE;
640                 minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
641         }
642         else
643         {
644                 LOG_INFO("You need to set the level name!\n");
645                 return;
646         }
647 }
648
649 #ifdef SVQC
650
651 // required function, handle server side events
652 int bd_server_event(entity minigame, string event, ...)
653 {
654         switch(event)
655         {
656                 case "start":
657                 {
658                         if(minigame.bd_levelname) { strunzone(minigame.bd_levelname); }
659                         minigame.bd_levelname = strzone(autocvar_sv_minigames_bulldozer_startlevel);
660                         bd_setup_pieces(minigame);
661                         minigame.minigame_flags = BD_TURN_MOVE;
662
663                         return true;
664                 }
665                 case "end":
666                 {
667                         entity e = NULL;
668                         while( (e = findentity(e, owner, minigame)) )
669                         if(e.classname == "minigame_board_piece")
670                         {
671                                 if(e.netname) { strunzone(e.netname); }
672                                 delete(e);
673                         }
674
675                         if(minigame.bd_nextlevel) { strunzone(minigame.bd_nextlevel); }
676                         if(minigame.bd_levelname) { strunzone(minigame.bd_levelname); }
677                         return false;
678                 }
679                 case "join":
680                 {
681                         int pl_num = minigame_count_players(minigame);
682
683                         if(pl_num >= BD_TEAMS) { return false; }
684
685                         return 1;
686                 }
687                 case "cmd":
688                 {
689                         switch(argv(0))
690                         {
691                                 case "move":
692                                         bd_do_move(minigame, ...(0,entity), ((...(1,int)) >= 2 ? argv(1) : string_null), ((...(1,int)) >= 3 ? argv(2) : string_null), ((...(1,int)) >= 4 ? argv(3) : string_null));
693                                         return true;
694                                 case "next":
695                                         bd_next_match(minigame,...(0,entity), ((...(1,int) >= 2 ? argv(1) : string_null)));
696                                         return true;
697                                 case "restart":
698                                         bd_restart_match(minigame,...(0,entity));
699                                         return true;
700                                 case "edit":
701                                         bd_activate_editor(minigame,...(0,entity));
702                                         return true;
703                                 case "save":
704                                         bd_close_editor(minigame,...(0,entity));
705                                         return true;
706                                 case "fill":
707                                         bd_do_fill(minigame, ...(0,entity), ((...(1,int)) >= 2 ? argv(1) : string_null), ((...(1,int)) >= 3 ? argv(2) : string_null));
708                                         return true;
709                         }
710
711                         return false;
712                 }
713                 case "network_send":
714                 {
715                         entity sent = ...(0,entity);
716                         int sf = ...(1,int);
717                         if ( sent.classname == "minigame_board_piece" && (sf & MINIG_SF_UPDATE) )
718                         {
719                                 int letter = minigame_tile_letter(sent.netname);
720                                 int number = minigame_tile_number(sent.netname);
721
722                                 WriteByte(MSG_ENTITY,letter);
723                                 WriteByte(MSG_ENTITY,number);
724
725                                 WriteByte(MSG_ENTITY,sent.bd_tiletype);
726
727                                 WriteByte(MSG_ENTITY,sent.bd_dir);
728                         }
729                         else if(sent.classname == "minigame_player" && (sf & BD_SF_PLAYERMOVES))
730                                 WriteShort(MSG_ENTITY,sent.bd_moves);
731                         return false;
732                 }
733         }
734
735         return false;
736 }
737
738
739 #elif defined(CSQC)
740
741 int bd_curr_tile;
742 string bd_curr_pos;
743
744 .entity bd_enemy;
745 .bool bd_hide;
746
747 vector bd_boardpos; // HUD board position
748 vector bd_boardsize;// HUD board size
749
750 string bd_get_tile_pic(int tileid)
751 {
752         switch(tileid)
753         {
754                 case BD_TILE_BOULDER: return "bd/boulder";
755                 case BD_TILE_BRICK1: return "bd/brick1";
756                 case BD_TILE_BRICK2: return "bd/brick2";
757                 case BD_TILE_BRICK3: return "bd/brick3";
758                 case BD_TILE_BRICK4: return "bd/brick4";
759                 case BD_TILE_BRICK5: return "bd/brick5";
760                 case BD_TILE_BRICK6: return "bd/brick6";
761                 case BD_TILE_BRICK7: return "bd/brick7";
762                 case BD_TILE_BRICK8: return "bd/brick8";
763                 case BD_TILE_TARGET: return "bd/target";
764                 case BD_TILE_DOZER: return "bd/dozer";
765         }
766
767         return string_null;
768 }
769
770 // Required function, draw the game board
771 void bd_hud_board(vector pos, vector mySize)
772 {
773         minigame_hud_fitsqare(pos, mySize);
774         bd_boardpos = pos;
775         bd_boardsize = mySize;
776
777         minigame_hud_simpleboard(pos,mySize,minigame_texture("bd/board"));
778
779         vector tile_size = minigame_hud_denormalize_size('1 1 0' / BD_TILE_SIZE,pos,mySize);
780         vector tile_pos;
781
782         entity e;
783         FOREACH_MINIGAME_ENTITY(e)
784         {
785                 if(e.classname == "minigame_board_piece")
786                 {
787                         if(e.bd_tiletype == BD_TILE_TARGET)
788                         {
789                                 e.bd_enemy = NULL;
790                                 e.bd_enemy = bd_find_piece(active_minigame, e.netname, false);
791                         }
792                         else if(e.bd_tiletype == BD_TILE_BOULDER)
793                         {
794                                 e.bd_hide = false; // reset either way
795                                 e.bd_hide = ((bd_find_piece(active_minigame, e.netname, true)) != NULL);
796                         }
797                 }
798         }
799         FOREACH_MINIGAME_ENTITY(e)
800         {
801                 if ( e.classname == "minigame_board_piece" )
802                 {
803                         if(e.bd_tiletype != BD_TILE_DOZER && !e.bd_hide) // hide boulders
804                         {
805                                 tile_pos = minigame_tile_pos(e.netname,BD_NUM_CNT,BD_LET_CNT);
806                                 tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize);
807
808                                 string thepiece = bd_get_tile_pic(e.bd_tiletype);
809
810                                 if(e.bd_enemy)
811                                         thepiece = "bd/boulder_target";
812
813                                 minigame_drawpic_centered( tile_pos,
814                                                 minigame_texture(thepiece),
815                                                 tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL );
816                         }
817                 }
818         }
819
820         // draw dozers on top, always
821         FOREACH_MINIGAME_ENTITY(e)
822         {
823                 if ( e.classname == "minigame_board_piece" && e.bd_tiletype == BD_TILE_DOZER )
824                 {
825                         tile_pos = minigame_tile_pos(e.netname,BD_NUM_CNT,BD_LET_CNT);
826                         tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize);
827
828                         int bdir = e.bd_dir;
829                         float theang = 0;
830
831                         switch(bdir)
832                         {
833                                 case BD_DIR_UP: theang = 0; break;
834                                 default:
835                                 case BD_DIR_DN: theang = M_PI; break;
836                                 case BD_DIR_LF: theang = M_PI * 3 / 2; break;
837                                 case BD_DIR_RT: theang = M_PI / 2; break; 
838                         }
839
840                         drawrotpic(tile_pos, theang, minigame_texture("bd/dozer"),
841                                                 tile_size, tile_size/2, '1 1 1',
842                                                 panel_fg_alpha, DRAWFLAG_NORMAL );
843                 }
844         }
845
846         if(active_minigame.minigame_flags & BD_TURN_EDIT)
847         if(bd_valid_tile(bd_curr_pos))
848         {
849                 entity piece = bd_find_piece(active_minigame, bd_curr_pos, false);
850                 entity targ = bd_find_piece(active_minigame, bd_curr_pos, true);
851                 string thepiece = ((piece || (targ && bd_curr_tile != BD_TILE_BOULDER)) ? "bd/delete" : bd_get_tile_pic(bd_curr_tile));
852
853                 tile_pos = minigame_tile_pos(bd_curr_pos,BD_LET_CNT,BD_NUM_CNT);
854                 tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize);
855                 if(bd_curr_tile == BD_TILE_DOZER)
856                 {
857                         drawrotpic(tile_pos, M_PI, minigame_texture("bd/dozer"),
858                                                 tile_size, tile_size/2, '1 1 1',
859                                                 panel_fg_alpha/2, DRAWFLAG_NORMAL );
860                 }
861                 else
862                 {
863                         minigame_drawpic_centered( tile_pos,
864                                         minigame_texture(thepiece),
865                                         tile_size, '1 1 1', panel_fg_alpha/2, DRAWFLAG_NORMAL );
866                 }
867         }
868
869         if ( (active_minigame.minigame_flags & BD_TURN_LOSS) || (active_minigame.minigame_flags & BD_TURN_WIN) )
870         {
871                 vector winfs = hud_fontsize*2;
872                 string victory_text = "Game over!";
873
874                 if(active_minigame.minigame_flags & BD_TURN_WIN)
875                         victory_text = "Well done! Click 'Next Level' to continue";
876
877                 vector win_pos = pos+eY*(mySize_y-winfs_y)/2;
878                 vector win_sz;
879                 win_sz = minigame_drawcolorcodedstring_wrapped(mySize_x,win_pos,
880                         sprintf("%s", victory_text),
881                         winfs, 0, DRAWFLAG_NORMAL, 0.5);
882
883                 drawfill(win_pos-eY*hud_fontsize_y,win_sz+2*eY*hud_fontsize_y,'0.3 0.3 1',0.8,DRAWFLAG_ADDITIVE);
884
885                 minigame_drawcolorcodedstring_wrapped(mySize_x,win_pos,
886                         sprintf("%s", victory_text),
887                         winfs, panel_fg_alpha, DRAWFLAG_NORMAL, 0.5);
888         }
889 }
890
891
892 // Required function, draw the game status panel
893 void bd_hud_status(vector pos, vector mySize)
894 {
895         HUD_Panel_DrawBg();
896         vector ts;
897         ts = minigame_drawstring_wrapped(mySize_x,pos,active_minigame.descriptor.message,
898                 hud_fontsize * 2, '0.25 0.47 0.72', panel_fg_alpha, DRAWFLAG_NORMAL,0.5);
899
900         pos_y += ts_y;
901         mySize_y -= ts_y;
902
903         vector player_fontsize = hud_fontsize * 1.75;
904         ts_y = ( mySize_y - 2*player_fontsize_y ) / BD_TEAMS;
905         ts_x = mySize_x;
906         vector mypos;
907         vector tile_size = '48 48 0';
908
909         mypos = pos;
910         drawfill(mypos,eX*mySize_x+eY*player_fontsize_y,'1 1 1',0.5,DRAWFLAG_ADDITIVE);
911         mypos_y += player_fontsize_y;
912         drawfill(mypos,eX*mySize_x+eY*tile_size_y,'1 1 1',0.25,DRAWFLAG_ADDITIVE);
913
914         entity e;
915         FOREACH_MINIGAME_ENTITY(e)
916         {
917                 if ( e.classname == "minigame_player" )
918                 {
919                         mypos = pos;
920                         minigame_drawcolorcodedstring_trunc(mySize_x,mypos,
921                                 entcs_GetName(e.minigame_playerslot-1),
922                                 player_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
923
924                         mypos_y += player_fontsize_y;
925                         string thepiece = "bd/dozer";
926                         if(active_minigame.minigame_flags & BD_TURN_EDIT)
927                                 thepiece = bd_get_tile_pic(bd_curr_tile);
928                         drawpic( mypos,
929                                         minigame_texture(thepiece),
930                                         tile_size * 0.7, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL );
931
932                         mypos_x += tile_size_x;
933
934                         drawstring(mypos,ftos(e.bd_moves),tile_size,
935                                            '0.7 0.84 1', panel_fg_alpha, DRAWFLAG_NORMAL);
936                 }
937         }
938 }
939
940 // Turn a set of flags into a help message
941 string bd_turn_to_string(int turnflags)
942 {
943         if ( turnflags & BD_TURN_LOSS )
944                 return _("Better luck next time!");
945
946         if ( turnflags & BD_TURN_WIN )
947                 if(random() > 0.5)
948                         return _("Tubular! Press \"Next Level\" to continue!");
949                 else
950                         return _("Wicked! Press \"Next Level\" to continue!");
951
952         if( turnflags & BD_TURN_EDIT )
953                 return _("Press the space bar to change your currently selected tile");
954
955         if ( turnflags & BD_TURN_MOVE )
956                 return _("Push the boulders onto the targets");
957
958         return "";
959 }
960
961 // Make the correct move
962 void bd_make_move(entity minigame, string dir)
963 {
964         if ( minigame.minigame_flags == BD_TURN_MOVE )
965         {
966                 minigame_cmd("move ", dir);
967         }
968 }
969
970 void bd_editor_make_move(entity minigame, string dir)
971 {
972         if ( minigame.minigame_flags == BD_TURN_EDIT )
973         {
974                 minigame_cmd("move ", bd_curr_pos, " ", ftos(bd_curr_tile), " ", dir);
975         }
976 }
977
978 void bd_editor_fill(entity minigame)
979 {
980         if ( minigame.minigame_flags == BD_TURN_EDIT )
981         {
982                 minigame_cmd("fill ", bd_curr_pos, " ", ftos(bd_curr_tile));
983         }
984 }
985
986 void bd_set_curr_pos(string s)
987 {
988         if ( bd_curr_pos )
989                 strunzone(bd_curr_pos);
990         if ( s )
991                 s = strzone(s);
992         bd_curr_pos = s;
993 }
994
995 bool bd_normal_move(entity minigame, int themove)
996 {
997         switch ( themove )
998         {
999                 case K_RIGHTARROW:
1000                 case K_KP_RIGHTARROW:
1001                         bd_make_move(minigame, "r");
1002                         return true;
1003                 case K_LEFTARROW:
1004                 case K_KP_LEFTARROW:
1005                         bd_make_move(minigame, "l");
1006                         return true;
1007                 case K_UPARROW:
1008                 case K_KP_UPARROW:
1009                         bd_make_move(minigame, "u");
1010                         return true;
1011                 case K_DOWNARROW:
1012                 case K_KP_DOWNARROW:
1013                         bd_make_move(minigame, "d");
1014                         return true;
1015         }
1016
1017         return false;
1018 }
1019
1020 bool bd_change_dozer_angle(entity minigame)
1021 {
1022         entity dozer = bd_find_piece(minigame, bd_curr_pos, false);
1023         if(!dozer || dozer.bd_tiletype != BD_TILE_DOZER)
1024                 return false;
1025
1026         switch(dozer.bd_dir)
1027         {
1028                 case BD_DIR_UP: dozer.bd_dir = BD_DIR_LF; break; // up -> left
1029                 default:
1030                 case BD_DIR_DN: dozer.bd_dir = BD_DIR_RT; break; // down -> right
1031                 case BD_DIR_LF: dozer.bd_dir = BD_DIR_DN; break; // left -> down
1032                 case BD_DIR_RT: dozer.bd_dir = BD_DIR_UP; break; // right -> up
1033         }
1034         string thedir = bd_get_dir_name(dozer.bd_dir);
1035
1036         bd_editor_make_move(minigame, thedir);
1037         return true;
1038 }
1039
1040 bool bd_editor_move(entity minigame, int themove)
1041 {
1042         switch ( themove )
1043         {
1044                 case K_RIGHTARROW:
1045                 case K_KP_RIGHTARROW:
1046                         if ( ! bd_curr_pos )
1047                                 bd_set_curr_pos("a3");
1048                         else
1049                                 bd_set_curr_pos(minigame_relative_tile(bd_curr_pos,1,0,BD_NUM_CNT,BD_LET_CNT));
1050                         return true;
1051                 case K_LEFTARROW:
1052                 case K_KP_LEFTARROW:
1053                         if ( ! bd_curr_pos )
1054                                 bd_set_curr_pos("c3");
1055                         else
1056                                 bd_set_curr_pos(minigame_relative_tile(bd_curr_pos,-1,0,BD_NUM_CNT,BD_LET_CNT));
1057                         return true;
1058                 case K_UPARROW:
1059                 case K_KP_UPARROW:
1060                         if ( ! bd_curr_pos )
1061                                 bd_set_curr_pos("a1");
1062                         else
1063                                 bd_set_curr_pos(minigame_relative_tile(bd_curr_pos,0,1,BD_NUM_CNT,BD_LET_CNT));
1064                         return true;
1065                 case K_DOWNARROW:
1066                 case K_KP_DOWNARROW:
1067                         if ( ! bd_curr_pos )
1068                                 bd_set_curr_pos("a3");
1069                         else
1070                                 bd_set_curr_pos(minigame_relative_tile(bd_curr_pos,0,-1,BD_NUM_CNT,BD_LET_CNT));
1071                         return true;
1072                 case K_ENTER:
1073                 case K_KP_ENTER:
1074                         bd_editor_make_move(minigame, "");
1075                         return true;
1076                 case K_SPACE:
1077                         if(bd_change_dozer_angle(minigame))
1078                                 return true;
1079                         bd_curr_tile += 1;
1080                         if(bd_curr_tile > BD_TILE_LAST)
1081                                 bd_curr_tile = 1;
1082                         return true;
1083         }
1084
1085         return false;
1086 }
1087
1088 // Required function, handle client events
1089 int bd_client_event(entity minigame, string event, ...)
1090 {
1091         switch(event)
1092         {
1093                 case "activate":
1094                 {
1095                         minigame.message = bd_turn_to_string(minigame.minigame_flags);
1096                         bd_set_curr_pos("");
1097                         bd_curr_tile = BD_TILE_BRICK1;
1098                         return false;
1099                 }
1100                 case "key_pressed":
1101                 {
1102                         if(minigame.minigame_flags & BD_TURN_MOVE)
1103                         {
1104                                 if(bd_normal_move(minigame, ...(0,int)))
1105                                         return true;
1106                         }
1107
1108                         if(minigame.minigame_flags & BD_TURN_EDIT)
1109                         {
1110                                 if(bd_editor_move(minigame, ...(0,int)))
1111                                         return true;
1112                         }
1113
1114                         return false;
1115                 }
1116                 case "mouse_pressed":
1117                 {
1118                         if(minigame.minigame_flags & BD_TURN_EDIT)
1119                         {
1120                                 if(...(0,int) == K_MOUSE1)
1121                                 {
1122                                         bd_editor_make_move(minigame, "");
1123                                         return true;
1124                                 }
1125
1126                                 if(...(0,int) == K_MOUSE2)
1127                                 {
1128                                         bd_editor_fill(minigame);
1129                                         return true;
1130                                 }
1131                         }
1132
1133                         return false;
1134                 }
1135                 case "mouse_moved":
1136                 {
1137                         if(minigame.minigame_flags & BD_TURN_EDIT)
1138                         {
1139                                 vector mouse_pos = minigame_hud_normalize(mousepos,bd_boardpos,bd_boardsize);
1140                                 bd_set_curr_pos(minigame_tile_name(mouse_pos,BD_LET_CNT,BD_NUM_CNT));
1141                                 if ( ! bd_valid_tile(bd_curr_pos) )
1142                                         bd_set_curr_pos("");
1143                         }
1144                         return true;
1145                 }
1146                 case "network_receive":
1147                 {
1148                         entity sent = ...(0,entity);
1149                         int sf = ...(1,int);
1150                         if ( sent.classname == "minigame" )
1151                         {
1152                                 if ( sf & MINIG_SF_UPDATE )
1153                                 {
1154                                         sent.message = bd_turn_to_string(sent.minigame_flags);
1155                                         //if ( sent.minigame_flags & minigame_self.team )
1156                                                 minigame_prompt();
1157                                 }
1158                         }
1159                         else if(sent.classname == "minigame_board_piece")
1160                         {
1161                                 if(sf & MINIG_SF_UPDATE)
1162                                 {
1163                                         int letter = ReadByte();
1164                                         int number = ReadByte();
1165                                         if(sent.netname) { strunzone(sent.netname); }
1166                                         sent.netname = strzone(minigame_tile_buildname(letter, number));
1167
1168                                         sent.bd_tiletype = ReadByte();
1169
1170                                         sent.bd_dir = ReadByte();
1171                                 }
1172                         }
1173                         else if(sent.classname == "minigame_player" && (sf & BD_SF_PLAYERMOVES))
1174                                 sent.bd_moves = ReadShort(); // make this a byte when possible
1175
1176                         return false;
1177                 }
1178                 case "menu_show":
1179                 {
1180                         HUD_MinigameMenu_CustomEntry(...(0,entity),_("Next Level"),"next");
1181                         HUD_MinigameMenu_CustomEntry(...(0,entity),_("Restart"),"restart");
1182                         HUD_MinigameMenu_CustomEntry(...(0,entity),_("Editor"),"edit");
1183                         HUD_MinigameMenu_CustomEntry(...(0,entity),_("Save"),"save");
1184                         return false;
1185                 }
1186                 case "menu_click":
1187                 {
1188                         if(...(0,string) == "next")
1189                                 minigame_cmd("next");
1190                         if(...(0,string) == "restart")
1191                                 minigame_cmd("restart");
1192                         if(...(0,string) == "edit")
1193                                 minigame_cmd("edit");
1194                         if(...(0,string) == "save")
1195                                 minigame_cmd("save");
1196                         return false;
1197                 }
1198         }
1199
1200         return false;
1201 }
1202
1203 #endif