A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Originally by Joshua Oreman, improved by Prashant Varanasi
11 * Ported to Rockbox by Ben Basha (Paprica)
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ****************************************************************************/
22
23#include "plugin.h"
24#include "lib/xlcd.h"
25#include "lib/configfile.h"
26#include "lib/helper.h"
27#include "lib/playback_control.h"
28
29
30
31/*
32Still To do:
33 - Make original speed and further increases in speed depend more on screen size
34 - attempt to make the tunnels get narrower as the game goes on
35 - make the chopper look better, maybe a picture, and scale according
36 to screen size
37 - use textures for the color screens for background and terrain,
38 eg stars on background
39 - allow choice of different levels [later: different screen themes]
40 - better high score handling, improved screen etc.
41*/
42
43#if (CONFIG_KEYPAD == IRIVER_H100_PAD) || (CONFIG_KEYPAD == IRIVER_H300_PAD)
44
45#define QUIT BUTTON_OFF
46#define ACTION BUTTON_UP
47#define ACTION2 BUTTON_SELECT
48#define ACTIONTEXT "SELECT"
49
50#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
51 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
52 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
53
54#define QUIT BUTTON_MENU
55#define ACTION BUTTON_SELECT
56#define ACTIONTEXT "SELECT"
57
58#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD /* grayscale at the moment */
59
60#define QUIT BUTTON_POWER
61#define ACTION BUTTON_UP
62#define ACTION2 BUTTON_SELECT
63#define ACTIONTEXT "SELECT"
64
65#elif CONFIG_KEYPAD == IRIVER_H10_PAD
66#define QUIT BUTTON_POWER
67#define ACTION BUTTON_RIGHT
68#define ACTIONTEXT "RIGHT"
69
70#elif (CONFIG_KEYPAD == SANSA_E200_PAD) || \
71 (CONFIG_KEYPAD == SANSA_C200_PAD) || \
72 (CONFIG_KEYPAD == SANSA_CLIP_PAD) || \
73 (CONFIG_KEYPAD == SANSA_M200_PAD) || \
74 (CONFIG_KEYPAD == SANSA_CONNECT_PAD)
75#define QUIT BUTTON_POWER
76#define ACTION BUTTON_SELECT
77#define ACTIONTEXT "SELECT"
78
79#elif (CONFIG_KEYPAD == SANSA_FUZE_PAD)
80#define QUIT BUTTON_HOME
81#define ACTION BUTTON_SELECT
82#define ACTIONTEXT "SELECT"
83
84#elif CONFIG_KEYPAD == GIGABEAT_PAD
85#define QUIT BUTTON_MENU
86#define ACTION BUTTON_SELECT
87#define ACTIONTEXT "SELECT"
88
89#elif CONFIG_KEYPAD == GIGABEAT_S_PAD \
90 || CONFIG_KEYPAD == SAMSUNG_YPR0_PAD
91#define QUIT BUTTON_BACK
92#define ACTION BUTTON_SELECT
93#define ACTION2 BUTTON_MENU
94#define ACTIONTEXT "SELECT"
95
96#elif CONFIG_KEYPAD == MROBE100_PAD
97#define QUIT BUTTON_POWER
98#define ACTION BUTTON_SELECT
99#define ACTIONTEXT "SELECT"
100
101#elif CONFIG_KEYPAD == IAUDIO_M3_PAD
102#define QUIT BUTTON_RC_REC
103#define ACTION BUTTON_RC_PLAY
104#define ACTION2 BUTTON_RC_MODE
105#define ACTIONTEXT "PLAY"
106
107#elif CONFIG_KEYPAD == COWON_D2_PAD
108#define QUIT BUTTON_POWER
109#define ACTION2 BUTTON_PLUS
110
111#elif CONFIG_KEYPAD == CREATIVEZVM_PAD
112#define QUIT BUTTON_BACK
113#define ACTION BUTTON_UP
114#define ACTION2 BUTTON_MENU
115#define ACTIONTEXT "UP"
116
117#elif CONFIG_KEYPAD == CREATIVE_ZENXFI3_PAD
118#define QUIT BUTTON_POWER
119#define ACTION BUTTON_UP
120#define ACTION2 BUTTON_MENU
121#define ACTIONTEXT "UP"
122
123#elif CONFIG_KEYPAD == PHILIPS_HDD1630_PAD
124#define QUIT BUTTON_POWER
125#define ACTION BUTTON_MENU
126#define ACTION2 BUTTON_SELECT
127#define ACTIONTEXT "MENU"
128
129#elif CONFIG_KEYPAD == PHILIPS_HDD6330_PAD
130#define QUIT BUTTON_POWER
131#define ACTION BUTTON_MENU
132#define ACTION2 BUTTON_PLAY
133#define ACTIONTEXT "MENU"
134
135#elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD
136#define QUIT BUTTON_POWER
137#define ACTION BUTTON_MENU
138#define ACTION2 BUTTON_PLAY
139#define ACTIONTEXT "MENU"
140
141#elif CONFIG_KEYPAD == ONDAVX747_PAD || \
142CONFIG_KEYPAD == ONDAVX777_PAD || \
143CONFIG_KEYPAD == MROBE500_PAD
144#define QUIT BUTTON_POWER
145
146#elif (CONFIG_KEYPAD == SAMSUNG_YH820_PAD) || \
147 (CONFIG_KEYPAD == SAMSUNG_YH92X_PAD)
148#define QUIT BUTTON_LEFT
149#define ACTION BUTTON_RIGHT
150#define ACTION2 BUTTON_UP
151#define ACTIONTEXT "RIGHT or UP"
152
153#elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
154#define QUIT BUTTON_REC
155#define ACTION BUTTON_PLAY
156#define ACTION2 BUTTON_UP
157#define ACTIONTEXT "PLAY"
158
159#elif CONFIG_KEYPAD == MPIO_HD200_PAD
160#define QUIT (BUTTON_REC|BUTTON_PLAY)
161#define ACTION BUTTON_FUNC
162#define ACTIONTEXT "FUNC"
163
164#elif CONFIG_KEYPAD == MPIO_HD300_PAD
165#define QUIT (BUTTON_MENU|BUTTON_REPEAT)
166#define ACTION BUTTON_ENTER
167#define ACTIONTEXT "ENTER"
168
169#elif CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD
170#define QUIT BUTTON_POWER
171#define ACTION BUTTON_SELECT
172#define ACTIONTEXT "SELECT"
173
174#elif (CONFIG_KEYPAD == HM60X_PAD) || \
175 (CONFIG_KEYPAD == HM801_PAD)
176#define QUIT BUTTON_POWER
177#define ACTION BUTTON_SELECT
178#define ACTIONTEXT "SELECT"
179
180#elif CONFIG_KEYPAD == SONY_NWZ_PAD
181#define QUIT BUTTON_BACK
182#define ACTION BUTTON_PLAY
183#define ACTIONTEXT "PLAY"
184
185#elif CONFIG_KEYPAD == CREATIVE_ZEN_PAD
186#define QUIT BUTTON_BACK
187#define ACTION BUTTON_SELECT
188#define ACTIONTEXT "Select"
189
190#elif CONFIG_KEYPAD == AGPTEK_ROCKER_PAD
191#define QUIT BUTTON_POWER
192#define ACTION BUTTON_SELECT
193#define ACTIONTEXT "Select"
194
195#elif CONFIG_KEYPAD == XDUOO_X3_PAD || CONFIG_KEYPAD == XDUOO_X3II_PAD || CONFIG_KEYPAD == XDUOO_X20_PAD || CONFIG_KEYPAD == FIIO_M3K_LINUX_PAD || CONFIG_KEYPAD == IHIFI_770_PAD || CONFIG_KEYPAD == IHIFI_800_PAD || CONFIG_KEYPAD == EROSQ_PAD
196#define QUIT BUTTON_POWER
197#define ACTION BUTTON_PLAY
198#define ACTIONTEXT "PLAY"
199
200#elif CONFIG_KEYPAD == FIIO_M3K_PAD
201#define QUIT BUTTON_POWER
202#define ACTION BUTTON_PLAY
203#define ACTIONTEXT "PLAY"
204
205#elif CONFIG_KEYPAD == SDL_PAD
206#define QUIT BUTTON_TOPLEFT
207#define ACTION BUTTON_TOPMIDDLE
208#define ACTION2 BUTTON_BOTTOMMIDDLE
209#define ACTIONTEXT "SELECT"
210#elif CONFIG_KEYPAD == MA_PAD
211#define QUIT BUTTON_BACK
212#define ACTION BUTTON_PLAY
213#define ACTIONTEXT "PLAY"
214
215#elif CONFIG_KEYPAD == RG_NANO_PAD
216#define QUIT BUTTON_START
217#define ACTION BUTTON_A
218#define ACTION2 BUTTON_B
219#define ACTIONTEXT "A"
220
221#elif !defined(HAVE_TOUCHSCREEN)
222#error No keymap defined!
223#endif
224
225#ifdef HAVE_TOUCHSCREEN
226#ifndef QUIT
227#define QUIT BUTTON_TOPLEFT
228#endif
229#ifndef ACTION
230#define ACTION BUTTON_BOTTOMLEFT
231#endif
232#ifndef ACTION2
233#define ACTION2 BUTTON_BOTTOMRIGHT
234#endif
235#ifndef ACTIONTEXT
236#define ACTIONTEXT "BOTTOMRIGHT"
237#endif
238#endif
239
240#define NUMBER_OF_BLOCKS 8
241#define NUMBER_OF_PARTICLES 3
242#define MAX_TERRAIN_NODES 15
243
244#define LEVEL_MODE_NORMAL 0
245#define LEVEL_MODE_STEEP 1
246
247#if LCD_HEIGHT <= 64
248#define CYCLES 100
249static inline int SCALE(int x)
250{
251 return x == 1 ? x : x >> 1;
252}
253#define SIZE 2
254#else
255#define CYCLES 60
256#define SCALE(x) (x)
257#define SIZE 1
258#endif
259
260/* in 10 milisecond (ticks) */
261#define CYCLETIME ((CYCLES*HZ)/1000)
262
263/*Chopper's local variables to track the terrain position etc*/
264static int chopCounter;
265static int iRotorOffset;
266static int iScreenX;
267static int iScreenY;
268static int iPlayerPosX;
269static int iPlayerPosY;
270static int iCameraPosX;
271static int iPlayerSpeedX;
272static int iPlayerSpeedY;
273static int iLastBlockPlacedPosX;
274static int iGravityTimerCountdown;
275static int iPlayerAlive;
276static int iLevelMode, iCurrLevelMode;
277static int blockh,blockw;
278static int highscore;
279static int score;
280
281#define CFG_FILE "chopper.cfg"
282#define MAX_POINTS 50000
283static struct configdata config[] =
284{
285 {TYPE_INT, 0, MAX_POINTS, { .int_p = &highscore }, "highscore", NULL}
286};
287
288struct CBlock
289{
290 int iWorldX;
291 int iWorldY;
292
293 int iSizeX;
294 int iSizeY;
295
296 int bIsActive;
297};
298
299struct CParticle
300{
301 int iWorldX;
302 int iWorldY;
303
304 int iSpeedX;
305 int iSpeedY;
306
307 int bIsActive;
308};
309
310struct CTerrainNode
311{
312 int x;
313 int y;
314};
315
316struct CTerrain
317{
318 struct CTerrainNode mNodes[MAX_TERRAIN_NODES];
319 int iNodesCount;
320 int iLastNodePlacedPosX;
321};
322
323struct CBlock mBlocks[NUMBER_OF_BLOCKS];
324struct CParticle mParticles[NUMBER_OF_PARTICLES];
325
326struct CTerrain mGround;
327struct CTerrain mRoof;
328
329/*Function declarations*/
330static void chopDrawParticle(struct CParticle *mParticle);
331static void chopDrawBlock(struct CBlock *mBlock);
332static void chopRenderTerrain(struct CTerrain *ter, bool isground);
333static void chopper_load(bool newgame);
334
335static void chopDrawPlayer(int x,int y) /* These are SCREEN coords, not world!*/
336{
337
338#if LCD_DEPTH > 2
339 rb->lcd_set_foreground(LCD_RGBPACK(50,50,200));
340#elif LCD_DEPTH == 2
341 rb->lcd_set_foreground(LCD_DARKGRAY);
342#endif
343 rb->lcd_fillrect(SCALE(x+6), SCALE(y+2), SCALE(12), SCALE(9));
344 rb->lcd_fillrect(SCALE(x-3), SCALE(y+6), SCALE(20), SCALE(3));
345
346#if LCD_DEPTH > 2
347 rb->lcd_set_foreground(LCD_RGBPACK(50,50,50));
348#elif LCD_DEPTH == 2
349 rb->lcd_set_foreground(LCD_DARKGRAY);
350#endif
351 rb->lcd_fillrect(SCALE(x+10), SCALE(y), SCALE(2), SCALE(3));
352 rb->lcd_fillrect(SCALE(x+10), SCALE(y), SCALE(1), SCALE(3));
353
354#if LCD_DEPTH > 2
355 rb->lcd_set_foreground(LCD_RGBPACK(40,40,100));
356#elif LCD_DEPTH == 2
357 rb->lcd_set_foreground(LCD_BLACK);
358#endif
359 rb->lcd_drawline(SCALE(x), SCALE(y+iRotorOffset), SCALE(x+20),
360 SCALE(y-iRotorOffset));
361
362#if LCD_DEPTH > 2
363 rb->lcd_set_foreground(LCD_RGBPACK(20,20,50));
364#elif LCD_DEPTH == 2
365 rb->lcd_set_foreground(LCD_BLACK);
366#endif
367 rb->lcd_fillrect(SCALE(x - 2), SCALE(y + 5), SCALE(2), SCALE(5));
368
369}
370
371static void chopClearTerrain(struct CTerrain *ter)
372{
373 ter->iNodesCount = 0;
374}
375
376
377static int iR(int low,int high)
378{
379 return low+rb->rand()%(high-low+1);
380}
381
382static void chopCopyTerrain(struct CTerrain *src, struct CTerrain *dest,
383 int xOffset,int yOffset)
384{
385 int i=0;
386
387 while(i < src->iNodesCount)
388 {
389 dest->mNodes[i].x = src->mNodes[i].x + xOffset;
390 dest->mNodes[i].y = src->mNodes[i].y + yOffset;
391
392 i++;
393 }
394
395 dest->iNodesCount = src->iNodesCount;
396 dest->iLastNodePlacedPosX = src->iLastNodePlacedPosX;
397
398}
399
400static void chopAddTerrainNode(struct CTerrain *ter, int x, int y)
401{
402 int i=0;
403
404 if(ter->iNodesCount + 1 >= MAX_TERRAIN_NODES)
405 {
406 /* DEBUGF("ERROR: Not enough nodes!\n"); */
407 return;
408 }
409
410 ter->iNodesCount++;
411
412 i = ter->iNodesCount - 1;
413
414 ter->mNodes[i].x = x;
415 ter->mNodes[i].y= y;
416
417 ter->iLastNodePlacedPosX = x;
418
419}
420
421static void chopTerrainNodeDeleteAndShift(struct CTerrain *ter,int nodeIndex)
422{
423 int i=nodeIndex;
424
425 while( i < ter->iNodesCount )
426 {
427 ter->mNodes[i - 1] = ter->mNodes[i];
428 i++;
429 }
430
431 ter->iNodesCount--;
432
433
434}
435
436static int chopUpdateTerrainRecycling(struct CTerrain *ter)
437{
438 int i=1;
439 int iNewNodePos,g,v;
440 while(i < ter->iNodesCount)
441 {
442
443 if( iCameraPosX > ter->mNodes[i].x)
444 {
445
446 chopTerrainNodeDeleteAndShift(ter,i);
447
448 iNewNodePos = ter->iLastNodePlacedPosX + 50;
449 g = iScreenY - 10;
450
451 v = 3*iPlayerSpeedX;
452 if(v>50)
453 v=50;
454 if(iCurrLevelMode == LEVEL_MODE_STEEP)
455 v*=5;
456
457 chopAddTerrainNode(ter,iNewNodePos,g - iR(-v,v));
458 }
459
460 i++;
461
462 }
463
464 return 1;
465}
466
467static int chopTerrainHeightAtPoint(struct CTerrain *ter, int pX)
468{
469
470 int iNodeIndexOne=0,iNodeIndexTwo=0, h, terY1, terY2, terX2, a, b;
471 float c,d;
472
473 int i=0;
474 for(i=1;i<MAX_TERRAIN_NODES;i++)
475 {
476 if(ter->mNodes[i].x > pX)
477 {
478 iNodeIndexOne = i - 1;
479 break;
480 }
481
482 }
483
484 iNodeIndexTwo = iNodeIndexOne + 1;
485 terY1 = ter->mNodes[iNodeIndexOne].y;
486 terY2 = ter->mNodes[iNodeIndexTwo].y;
487
488 /* terX1 = 0; */
489 terX2 = ter->mNodes[iNodeIndexTwo].x - ter->mNodes[iNodeIndexOne].x;
490
491 pX-= ter->mNodes[iNodeIndexOne].x;
492
493 a = terY2 - terY1;
494 b = terX2;
495 c = pX;
496 d = (c/b) * a;
497
498 h = d + terY1;
499
500 return h;
501
502}
503
504static int chopPointInTerrain(struct CTerrain *ter, int pX, int pY, int iTestType)
505{
506 int h = chopTerrainHeightAtPoint(ter, pX);
507
508 if(iTestType == 0)
509 return (pY > h);
510 else
511 return (pY < h);
512}
513
514static void chopAddBlock(int x,int y,int sx,int sy, int indexOverride)
515{
516 int i=0;
517
518 if(indexOverride < 0)
519 {
520 while(mBlocks[i].bIsActive && i < NUMBER_OF_BLOCKS)
521 i++;
522 if(i==NUMBER_OF_BLOCKS)
523 {
524 DEBUGF("No blocks!\n");
525 return;
526 }
527 }
528 else
529 i = indexOverride;
530
531 mBlocks[i].bIsActive = 1;
532 mBlocks[i].iWorldX = x;
533 mBlocks[i].iWorldY = y;
534 mBlocks[i].iSizeX = sx;
535 mBlocks[i].iSizeY = sy;
536
537 iLastBlockPlacedPosX = x;
538}
539
540static void chopAddParticle(int x,int y,int sx,int sy)
541{
542 for(int i = 0; i < NUMBER_OF_PARTICLES; ++i)
543 {
544 if(!mParticles[i].bIsActive)
545 {
546 mParticles[i].bIsActive = 1;
547 mParticles[i].iWorldX = x;
548 mParticles[i].iWorldY = y;
549 mParticles[i].iSpeedX = sx;
550 mParticles[i].iSpeedY = sy;
551 return;
552 }
553 }
554}
555
556static void chopGenerateBlockIfNeeded(void)
557{
558 int i=0;
559 int DistSpeedX = iPlayerSpeedX * 5;
560 if(DistSpeedX<200) DistSpeedX = 200;
561
562 while(i < NUMBER_OF_BLOCKS)
563 {
564 if(!mBlocks[i].bIsActive)
565 {
566 int iX,iY,sX,sY;
567
568 iX = iLastBlockPlacedPosX + (350-DistSpeedX);
569 sX = blockw;
570
571 iY = iR(0,iScreenY);
572 sY = blockh + iR(1,blockh/3);
573
574 chopAddBlock(iX,iY,sX,sY,i);
575 }
576
577 i++;
578 }
579
580}
581
582static int chopBlockCollideWithPlayer(struct CBlock *mBlock)
583{
584 int px = iPlayerPosX;
585 int py = iPlayerPosY;
586
587 int x = mBlock->iWorldX-17;
588 int y = mBlock->iWorldY-11;
589
590 int x2 = x + mBlock->iSizeX+17;
591 int y2 = y + mBlock->iSizeY+11;
592
593 if(px>x && px<x2)
594 {
595 if(py>y && py<y2)
596 {
597 return 1;
598 }
599 }
600
601 return 0;
602}
603
604static int chopBlockOffscreen(struct CBlock *mBlock)
605{
606 if(mBlock->iWorldX + mBlock->iSizeX < iCameraPosX)
607 return 1;
608 else
609 return 0;
610}
611
612static int chopParticleOffscreen(struct CParticle *mParticle)
613{
614 if (mParticle->iWorldX < iCameraPosX || mParticle->iWorldY < 0 ||
615 mParticle->iWorldY > iScreenY || mParticle->iWorldX > iCameraPosX +
616 iScreenX)
617 {
618 return 1;
619 }
620 else
621 return 0;
622}
623
624static void chopKillPlayer(void)
625{
626 int i;
627
628 for (i = 0; i < NUMBER_OF_PARTICLES; i++) {
629 mParticles[i].bIsActive = 0;
630 chopAddParticle(iPlayerPosX + iR(0,20), iPlayerPosY + iR(0,20),
631 iR(-2,2), iR(-2,2));
632 }
633
634 iPlayerAlive--;
635
636 if (iPlayerAlive == 0) {
637 rb->splash(HZ, "Game Over");
638
639 if (score > highscore) {
640 char scoretext[30];
641 highscore = score;
642 rb->snprintf(scoretext, sizeof(scoretext), "New High Score: %d",
643 highscore);
644 rb->splash(HZ*2, scoretext);
645 }
646 } else
647 chopper_load(false);
648}
649
650static void chopDrawTheWorld(void)
651{
652 int i=0;
653
654 while(i < NUMBER_OF_BLOCKS)
655 {
656 if(mBlocks[i].bIsActive)
657 {
658 if(chopBlockOffscreen(&mBlocks[i]) == 1)
659 mBlocks[i].bIsActive = 0;
660 else
661 chopDrawBlock(&mBlocks[i]);
662 }
663
664 i++;
665 }
666
667 i=0;
668
669 while(i < NUMBER_OF_PARTICLES)
670 {
671 if(mParticles[i].bIsActive)
672 {
673 if(chopParticleOffscreen(&mParticles[i]) == 1)
674 mParticles[i].bIsActive = 0;
675 else
676 chopDrawParticle(&mParticles[i]);
677 }
678
679 i++;
680 }
681
682 chopRenderTerrain(&mGround, true);
683 chopRenderTerrain(&mRoof, false);
684
685}
686
687static void chopDrawParticle(struct CParticle *mParticle)
688{
689
690 int iPosX = (mParticle->iWorldX - iCameraPosX);
691 int iPosY = (mParticle->iWorldY);
692#if LCD_DEPTH > 2
693 rb->lcd_set_foreground(LCD_RGBPACK(192,192,192));
694#elif LCD_DEPTH == 2
695 rb->lcd_set_foreground(LCD_LIGHTGRAY);
696#endif
697 rb->lcd_fillrect(SCALE(iPosX), SCALE(iPosY), SCALE(3), SCALE(3));
698
699}
700
701static void chopDrawScene(void)
702{
703 char s[30];
704 int w;
705#if LCD_DEPTH > 2
706 rb->lcd_set_background(LCD_BLACK);
707#elif LCD_DEPTH == 2
708 rb->lcd_set_background(LCD_WHITE);
709#endif
710 rb->lcd_clear_display();
711 chopDrawTheWorld();
712 chopDrawPlayer(iPlayerPosX - iCameraPosX, iPlayerPosY);
713
714 score = -20 + iPlayerPosX/3;
715
716#if LCD_DEPTH == 1
717 rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
718#else
719 rb->lcd_set_drawmode(DRMODE_FG);
720#endif
721
722#if LCD_DEPTH > 2
723 rb->lcd_set_foreground(LCD_BLACK);
724#elif LCD_DEPTH == 2
725 rb->lcd_set_foreground(LCD_WHITE);
726#endif
727
728#if LCD_WIDTH <= 128
729 rb->snprintf(s, sizeof(s), "Dist: %d", score);
730#else
731 rb->snprintf(s, sizeof(s), "Distance: %d", score);
732#endif
733 rb->lcd_getstringsize(s, &w, NULL);
734 rb->lcd_putsxy(2, 2, s);
735 if (score < highscore)
736 {
737 int w2;
738#if LCD_WIDTH <= 128
739 rb->snprintf(s, sizeof(s), "Hi: %d", highscore);
740#else
741 rb->snprintf(s, sizeof(s), "Best: %d", highscore);
742#endif
743 rb->lcd_getstringsize(s, &w2, NULL);
744 if (LCD_WIDTH - 2 - w2 > w + 2)
745 rb->lcd_putsxy(LCD_WIDTH - 2 - w2, 2, s);
746 }
747 rb->lcd_set_drawmode(DRMODE_SOLID);
748
749 rb->lcd_update();
750}
751
752static bool _ingame;
753static int chopMenuCb(int action,
754 const struct menu_item_ex *this_item,
755 struct gui_synclist *this_list)
756{
757 (void)this_list;
758 if(action == ACTION_REQUEST_MENUITEM
759 && !_ingame && ((intptr_t)this_item)==0)
760 return ACTION_EXIT_MENUITEM;
761 return action;
762}
763static int chopMenu(int menunum)
764{
765 int result = 0;
766 int res = 0;
767 bool menu_quit = false;
768
769 static const struct opt_items levels[2] = {
770 { "Normal", -1 },
771 { "Steep", -1 },
772 };
773
774 MENUITEM_STRINGLIST(menu,"Chopper Menu",chopMenuCb,
775 "Resume Game","Start New Game",
776 "Level","Playback Control","Quit");
777 _ingame = (menunum!=0);
778
779#ifdef HAVE_LCD_COLOR
780 rb->lcd_set_foreground(LCD_WHITE);
781 rb->lcd_set_background(LCD_BLACK);
782#elif LCD_DEPTH == 2
783 rb->lcd_set_foreground(LCD_BLACK);
784 rb->lcd_set_background(LCD_WHITE);
785#endif
786
787 rb->lcd_clear_display();
788 rb->button_clear_queue();
789
790 while (!menu_quit) {
791 switch(rb->do_menu(&menu, &result, NULL, false))
792 {
793 case 0: /* Resume Game */
794 menu_quit=true;
795 res = -1;
796 break;
797 case 1: /* Start New Game */
798 menu_quit=true;
799 chopper_load(true);
800 res = -1;
801 break;
802 case 2:
803 rb->set_option("Level", &iLevelMode, RB_INT, levels, 2, NULL);
804 break;
805 case 3:
806 playback_control(NULL);
807 break;
808 case 4:
809 menu_quit=true;
810 res = PLUGIN_OK;
811 break;
812 case MENU_ATTACHED_USB:
813 menu_quit=true;
814 res = PLUGIN_USB_CONNECTED;
815 break;
816 }
817 }
818 rb->lcd_clear_display();
819 return res;
820}
821
822static int chopGameLoop(void)
823{
824 int move_button, ret;
825 bool exit=false;
826 bool showsplash=true;
827 int end, i=0, bdelay=0, last_button=BUTTON_NONE;
828
829 if (chopUpdateTerrainRecycling(&mGround) == 1)
830 /* mirror the sky if we've changed the ground */
831 chopCopyTerrain(&mGround, &mRoof, 0, - ( (iScreenY * 3) / 4));
832
833 ret = chopMenu(0);
834 if (ret != -1)
835 return PLUGIN_OK;
836
837 while (!exit) {
838
839 end = *rb->current_tick + CYCLETIME;
840
841 if(chopUpdateTerrainRecycling(&mGround) == 1)
842 /* mirror the sky if we've changed the ground */
843 chopCopyTerrain(&mGround, &mRoof, 0, - ( (iScreenY * 3) / 4));
844
845 iRotorOffset = iR(-1,1);
846
847 /* We need to have this here so particles move when we're dead */
848
849 for (i=0; i < NUMBER_OF_PARTICLES; i++)
850 if(mParticles[i].bIsActive == 1)
851 {
852 mParticles[i].iWorldX += mParticles[i].iSpeedX;
853 mParticles[i].iWorldY += mParticles[i].iSpeedY;
854 }
855
856 /* Redraw the main window: */
857 chopDrawScene();
858
859
860 iGravityTimerCountdown--;
861
862 if(iGravityTimerCountdown <= 0)
863 {
864 iGravityTimerCountdown = 3;
865 chopAddParticle(iPlayerPosX, iPlayerPosY+5, 0, 0);
866 }
867
868 if(iCurrLevelMode == LEVEL_MODE_NORMAL)
869 chopGenerateBlockIfNeeded();
870
871 if (showsplash) {
872 chopDrawScene();
873 rb->splash(HZ, "Get Ready!");
874 showsplash = false;
875 }
876
877 move_button=rb->button_status();
878 if (rb->button_get(false) == QUIT) {
879 ret = chopMenu(1);
880 if (ret != -1)
881 return PLUGIN_OK;
882 showsplash = true;
883 bdelay = 0;
884 last_button = BUTTON_NONE;
885 move_button = BUTTON_NONE;
886 }
887
888 switch (move_button) {
889 case ACTION:
890#ifdef ACTION2
891 case ACTION2:
892#endif
893 if (last_button != ACTION
894#ifdef ACTION2
895 && last_button != ACTION2
896#endif
897 )
898 bdelay = -2;
899 if (bdelay == 0)
900 iPlayerSpeedY = -3;
901 break;
902
903 default:
904 if (last_button == ACTION
905#ifdef ACTION2
906 || last_button == ACTION2
907#endif
908 )
909 bdelay = 3;
910 if (bdelay == 0)
911 iPlayerSpeedY = 4;
912
913 if (rb->default_event_handler(move_button) == SYS_USB_CONNECTED)
914 return PLUGIN_USB_CONNECTED;
915 break;
916 }
917 last_button = move_button;
918
919 if (bdelay < 0) {
920 iPlayerSpeedY = bdelay;
921 bdelay++;
922 } else if (bdelay > 0) {
923 iPlayerSpeedY = bdelay;
924 bdelay--;
925 }
926
927 iCameraPosX = iPlayerPosX - 25;
928 iPlayerPosX += iPlayerSpeedX;
929 iPlayerPosY += iPlayerSpeedY;
930
931 chopCounter++;
932 /* increase speed as we go along */
933 if (chopCounter == 100){
934 iPlayerSpeedX++;
935 chopCounter=0;
936 }
937
938 if (iPlayerPosY > iScreenY-10 || iPlayerPosY < -5 ||
939 chopPointInTerrain(&mGround, iPlayerPosX, iPlayerPosY + 10, 0) ||
940 chopPointInTerrain(&mRoof, iPlayerPosX ,iPlayerPosY, 1))
941 {
942 chopKillPlayer();
943 chopDrawScene();
944 ret = chopMenu(0);
945 if (ret != -1)
946 return ret;
947 showsplash = true;
948 }
949
950 for (i=0; i < NUMBER_OF_BLOCKS; i++)
951 if(mBlocks[i].bIsActive == 1)
952 if(chopBlockCollideWithPlayer(&mBlocks[i])) {
953 chopKillPlayer();
954 chopDrawScene();
955 ret = chopMenu(0);
956 if (ret != -1)
957 return ret;
958 showsplash = true;
959 }
960
961 if (TIME_BEFORE(*rb->current_tick, end))
962 rb->sleep(end - *rb->current_tick); /* wait until time is over */
963 else
964 rb->yield();
965
966 }
967 return PLUGIN_OK;
968}
969
970static void chopDrawBlock(struct CBlock *mBlock)
971{
972 int iPosX = (mBlock->iWorldX - iCameraPosX);
973 int iPosY = (mBlock->iWorldY);
974#if LCD_DEPTH > 2
975 rb->lcd_set_foreground(LCD_RGBPACK(100,255,100));
976#elif LCD_DEPTH == 2
977 rb->lcd_set_foreground(LCD_BLACK);
978#endif
979 rb->lcd_fillrect(SCALE(iPosX), SCALE(iPosY), SCALE(mBlock->iSizeX),
980 SCALE(mBlock->iSizeY));
981}
982
983
984static void chopRenderTerrain(struct CTerrain *ter, bool isground)
985{
986
987 int i = 1;
988
989 int oldx = 0;
990
991 while(i < ter->iNodesCount && oldx < iScreenX)
992 {
993
994 int x = ter->mNodes[i-1].x - iCameraPosX;
995 int y = ter->mNodes[i-1].y;
996
997 int x2 = ter->mNodes[i].x - iCameraPosX;
998 int y2 = ter->mNodes[i].y;
999
1000 int ax, ay;
1001
1002 if ((y < y2) != isground)
1003 {
1004 ax = x2;
1005 ay = y;
1006 }
1007 else
1008 {
1009 ax = x;
1010 ay = y2;
1011 }
1012#if LCD_DEPTH > 2
1013 rb->lcd_set_foreground(LCD_RGBPACK(100,255,100));
1014#elif LCD_DEPTH == 2
1015 rb->lcd_set_foreground(LCD_DARKGRAY);
1016#endif
1017
1018 rb->lcd_drawline(SCALE(x), SCALE(y), SCALE(x2), SCALE(y2));
1019
1020 xlcd_filltriangle(SCALE(x), SCALE(y), SCALE(x2), SCALE(y2),
1021 SCALE(ax), SCALE(ay));
1022
1023 if (isground)
1024 {
1025 y = ay;
1026 y2 = (LCD_HEIGHT*SIZE);
1027 }
1028 else
1029 {
1030 y = 0;
1031 y2 = ay;
1032 }
1033 if (y2-y > 0)
1034 rb->lcd_fillrect(SCALE(x), SCALE(y), SCALE(x2-x)+1, SCALE(y2-y)+1);
1035
1036 oldx = x;
1037 i++;
1038 }
1039}
1040
1041static void chopper_load(bool newgame)
1042{
1043
1044 int i;
1045 int g;
1046
1047 if (newgame) {
1048 iScreenX = LCD_WIDTH * SIZE;
1049 iScreenY = LCD_HEIGHT * SIZE;
1050 blockh = iScreenY / 5;
1051 blockw = iScreenX / 20;
1052 iPlayerAlive = 1;
1053 iCurrLevelMode = iLevelMode;
1054 score = 0;
1055 }
1056 iRotorOffset = 0;
1057 iPlayerPosX = 60;
1058 iPlayerPosY = (iScreenY * 4) / 10;
1059 iLastBlockPlacedPosX = 0;
1060 iGravityTimerCountdown = 2;
1061 chopCounter = 0;
1062 iPlayerSpeedX = 3;
1063 iPlayerSpeedY = 0;
1064 iCameraPosX = 30;
1065
1066 for (i=0; i < NUMBER_OF_PARTICLES; i++)
1067 mParticles[i].bIsActive = 0;
1068
1069 for (i=0; i < NUMBER_OF_BLOCKS; i++)
1070 mBlocks[i].bIsActive = 0;
1071
1072 g = iScreenY - 10;
1073 chopClearTerrain(&mGround);
1074
1075 for (i=0; i < MAX_TERRAIN_NODES; i++)
1076 chopAddTerrainNode(&mGround,i * 30,g - iR(0,20));
1077
1078 if (chopUpdateTerrainRecycling(&mGround) == 1)
1079 /* mirror the sky if we've changed the ground */
1080 chopCopyTerrain(&mGround, &mRoof, 0, - ( (iScreenY * 3) / 4));
1081
1082 if (iCurrLevelMode == LEVEL_MODE_NORMAL)
1083 /* make it a bit more exciting, cause it's easy terrain... */
1084 iPlayerSpeedX *= 2;
1085}
1086
1087/* this is the plugin entry point */
1088enum plugin_status plugin_start(const void* parameter)
1089{
1090 (void)parameter;
1091 int ret;
1092
1093 rb->lcd_setfont(FONT_SYSFIXED);
1094#if LCD_DEPTH > 1
1095 rb->lcd_set_backdrop(NULL);
1096#endif
1097#ifdef HAVE_LCD_COLOR
1098 rb->lcd_set_background(LCD_BLACK);
1099 rb->lcd_set_foreground(LCD_WHITE);
1100#endif
1101
1102
1103 /* Turn off backlight timeout */
1104 backlight_ignore_timeout();
1105
1106
1107 rb->srand( *rb->current_tick );
1108
1109 configfile_load(CFG_FILE, config, 1, 0);
1110
1111 chopper_load(true);
1112 ret = chopGameLoop();
1113
1114 configfile_save(CFG_FILE, config, 1, 0);
1115
1116 rb->lcd_setfont(FONT_UI);
1117
1118 /* Turn on backlight timeout (revert to settings) */
1119 backlight_use_settings();
1120
1121
1122 return ret;
1123}