2 REGISTER_MINIGAME(snake, "Snake"); // SNAAAAKE
4 const float SNAKE_TURN_MOVE = 0x0100; // the snake is moving, player must control it
5 const float SNAKE_TURN_WIN = 0x0200; // multiplayer victory
6 const float SNAKE_TURN_LOSS = 0x0400; // they did it?!
7 const float SNAKE_TURN_TYPE = 0x0f00; // turn type mask
9 const int SNAKE_TURN_TEAM = 0x000f; // turn team mask
11 const int SNAKE_SF_PLAYERSCORE = MINIG_SF_CUSTOM;
13 const int SNAKE_LET_CNT = 15;
14 const int SNAKE_NUM_CNT = 15;
16 const int SNAKE_TILE_SIZE = 15;
18 const int SNAKE_TEAMS = 6;
20 bool autocvar_sv_minigames_snake_wrap = false;
21 float autocvar_sv_minigames_snake_delay_initial = 0.7;
22 float autocvar_sv_minigames_snake_delay_multiplier = 50;
23 float autocvar_sv_minigames_snake_delay_min = 0.1;
25 int autocvar_sv_minigames_snake_lives = 3;
33 .entity snake_next, snake_last, snake_prev;
37 .int snake_lives[SNAKE_TEAMS + 1];
39 .int snake_lost_teams;
41 bool snake_alone(entity minig)
45 while ( ( e = findentity(e,owner,minig) ) )
46 if ( e.classname == "minigame_board_piece" && e.cnt == 1 )
49 return headcount <= 1;
52 // find same game piece given its tile name
53 entity snake_find_piece(entity minig, string tile)
56 while ( ( e = findentity(e,owner,minig) ) )
57 if ( e.classname == "minigame_board_piece" && e.netname == tile )
62 // find same game piece given its cnt
63 entity snake_find_cnt(entity minig, int steam, int tile)
66 while ( ( e = findentity(e,owner,minig) ) )
67 if ( e.classname == "minigame_board_piece" && e.cnt == tile && e.team == steam )
72 // check if the tile name is valid (15x15 grid)
73 bool snake_valid_tile(string tile)
77 int number = minigame_tile_number(tile);
78 int letter = minigame_tile_letter(tile);
79 return 0 <= number && number < SNAKE_NUM_CNT && 0 <= letter && letter < SNAKE_LET_CNT;
82 entity snake_find_head(entity minig, int steam)
85 while ( ( e = findentity(e,owner,minig) ) )
86 if ( e.classname == "minigame_board_piece" && e.cnt == 1 && e.team == steam )
91 void snake_new_mouse(entity minigame)
93 RandomSelection_Init();
95 for(i = 0; i < SNAKE_LET_CNT; ++i)
96 for(j = 0; j < SNAKE_NUM_CNT; ++j)
98 string pos = minigame_tile_buildname(i, j);
99 if(!snake_find_piece(minigame, pos))
100 RandomSelection_AddString(pos, 1, 1);
103 entity piece = msle_spawn(minigame,"minigame_board_piece");
105 piece.netname = strzone(RandomSelection_chosen_string);
106 minigame_server_sendflags(piece,MINIG_SF_ALL);
108 minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
111 entity snake_get_player(entity minigame, int pteam);
112 int snake_winning_team(entity minigame)
114 int winning_team = 0;
115 for(int i = 1; i <= SNAKE_TEAMS; ++i)
117 entity pl = snake_get_player(minigame, i);
118 if(pl && minigame.snake_lives[i] > 0)
129 void snake_check_winner(entity minigame)
131 if(snake_alone(minigame) && !minigame.snake_lost_teams)
134 int winner = snake_winning_team(minigame);
137 for(int i = 1; i <= SNAKE_TEAMS; ++i)
139 entity pl = snake_get_player(minigame, i);
140 if(pl && minigame.snake_lives[i] > 0)
146 minigame.minigame_flags = SNAKE_TURN_LOSS;
147 minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
153 minigame.minigame_flags = SNAKE_TURN_WIN | winner;
154 minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
158 void snake_move_head(entity minigame, entity head);
159 void snake_head_think(entity this)
161 entity minigame = this.owner;
163 if(minigame.minigame_flags & SNAKE_TURN_MOVE)
164 snake_move_head(minigame, this);
166 snake_check_winner(minigame);
168 this.nextthink = time + this.snake_delay;
171 void minigame_setup_snake(entity minigame, int pteam)
173 RandomSelection_Init();
175 for(i = 1; i < SNAKE_LET_CNT - 1; ++i)
176 for(j = 1; j < SNAKE_NUM_CNT - 1; ++j)
178 string pos = minigame_tile_buildname(i, j);
179 if(!snake_find_piece(minigame, pos))
180 RandomSelection_AddString(pos, 1, 1);
183 entity piece = msle_spawn(minigame,"minigame_board_piece");
185 piece.netname = strzone(RandomSelection_chosen_string);
187 piece.snake_next = NULL;
188 piece.snake_prev = NULL;
189 piece.snake_last = piece;
190 setthink(piece, snake_head_think);
191 piece.snake_delay = autocvar_sv_minigames_snake_delay_initial;
192 piece.nextthink = time + 0.1;
193 minigame_server_sendflags(piece,MINIG_SF_ALL);
196 void snake_setup_pieces(entity minigame)
198 snake_new_mouse(minigame);
200 minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
203 entity snake_get_player(entity minigame, int pteam)
207 for(e = minigame.minigame_players; e; e = e.list_next)
210 while( (e = findentity(e,owner,minigame)) )
211 if ( e.classname == "minigame_player" )
218 void snake_add_score(entity minigame, int pteam, int thescore)
223 entity pl = snake_get_player(minigame, pteam);
226 pl.snake_score += thescore;
227 pl.SendFlags |= SNAKE_SF_PLAYERSCORE;
232 void snake_move_body(entity minigame, entity head, bool ate_mouse)
234 for(entity e = head.snake_last; e; e = e.snake_prev)
236 if(!e || e == head) { break; }
238 entity nextpiece = e.snake_prev; // can be head
240 if(e.netname) { strunzone(e.netname); }
241 e.netname = strzone(nextpiece.netname);
242 e.snake_dir = nextpiece.snake_dir;
243 minigame_server_sendflags(e, MINIG_SF_UPDATE);
248 entity tail = head.snake_last;
250 tail.snake_tail = false;
252 int newcnt = tail.cnt + 1;
253 head.snake_delay = max(autocvar_sv_minigames_snake_delay_min, autocvar_sv_minigames_snake_delay_initial - (newcnt / autocvar_sv_minigames_snake_delay_multiplier));
254 snake_add_score(minigame, head.team, 1);
256 entity piece = msle_spawn(minigame,"minigame_board_piece");
258 piece.team = head.team;
259 piece.snake_prev = tail;
260 piece.snake_dir = tail.snake_dir;
261 piece.snake_next = NULL;
262 piece.snake_tail = true;
263 piece.netname = strzone(tail.netname);
265 tail.snake_next = piece;
266 head.snake_last = piece;
268 minigame_server_sendflags(piece,MINIG_SF_UPDATE);
270 //minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
274 void snake_eat_team(entity minigame, int pteam)
276 entity head = snake_find_head(minigame, pteam);
277 if(!head) { return; }
279 minigame.snake_lives[pteam] -= 1;
281 entity pl = snake_get_player(minigame, pteam);
283 pl.SendFlags |= SNAKE_SF_PLAYERSCORE;
286 head.nextthink = time + 1; // make sure they don't to eat us somehow
289 while ( ( e = findentity(e,owner,minigame) ) )
290 if ( e.classname == "minigame_board_piece" && e.cnt && e.team == pteam )
292 if(e.netname) { strunzone(e.netname); }
296 if(minigame.snake_lives[pteam] <= 0)
297 minigame.snake_lost_teams |= BIT(pteam);
299 if(pl && minigame.snake_lives[pteam] > 0)
300 minigame_setup_snake(minigame, pteam);
303 void snake_move_head(entity minigame, entity head)
305 if(!head.snake_dir_x && !head.snake_dir_y)
310 if(autocvar_sv_minigames_snake_wrap)
311 newpos = minigame_relative_tile(head.netname, head.snake_dir_x, head.snake_dir_y, SNAKE_NUM_CNT, SNAKE_LET_CNT);
314 int myx = minigame_tile_letter(head.netname);
315 int myy = minigame_tile_number(head.netname);
317 myx += head.snake_dir_x;
318 myy += head.snake_dir_y;
320 newpos = minigame_tile_buildname(myx, myy);
323 entity hit = snake_find_piece(minigame, newpos);
325 if(!snake_valid_tile(newpos) || (hit && hit.cnt && hit.team == head.team))
327 if(snake_alone(minigame))
329 minigame.minigame_flags = SNAKE_TURN_LOSS;
330 minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
334 snake_add_score(minigame, head.team, -1);
335 snake_eat_team(minigame, head.team);
341 bool ate_mouse = (hit && !hit.cnt);
343 // move the body first, then set the new head position?
344 snake_move_body(minigame, head, ate_mouse);
346 if(head.netname) { strunzone(head.netname); }
347 head.netname = strzone(newpos);
348 minigame_server_sendflags(head,MINIG_SF_UPDATE);
350 // above check makes sure it's not our team
353 snake_eat_team(minigame, hit.team);
354 snake_add_score(minigame, head.team, 1);
359 if(hit.netname) { strunzone(hit.netname); }
362 snake_new_mouse(minigame);
367 void snake_move(entity minigame, entity player, string dxs, string dys )
369 if ( minigame.minigame_flags & SNAKE_TURN_MOVE )
372 //if ( snake_valid_tile(pos) )
373 //if ( snake_find_piece(minigame, pos) )
375 entity head = snake_find_head(minigame, player.team);
377 return; // their head is already dead
379 int dx = ((dxs) ? bound(-1, stof(dxs), 1) : 0);
380 int dy = ((dys) ? bound(-1, stof(dys), 1) : 0);
382 int myl = minigame_tile_letter(head.netname);
383 int myn = minigame_tile_number(head.netname);
385 entity check_piece = snake_find_piece(minigame, minigame_tile_buildname(myl + dx, myn + dy));
386 if(check_piece && check_piece.cnt == 2)
389 if(head.snake_dir == '0 0 0')
390 head.nextthink = time; // TODO: make sure this can't be exploited!
391 head.snake_dir_x = dx;
392 head.snake_dir_y = dy;
393 head.snake_dir_z = 0;
394 minigame_server_sendflags(head,MINIG_SF_UPDATE);
395 minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
403 // required function, handle server side events
404 int snake_server_event(entity minigame, string event, ...)
410 snake_setup_pieces(minigame);
411 minigame.minigame_flags = SNAKE_TURN_MOVE;
412 minigame.snake_lost_teams = 0;
416 for(int i = 1; i <= SNAKE_TEAMS; ++i)
417 minigame.snake_lives[i] = autocvar_sv_minigames_snake_lives;
420 minigame.snake_lives[1] = 1;
427 while( (e = findentity(e, owner, minigame)) )
428 if(e.classname == "minigame_board_piece")
430 if(e.netname) { strunzone(e.netname); }
437 int pl_num = minigame_count_players(minigame);
439 if(pl_num >= SNAKE_TEAMS) { return false; }
441 int t = 1; // Team 1 by default
443 for(int i = 1; i <= SNAKE_TEAMS; ++i)
445 entity e = snake_get_player(minigame, i);
453 if(!snake_find_head(minigame, t) && !(minigame.snake_lost_teams & BIT(t)))
455 entity pl = ...(1,entity);
458 //pl.snake_lives = ((SNAKE_TEAMS > 1) ? autocvar_sv_minigames_snake_lives : 1);
459 // send score anyway, lives are set
460 pl.SendFlags |= SNAKE_SF_PLAYERSCORE;
462 minigame_setup_snake(minigame, t);
472 snake_move(minigame, ...(0,entity), ((...(1,int)) >= 2 ? argv(1) : string_null), ((...(1,int)) == 3 ? argv(2) : string_null));
480 entity sent = ...(0,entity);
482 if ( sent.classname == "minigame_board_piece" && (sf & MINIG_SF_UPDATE) )
484 int letter = minigame_tile_letter(sent.netname);
485 int number = minigame_tile_number(sent.netname);
487 WriteByte(MSG_ENTITY,letter);
488 WriteByte(MSG_ENTITY,number);
490 WriteByte(MSG_ENTITY,sent.cnt);
491 WriteByte(MSG_ENTITY,sent.snake_tail);
493 int dx = sent.snake_dir_x;
494 int dy = sent.snake_dir_y;
497 WriteByte(MSG_ENTITY,dx);
498 WriteByte(MSG_ENTITY,dy);
500 else if ( sent.classname == "minigame_player" && (sf & SNAKE_SF_PLAYERSCORE ) )
502 WriteLong(MSG_ENTITY,sent.snake_score);
503 WriteByte(MSG_ENTITY,max(0, minigame.snake_lives[sent.team]));
505 else if ( sent.classname == "minigame" && (sf & MINIG_SF_UPDATE ) )
507 WriteByte(MSG_ENTITY,autocvar_sv_minigames_snake_wrap);
519 vector snake_boardpos; // HUD board position
520 vector snake_boardsize;// HUD board size
524 vector snake_teamcolor(int steam)
528 case 1: return '1 0 0';
529 case 2: return '0 0 1';
530 case 3: return '1 1 0';
531 case 4: return '1 0 1';
532 case 5: return '0 1 0';
533 case 6: return '0 1 1';
539 // Required function, draw the game board
540 void snake_hud_board(vector pos, vector mySize)
542 minigame_hud_fitsqare(pos, mySize);
543 snake_boardpos = pos;
544 snake_boardsize = mySize;
546 minigame_hud_simpleboard(pos,mySize,minigame_texture("snake/board"));
548 vector tile_size = minigame_hud_denormalize_size('1 1 0' / SNAKE_TILE_SIZE,pos,mySize);
552 FOREACH_MINIGAME_ENTITY(e)
554 if ( e.classname == "minigame_board_piece" )
556 tile_pos = minigame_tile_pos(e.netname,SNAKE_NUM_CNT,SNAKE_LET_CNT);
557 tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize);
559 vector tile_color = snake_teamcolor(e.team);
561 string thepiece = "snake/mouse";
563 thepiece = "snake/body";
565 thepiece = "snake/tail";
568 int dx = minigame_tile_letter(e.netname) + e.snake_dir_x * 2;
569 int dy = minigame_tile_number(e.netname) + e.snake_dir_y * 2;
570 entity mouse = snake_find_piece(active_minigame, minigame_tile_buildname(dx, dy));
571 thepiece = "snake/head";
572 if(mouse && mouse.team != e.team)
575 int myx = minigame_tile_letter(e.netname);
576 int myy = minigame_tile_number(e.netname);
584 int newx = minigame_tile_letter(e.netname) + e.snake_dir_x;
585 int newy = minigame_tile_number(e.netname) + e.snake_dir_y;
586 string newpos = minigame_tile_buildname(newx, newy);
588 vector my_pos = minigame_tile_pos(newpos,SNAKE_NUM_CNT,SNAKE_LET_CNT);
589 my_pos = minigame_hud_denormalize(my_pos,pos,mySize);
591 drawrotpic(my_pos, myang, minigame_texture("snake/tongue"),
592 tile_size, tile_size/2, tile_color,
593 panel_fg_alpha, DRAWFLAG_NORMAL );
597 if(e.cnt == 1 || e.snake_tail)
599 vector thedir = e.snake_dir;
603 int thex = minigame_tile_letter(e.netname);
604 int they = minigame_tile_number(e.netname);
605 entity t = snake_find_cnt(active_minigame, e.team, e.cnt - 1);
606 int tx = minigame_tile_letter(t.netname);
607 int ty = minigame_tile_number(t.netname);
633 drawrotpic(tile_pos, theang, minigame_texture(thepiece),
634 tile_size, tile_size/2, tile_color,
635 panel_fg_alpha, DRAWFLAG_NORMAL );
639 minigame_drawpic_centered( tile_pos,
640 minigame_texture(thepiece),
641 tile_size, tile_color, panel_fg_alpha, DRAWFLAG_NORMAL );
646 if ( (active_minigame.minigame_flags & SNAKE_TURN_LOSS) || (active_minigame.minigame_flags & SNAKE_TURN_WIN) || (active_minigame.snake_lives[minigame_self.team] <= 0) )
648 int scores = minigame_self.snake_score;
650 vector winfs = hud_fontsize*2;
651 string scores_text, victory_text;
652 victory_text = "Game over!";
653 scores_text = strcat("Score: ", ftos(scores));
655 if(active_minigame.minigame_flags & SNAKE_TURN_WIN)
656 if((active_minigame.minigame_flags & SNAKE_TURN_TEAM) == minigame_self.team)
657 victory_text = "You win!";
658 if(active_minigame.snake_lives[minigame_self.team] <= 0)
659 victory_text = "You ran out of lives!";
661 vector win_pos = pos+eY*(mySize_y-winfs_y)/2;
663 win_sz = minigame_drawcolorcodedstring_wrapped(mySize_x,win_pos,
664 sprintf("%s %s", victory_text, scores_text),
665 winfs, 0, DRAWFLAG_NORMAL, 0.5);
667 drawfill(win_pos-eY*hud_fontsize_y,win_sz+2*eY*hud_fontsize_y,'0.3 0.3 1',0.8,DRAWFLAG_ADDITIVE);
669 minigame_drawcolorcodedstring_wrapped(mySize_x,win_pos,
670 sprintf("%s %s", victory_text, scores_text),
671 winfs, panel_fg_alpha, DRAWFLAG_NORMAL, 0.5);
676 // Required function, draw the game status panel
677 void snake_hud_status(vector pos, vector mySize)
680 vector ts = minigame_drawstring_wrapped(mySize.x, pos, active_minigame.descriptor.message,
681 hud_fontsize * 2, '0.25 0.47 0.72', panel_fg_alpha, DRAWFLAG_NORMAL,0.5);
682 ts.y += hud_fontsize.y;
686 vector player_fontsize = hud_fontsize * 1.75;
687 ts.y = player_fontsize.y + (mySize.y - SNAKE_TEAMS * player_fontsize.y) / SNAKE_TEAMS;
692 FOREACH_MINIGAME_ENTITY(e)
694 if ( e.classname == "minigame_player" )
696 mypos = pos + eY * (e.team - 1) * ts.y;
698 if (e == minigame_self)
700 const vector hl_size = '1 1 0';
701 drawfill(mypos + hl_size, ts - 2 * hl_size, snake_teamcolor(e.team), 0.25 * panel_fg_alpha, DRAWFLAG_ADDITIVE);
702 drawborderlines(hl_size.x, mypos + hl_size, ts - 2 * hl_size, snake_teamcolor(e.team), panel_fg_alpha, DRAWFLAG_NORMAL);
705 drawfill(mypos, ts, snake_teamcolor(e.team), 0.25 * panel_fg_alpha, DRAWFLAG_ADDITIVE);
707 minigame_drawcolorcodedstring_trunc(mySize.x - hud_fontsize.x * 0.5, mypos + eX * hud_fontsize.x * 0.25,
708 entcs_GetName(e.minigame_playerslot - 1),
709 player_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
711 mypos.y += player_fontsize.y;
712 drawstring_aspect(mypos, ftos(e.snake_score), ts - eY * player_fontsize.y - eX * ts.x * (3 / 4),
713 '0.7 0.84 1', panel_fg_alpha, DRAWFLAG_NORMAL);
714 drawstring_aspect(mypos + eX * ts.x * (1 / 4), strcat("1UP: ", ftos(active_minigame.snake_lives[e.team])), ts - eY * player_fontsize.y - eX * ts.x * (1 / 4),
715 '1 0.44 0.54', panel_fg_alpha, DRAWFLAG_NORMAL);
720 // Turn a set of flags into a help message
721 string snake_turn_to_string(int turnflags)
723 if ( turnflags & SNAKE_TURN_LOSS )
724 return _("Game over!");
726 if ( turnflags & SNAKE_TURN_WIN )
728 if ( (turnflags&SNAKE_TURN_TEAM) != minigame_self.team )
729 return _("You ran out of lives!");
730 return _("You win!");
733 if(active_minigame.snake_lives[minigame_self.team] <= 0)
734 return _("You ran out of lives!");
736 if ( (snake_find_head(active_minigame, minigame_self.team)).snake_dir == '0 0 0' )
737 return _("Press an arrow key to begin the game");
739 if ( turnflags & SNAKE_TURN_MOVE )
741 return _("Avoid the snake's body, collect the mice!");
743 return _("Avoid the screen edges and the snake's body, collect the mice!");
748 // Make the correct move
749 void snake_set_direction(entity minigame, int dx, int dy)
751 //if ( minigame.minigame_flags == SNAKE_TURN_MOVE )
753 minigame_cmd("move ",ftos(dx), " ", ftos(dy));
757 // Required function, handle client events
758 int snake_client_event(entity minigame, string event, ...)
764 minigame.message = snake_turn_to_string(minigame.minigame_flags);
769 //if((minigame.minigame_flags & SNAKE_TURN_TEAM) == minigame_self.team)
771 switch ( ...(0,int) )
774 case K_KP_RIGHTARROW:
775 snake_set_direction(minigame, 1, 0);
779 snake_set_direction(minigame, -1, 0);
783 snake_set_direction(minigame, 0, 1);
787 snake_set_direction(minigame, 0, -1);
794 case "network_receive":
796 entity sent = ...(0,entity);
798 if ( sent.classname == "minigame" )
800 if ( sf & MINIG_SF_UPDATE )
802 snake_wrap = ReadByte();
803 sent.message = snake_turn_to_string(sent.minigame_flags);
804 //if ( sent.minigame_flags & minigame_self.team )
808 else if(sent.classname == "minigame_board_piece")
810 if(sf & MINIG_SF_UPDATE)
812 int letter = ReadByte();
813 int number = ReadByte();
814 if(sent.netname) { strunzone(sent.netname); }
815 sent.netname = strzone(minigame_tile_buildname(letter, number));
817 sent.cnt = ReadByte();
818 sent.snake_tail = ReadByte();
826 sent.snake_dir_x = dx;
827 sent.snake_dir_y = dy;
828 sent.snake_dir_z = 0;
831 else if ( sent.classname == "minigame_player" && (sf & SNAKE_SF_PLAYERSCORE ) )
833 sent.snake_score = ReadLong();
834 minigame.snake_lives[sent.team] = ReadByte();