utd.h 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256
  1. /**
  2. @file utd.h
  3. microTD (uTD): SAF edition
  4. This is a SAF rewrite of a tiny tower defense game originally written for
  5. Arduboy. It adds some extra things such as color graphics and a new map.
  6. by drummyfish, 2021
  7. released under CC0 1.0, public domain
  8. small game manual:
  9. tower $ range speed damage upgrades notes
  10. -----------------------------------------------------------------------------
  11. guard 8 * ** * +range, +speed Shoots arrows.
  12. cannon 8 * * * +range, +damage Does splash damage.
  13. ice 17 * * +speed, +range Slows down enemies.
  14. electro 30 ** * ** +damage, shock Shoots lightning.
  15. sniper 45 ***** ** ** +speed, +range Covers huge range.
  16. magic 60 * * * +damage, speed aura Support tower.
  17. water 100 *** *** **** +range Can knock enemies back.
  18. fire 100 *** ** **** +range Does splash damage.
  19. creep hp speed $ notes attacked by: G C I E S M W F
  20. -----------------------------------------------------------------------------
  21. spider * ** 1 . . . . . . . .
  22. lizard * *** 1 . . . . . . . .
  23. snake ** ** 1 . . . . . . . .
  24. wolf *** ** 1 Good against cold. . . 50% . . . . .
  25. bat ** *** 1 . NO . . . . . .
  26. ent **** * 2 . . . . . . . .
  27. big spider *** ** 2 Spawns 2 small ones on death. . . . . . . . .
  28. ghost *** ** 2 NO NO . . NO . . .
  29. ogre ***** ** 3 . . . . . . . .
  30. dino ***** *** 3 . . . . . . . .
  31. demon ***** *** 3 Supposed to make you lose. NO NO NO NO NO NO . .
  32. TODO: procedurally generated maps?
  33. */
  34. #define SAF_PROGRAM_NAME "microTD"
  35. #define SAF_SETTING_FASTER_1BIT 2
  36. //#define SAF_SETTING_FORCE_1BIT 1
  37. #define SAF_SETTING_POKITTO_SCALE 2
  38. #define UTD_ALTERNATIVE_TILES 0
  39. #include "../saf.h"
  40. #define UTD_MAPS 5
  41. #define UTD_MAX_MAP_SIZE 34 ///< maximum map description size in bytes
  42. #define UTD_MAX_CREEPS 50 ///< maximum number of creeps present on map at once
  43. #define UTD_MAP_WIDTH 16 ///< map width in squares
  44. #define UTD_MAP_HEIGHT 8 ///< map height in squares
  45. #define UTD_MAPS 6 ///< total number of maps
  46. #define UTD_SPLASH_RANGE 12
  47. #define UTD_WAVE_BASE_REWARD 5
  48. // game states:
  49. #define UTD_GAME_STATE_MENU 0
  50. #define UTD_GAME_STATE_PLAYING 1
  51. #define UTD_GAME_STATE_PLAYING_MENU 2
  52. #define UTD_GAME_STATE_PLAYING_WAVE 3
  53. #define UTD_GAME_STATE_CONFIRM_QUIT 4
  54. #define UTD_GAME_STATE_LOST 5
  55. uint8_t UTD_gameState;
  56. uint8_t UTD_mapIndex;
  57. uint8_t UTD_sound;
  58. uint8_t UTD_backColor = SAF_COLOR_WHITE;
  59. uint8_t UTD_menuItem = 0; ///< currently selected menu item
  60. uint8_t UTD_cameraPos = 0; ///< horizontal camera position in squares
  61. uint8_t UTD_ramImage[10] = {8,8,0,0,0,0,0,0,0,0}; ///< 8x8 binary image in RAM
  62. uint8_t UTD_ramMask[10] = {8,8,0,0,0,0,0,0,0,0}; ///< 8x8 binary image mask
  63. uint8_t UTD_cursorPos = 0; ///< sequential square position of the cursor
  64. int8_t UTD_lives = 0; ///< player's current lives
  65. uint16_t UTD_money = 0; ///< player's current money
  66. uint16_t UTD_round = 0; ///< current round
  67. uint8_t UTD_updateCounter;
  68. /// Instance of a creep on the map.
  69. typedef struct
  70. {
  71. uint8_t typeFreeze; ///< lower 4 bits: type, upper 4 bits: freeze counter
  72. uint8_t healthLives; ///< lower 6 bits: health, upper 2 bits: lives
  73. uint8_t pathStart; ///< offset to path start within level data
  74. int16_t pathPosition; ///< position in pixels on path (can be negative)
  75. } UTD_Creep;
  76. UTD_Creep UTD_creeps[UTD_MAX_CREEPS]; ///< array of creeps currently on map
  77. uint8_t UTD_creepCount; ///< current number of creeps on the map
  78. #if SAF_PLATFORM_HARWARD
  79. #define UTD_PROGMEM const PROGMEM
  80. #else
  81. #define UTD_PROGMEM static const
  82. #endif
  83. /// array of 8x8 1bit images
  84. UTD_PROGMEM uint8_t UTD_images[] =
  85. {
  86. #define UTD_IMAGE_CREEPS 0 // creep images, each one followed by mask
  87. 0xff,0xff,0x99,0xc3,0x81,0xc3,0x99,0xff,
  88. 0x00,0x00,0x66,0x3c,0x7e,0x3c,0x66,0x00,
  89. 0xff,0x7f,0x7f,0x31,0x80,0xc3,0x99,0xff,
  90. 0x00,0x80,0x80,0xce,0x7f,0x3c,0x66,0x00,
  91. 0xe3,0xc9,0x83,0x9f,0xc3,0xf9,0x83,0xff,
  92. 0x1c,0x3e,0x7c,0x60,0x3c,0x06,0x7c,0x00,
  93. 0xff,0x79,0x32,0x80,0xc3,0x9b,0xb9,0xff,
  94. 0x00,0x86,0xcf,0x7f,0x3c,0x64,0x46,0x00,
  95. 0xff,0xbd,0x18,0x81,0x81,0xc3,0xdb,0xff,
  96. 0x00,0x42,0xe7,0x7e,0x7e,0x3c,0x24,0x00,
  97. 0xc3,0xc3,0xe7,0x81,0x24,0xe7,0xc3,0x99,
  98. 0x3c,0x3c,0x18,0x7e,0xdb,0x18,0x3c,0x66,
  99. 0xff,0x24,0x81,0xc3,0x00,0xc3,0x81,0x3c,
  100. 0x00,0xdb,0x7e,0x3c,0xff,0x3c,0x7e,0xc3,
  101. 0xe7,0xc3,0x5a,0x00,0x81,0x81,0xa5,0xff,
  102. 0x18,0x3c,0xbd,0xff,0x7e,0x7e,0x5a,0x00,
  103. 0x67,0x67,0x01,0x80,0xc2,0xc3,0xdb,0x99,
  104. 0x98,0x98,0xfe,0x7f,0x3d,0x3c,0x24,0x66,
  105. 0x73,0x71,0x24,0x00,0x83,0xc7,0x93,0xc9,
  106. 0x8c,0x8e,0xdf,0xff,0x7c,0x38,0x6c,0x36,
  107. 0x42,0x00,0xdb,0x81,0x00,0x42,0xc3,0x99,
  108. 0xbd,0xff,0x3c,0x7e,0xff,0xbd,0x3c,0x66,
  109. #define UTD_IMAGE_TILES 22
  110. #if UTD_ALTERNATIVE_TILES
  111. 0x7f,0xff,0x7f,0xff,0x7f,0xff,0x7f,0xaa,
  112. 0xff,0xc3,0x81,0x81,0x81,0x99,0xbd,0xff,
  113. 0x7f,0xfe,0x7f,0xfe,0x7f,0xfe,0x7f,0xfe,
  114. 0x55,0xff,0x7f,0xff,0x7f,0xff,0x7f,0xfe,
  115. 0x7f,0xff,0x7f,0xff,0x7f,0xff,0x7f,0xfe,
  116. 0xff,0xc3,0x99,0xbd,0xbd,0xbd,0xbd,0xff,
  117. 0x7f,0xfe,0xff,0xfe,0xff,0xfe,0xff,0xaa,
  118. 0x55,0xff,0xff,0xff,0xff,0xff,0xff,0xaa,
  119. 0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xaa,
  120. 0x55,0xfe,0xff,0xfe,0xff,0xfe,0xff,0xfe,
  121. 0x7f,0xfe,0xff,0xfe,0xff,0xfe,0xff,0xfe,
  122. 0x55,0xff,0xff,0xff,0xff,0xff,0xff,0xfe,
  123. 0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xfe,
  124. #else
  125. 0xef,0xf7,0xeb,0xf5,0xfa,0xff,0xff,0xff,
  126. 0xff,0xc3,0x81,0x81,0x81,0x99,0xbd,0xff,
  127. 0xef,0xf7,0xef,0xf7,0xef,0xf7,0xef,0xf7,
  128. 0xff,0xff,0xff,0xfd,0xfa,0xf5,0xeb,0xf7,
  129. 0xef,0xf7,0xef,0xf5,0xea,0xf7,0xef,0xf7,
  130. 0xff,0xc3,0x99,0xbd,0xbd,0xbd,0xbd,0xff,
  131. 0xef,0xd7,0xaf,0x5f,0xbf,0xff,0xff,0xff,
  132. 0xff,0xff,0xff,0x55,0xaa,0xff,0xff,0xff,
  133. 0xef,0xf7,0xef,0x55,0xaa,0xff,0xff,0xff,
  134. 0xff,0xff,0xff,0x5f,0xaf,0xd7,0xef,0xf7,
  135. 0xef,0xf7,0xef,0x57,0xaf,0xf7,0xef,0xf7,
  136. 0xff,0xff,0xff,0x55,0xaa,0xf7,0xef,0xf7,
  137. 0xef,0xf7,0xef,0x55,0xaa,0xf7,0xef,0xf7,
  138. #endif
  139. #define UTD_IMAGE_TOWERS (UTD_IMAGE_TILES + 13)
  140. 0xff,0xe7,0xdb,0xbd,0x99,0xdb,0xc3,0xff,
  141. 0xff,0xe7,0xc3,0x81,0x81,0xc3,0xc3,0xff,
  142. 0xff,0x81,0xbd,0xdb,0xdb,0xbd,0x81,0xff,
  143. 0xff,0x81,0x81,0xc3,0xc3,0x81,0x81,0xff,
  144. 0xff,0xe7,0xdb,0xdb,0xbd,0xbd,0x81,0xff,
  145. 0xff,0xe7,0xc3,0xc3,0x81,0x81,0x81,0xff,
  146. 0xff,0xdb,0xa5,0xbd,0xdb,0xbd,0x81,0xff,
  147. 0xff,0xdb,0x81,0x81,0xc3,0x81,0x81,0xff,
  148. 0xff,0x99,0xa5,0xbd,0xdb,0xdb,0xc3,0xff,
  149. 0xff,0x99,0x81,0x81,0xc3,0xc3,0xc3,0xff,
  150. 0xff,0xc3,0xbd,0xdb,0xbd,0xdb,0xc3,0xff,
  151. 0xff,0xc3,0x81,0xc3,0x81,0xc3,0xc3,0xff,
  152. #define UTD_IMAGE_TOWERS_BIG (UTD_IMAGE_TOWERS + 12)
  153. 0xff,0xfc,0xf9,0xf2,0xf6,0xf3,0xc1,0xcf,
  154. 0xff,0x3f,0x9f,0x4f,0x6f,0xcf,0x83,0xf3,
  155. 0xe1,0xfb,0xfb,0xf1,0xef,0xe5,0xe0,0xff,
  156. 0x87,0xdf,0xdf,0x8f,0xf7,0xa7,0x07,0xff,
  157. 0xff,0xfc,0xf8,0xf1,0xf1,0xf0,0xc0,0xdf,
  158. 0xff,0x3f,0x1f,0x8f,0x8f,0x0f,0x03,0xfb,
  159. 0xe0,0xf8,0xf8,0xf0,0xe0,0xea,0xe0,0xff,
  160. 0x07,0x1f,0x1f,0x0f,0x07,0x57,0x07,0xff,
  161. 0xff,0xf3,0xe5,0xec,0xe6,0xf3,0xfb,0xf9,
  162. 0xff,0xcf,0xa7,0x37,0x67,0xcf,0xdf,0x9f,
  163. 0xf3,0xe1,0xcf,0xe1,0xf7,0xed,0xe0,0xff,
  164. 0xcf,0x87,0xf3,0x87,0xef,0xb7,0x07,0xff,
  165. 0xff,0xf3,0xe1,0xec,0xe6,0xf2,0xf8,0xf8,
  166. 0xff,0xcf,0x87,0x37,0x67,0x4f,0x1f,0x1f,
  167. 0xf0,0xe1,0xdf,0xe1,0xf0,0xea,0xe0,0xff,
  168. 0x0f,0x87,0xfb,0x87,0x0f,0x57,0x07,0xff,
  169. #define UTD_IMAGE_ICONS (UTD_IMAGE_TOWERS_BIG + 16)
  170. 0xff,0xc3,0x99,0xa5,0xa5,0x99,0xdb,0xff,
  171. 0xff,0x99,0x81,0xa5,0xbd,0x99,0xdb,0xff,
  172. 0xff,0xef,0xc7,0xfd,0xf9,0xfd,0xfd,0xff,
  173. 0xff,0xdf,0x8f,0xf9,0xfd,0xfb,0xf9,0xff,
  174. 0xff,0xff,0x99,0xc3,0xe7,0xc3,0x99,0xff,
  175. 0xff,0xef,0xe7,0xe3,0xe3,0xe7,0xef,0xff,
  176. 0xff,0xc3,0x99,0xad,0xb5,0x99,0xc3,0xff,
  177. #define UTD_IMAGE_WAVE (UTD_IMAGE_ICONS + 7)
  178. 0xff,0xff,0xc7,0x93,0x39,0x7c,0xff,0xff
  179. };
  180. UTD_PROGMEM uint8_t UTD_logo[59] = // logo image, not 8x8
  181. {
  182. 0x19,0x12,0xfd,0xd5,0xdf,0xfc,0x60,0xc7,0xec,0x10,0x41,0xe3,0x1c,0x71,0xdb,
  183. 0x8e,0x38,0xc7,0x00,0x00,0x17,0x7f,0xff,0xf7,0x40,0x00,0x05,0x4f,0xcf,0xb9,
  184. 0x0d,0xb5,0x56,0x96,0xd8,0x89,0x43,0x6c,0x44,0xa5,0x8e,0x22,0x50,0xdf,0x11,
  185. 0x69,0x3f,0x08,0xe5,0x40,0x00,0x05,0xd5,0x55,0x55,0xf0,0x00,0x01,0xc0
  186. };
  187. // map tile constants:
  188. #define UTD_TILE_NONE 0x0000
  189. #define UTD_TILE_ROAD_U 0x0001 // road tiles can be combine with |
  190. #define UTD_TILE_ROAD_R 0x0002
  191. #define UTD_TILE_ROAD_D 0x0004
  192. #define UTD_TILE_ROAD_L 0x0008
  193. #define UTD_TILE_START 0x0004
  194. #define UTD_TILE_FINISH 0x0008
  195. #define UTD_TILE_ROAD_UD (UTD_TILE_ROAD_U | UTD_TILE_ROAD_D)
  196. #define UTD_TILE_ROAD_LR (UTD_TILE_ROAD_L | UTD_TILE_ROAD_R)
  197. /* Tower tile constants also server as tower IDs. The tower constant value
  198. format is following:
  199. MSB abcdefgh ijklmnop LSB
  200. - a: always 1
  201. - bcdefgh: targeted creep ID, only valid if attack progress != 0
  202. - ij: upgrade info
  203. - klm: attack progress, 0 or 1 = looking for target, 0 = no target
  204. - nop: tower type */
  205. #define UTD_TOWER_GUARD 0x8000
  206. #define UTD_TOWER_CANNON 0x8001
  207. #define UTD_TOWER_ICE 0x8002
  208. #define UTD_TOWER_ELECTRO 0x8003
  209. #define UTD_TOWER_SNIPER 0x8004
  210. #define UTD_TOWER_MAGIC 0x8005
  211. #define UTD_TOWER_WATER 0x8006
  212. #define UTD_TOWER_FIRE 0x8007
  213. #define UTD_TOWER_TYPE(v) (v & 0x8007)
  214. #define UTD_IS_TOWER(v) (v & 0x8000)
  215. #define UTD_TOWER_IMAGE(v) (UTD_IMAGE_TOWERS + (v & 0x07) * 2)
  216. #define UTD_TOWER_IS_SMALL(v) (((v) & 0x8007) < UTD_TOWER_WATER)
  217. #define UTD_TOWER_TARGET(v) (((v) >> 8) & 0x7f)
  218. #define UTD_TOWER_TARGET_SET(v,x) (((v) & 0x80ff) | (x) << 8)
  219. #define UTD_TOWER_ATTACK_PROGRESS(v) (((v) >> 3) & 0x07)
  220. #define UTD_TOWER_ATTACK_PROGRESS_SET(v,x) (((v) & 0xffc7) | ((x) << 3))
  221. #define UTD_TOWERS_SMALL 6 ///< total number of small tower types
  222. #define UTD_TOWERS (UTD_TOWERS_SMALL + 2)
  223. // play menu items:
  224. #define UTD_UPGRADE1 0x0080
  225. #define UTD_UPGRADE2 0x0040
  226. #define UTD_PLAY_MENU_SELL 0x000a
  227. #define UTD_PLAY_MENU_GO 0x000b
  228. #define UTD_PLAY_MENU_QUIT 0x000c
  229. #define UTD_UPGRADE_NONE 0x00
  230. #define UTD_UPGRADE_RANGE 0x01
  231. #define UTD_UPGRADE_SPEED 0x02
  232. #define UTD_UPGRADE_DAMAGE 0x03
  233. #define UTD_UPGRADE_SHOCK 0x04
  234. #define UTD_UPGRADE_SPEED_AURA 0x05
  235. #define UTD_PLAY_MENU_ITEMS 13
  236. #define UTD_CREEP_SPIDER 0x00
  237. #define UTD_CREEP_LIZARD 0x01
  238. #define UTD_CREEP_SNAKE 0x02
  239. #define UTD_CREEP_WOLF 0x03 ///< freeze is less effective
  240. #define UTD_CREEP_BAT 0x04 ///< immune to cannon
  241. #define UTD_CREEP_ENT 0x05
  242. #define UTD_CREEP_SPIDER_BIG 0x06 ///< spawns two small spiders when killed
  243. #define UTD_CREEP_GHOST 0x07 ///< immune to physical attack
  244. #define UTD_CREEP_OGRE 0x08
  245. #define UTD_CREEP_DINO 0x09
  246. #define UTD_CREEP_DEMON 0x0a ///< only attackable by fire/water
  247. #define UTD_CREEPS 11 ///< total number of creep types
  248. void UTD_loadImage(uint8_t index, uint8_t *dest)
  249. {
  250. const uint8_t *p = UTD_images + 8 * index;
  251. dest += 2;
  252. for (uint8_t i = 0; i < 8; ++i)
  253. {
  254. #if SAF_PLATFORM_HARWARD
  255. *dest = pgm_read_byte(p);
  256. #else
  257. *dest = *p;
  258. #endif
  259. p++;
  260. dest++;
  261. }
  262. }
  263. void UTD_drawLogo(void)
  264. {
  265. #if SAF_PLATFORM_HARWARD
  266. uint8_t img[sizeof(UTD_logo)];
  267. for (uint8_t i = 0; i < sizeof(UTD_logo); ++i)
  268. img[i] = pgm_read_byte(UTD_logo + i);
  269. #else
  270. const uint8_t *img = UTD_logo;
  271. #endif
  272. SAF_drawImage1Bit(img,19,6,0,SAF_COLOR_WHITE,
  273. #if SAF_PLATFORM_COLOR_COUNT > 2
  274. SAF_COLOR_RED_DARK,
  275. #else
  276. SAF_COLOR_BLACK,
  277. #endif
  278. 0);
  279. }
  280. /** Current map description. The format is following:
  281. Map specifies the layout of creep paths as a series of bytes. The format is
  282. following:
  283. - The map is a sequence of one of more path definitions terminated by a 0
  284. byte.
  285. - A path definition starts with starting square coordinates in a single byte,
  286. lower 4 bits are X, upper 4 bits are Y.
  287. - Path segments follow, each one as a single byte of two parts: the 4 highest
  288. bits are the direction (0 = up, 1 = right, 2 = down, 3 = left) and the 4
  289. lowest bits are a square distance towards the next segment.
  290. - The path definition is either terminated by a 0 byte (end of map definition)
  291. or 255 byte (end of path definition, another will follow). */
  292. uint8_t UTD_map[UTD_MAX_MAP_SIZE];
  293. /// 2D array of current map tiles computed from the map description.
  294. uint16_t UTD_mapTiles[UTD_MAP_WIDTH * UTD_MAP_HEIGHT];
  295. uint8_t UTD_minU8(uint8_t a, uint8_t b)
  296. {
  297. return a < b ? a : b;
  298. }
  299. void UTD_playSound(uint8_t index)
  300. {
  301. if (UTD_sound)
  302. SAF_playSound(index);
  303. }
  304. uint8_t UTD_buttonPressedOrHeld(uint8_t key)
  305. {
  306. uint8_t b = SAF_buttonPressed(key);
  307. return (b == 1) || (b >= 18);
  308. }
  309. void UTD_squarePositionTo2D(uint8_t squarePosition, int8_t *x, int8_t *y)
  310. {
  311. *x = (squarePosition % UTD_MAP_WIDTH) * 8 + 4;
  312. *y = (squarePosition / UTD_MAP_WIDTH) * 8 + 4;
  313. }
  314. void UTD_interpolateCoords(int8_t *x1, int8_t *y1, int8_t x2, int8_t y2,
  315. uint8_t t, uint8_t tMax)
  316. {
  317. *x1 += (((int16_t) (x2 - *x1)) * t) / tMax;
  318. *y1 += (((int16_t) (y2 - *y1)) * t) / tMax;
  319. }
  320. uint8_t UTD_canAttack(uint16_t tower, uint8_t creepType)
  321. {
  322. tower = UTD_TOWER_TYPE(tower);
  323. return
  324. // cannon can't attack flying
  325. !(tower == UTD_TOWER_CANNON && creepType == UTD_CREEP_BAT) &&
  326. // physical can't attack ghost
  327. !(creepType == UTD_CREEP_GHOST &&
  328. (tower == UTD_TOWER_CANNON ||
  329. tower == UTD_TOWER_GUARD ||
  330. tower == UTD_TOWER_SNIPER)) &&
  331. // demon can only be attacked by water or fire
  332. !(creepType == UTD_CREEP_DEMON &&
  333. tower != UTD_TOWER_WATER &&
  334. tower != UTD_TOWER_FIRE);
  335. }
  336. uint8_t UTD_towerColor(uint16_t towerType)
  337. {
  338. #if SAF_PLATFORM_COLOR_COUNT > 2
  339. switch (towerType)
  340. {
  341. case UTD_TOWER_GUARD: return 0x08; break;
  342. case UTD_TOWER_CANNON: return 0x20; break;
  343. case UTD_TOWER_ICE: return 0x01; break;
  344. case UTD_TOWER_ELECTRO: return 0x21; break;
  345. case UTD_TOWER_SNIPER: return 0x00; break;
  346. case UTD_TOWER_MAGIC: return 0x25; break;
  347. case UTD_TOWER_FIRE: return 0x40; break;
  348. case UTD_TOWER_WATER: return 0x05; break;
  349. default: break;
  350. }
  351. #else
  352. _SAF_UNUSED(towerType);
  353. #endif
  354. return SAF_COLOR_BLACK;
  355. }
  356. uint8_t UTD_creepColor(uint8_t creepType)
  357. {
  358. #if SAF_PLATFORM_COLOR_COUNT > 2
  359. switch (creepType)
  360. {
  361. case UTD_CREEP_SPIDER: return 0x20; break;
  362. case UTD_CREEP_LIZARD: return 0x04; break;
  363. case UTD_CREEP_SNAKE: return 0x01; break;
  364. case UTD_CREEP_WOLF: return 0x25; break;
  365. case UTD_CREEP_BAT: return 0x25; break;
  366. case UTD_CREEP_ENT: return 0x20; break;
  367. case UTD_CREEP_SPIDER_BIG: return 0x24; break;
  368. case UTD_CREEP_GHOST: return 0x25; break;
  369. case UTD_CREEP_OGRE: return 0x20; break;
  370. case UTD_CREEP_DINO: return 0x04; break;
  371. case UTD_CREEP_DEMON: return SAF_COLOR_BLACK; break;
  372. default: break;
  373. }
  374. #else
  375. _SAF_UNUSED(creepType);
  376. #endif
  377. return SAF_COLOR_BLACK;
  378. }
  379. void UTD_getCreepStats(uint8_t creep, uint8_t *hp, uint8_t *speed,
  380. uint8_t *reward)
  381. {
  382. // creep stats in a function (vs a table) avoids dealing with PROGMEM
  383. switch (creep)
  384. {
  385. #define C(c,h,s,r) case c: *hp = h; *speed = s; *reward = r; break;
  386. C(UTD_CREEP_SPIDER,7,2,1)
  387. C(UTD_CREEP_LIZARD,8,1,1)
  388. C(UTD_CREEP_SNAKE,12,2,1)
  389. C(UTD_CREEP_WOLF,20,2,1)
  390. C(UTD_CREEP_BAT,13,1,1)
  391. C(UTD_CREEP_ENT,43,4,2)
  392. C(UTD_CREEP_SPIDER_BIG,20,2,2)
  393. C(UTD_CREEP_GHOST,30,3,2)
  394. C(UTD_CREEP_OGRE,58,2,3)
  395. C(UTD_CREEP_DINO,63,1,3)
  396. C(UTD_CREEP_DEMON,63,1,3)
  397. #undef C
  398. default: break;
  399. }
  400. }
  401. /** Converts a position along a path to 2D pixel position on the map. Returns
  402. 0 if the position is not withing the path finish (before or after),
  403. otherwise 1. */
  404. uint8_t UTD_pathPositionTo2D(uint8_t pathStart, int16_t pathPosition,
  405. int8_t *x, int8_t *y)
  406. {
  407. uint8_t *s = UTD_map + pathStart;
  408. *x = (*s & 0x0f) * 8 + 4;
  409. *y = (*s >> 4) * 8 + 4;
  410. s++;
  411. if (pathPosition < 0)
  412. return 0;
  413. while (1) // for each segment
  414. {
  415. uint8_t segment = *s;
  416. if (segment == 0 || segment == 255) // no more segments?
  417. break;
  418. int8_t dx = 0, dy = 0;
  419. switch (segment >> 4)
  420. {
  421. case 0: dy = -1; break;
  422. case 1: dx = 1; break;
  423. case 2: dy = 1; break;
  424. case 3: dx = -1; break;
  425. default: break;
  426. }
  427. uint8_t segmentLength = (segment & 0x0f) * 8;
  428. if (pathPosition > segmentLength)
  429. {
  430. *x += dx * segmentLength;
  431. *y += dy * segmentLength;
  432. pathPosition -= segmentLength;
  433. }
  434. else
  435. {
  436. *x += dx * pathPosition;
  437. *y += dy * pathPosition;
  438. return 1;
  439. }
  440. s++;
  441. }
  442. return 0;
  443. }
  444. /**
  445. Decreases creep's lives and potentially sends it back to the start.
  446. */
  447. void UTD_creepEnds(uint8_t creepIndex)
  448. {
  449. uint8_t cH = 0, cS = 0, cR = 0;
  450. UTD_getCreepStats(UTD_creeps[creepIndex].typeFreeze & 0x0f,
  451. &cH,&cS,&cR);
  452. UTD_Creep *c = UTD_creeps + creepIndex;
  453. uint8_t lives = c->healthLives >> 6;
  454. uint8_t remove = lives == 0;
  455. if (remove)
  456. {
  457. // remove creep
  458. for (uint8_t i = creepIndex; i < UTD_creepCount - 1; ++i)
  459. UTD_creeps[i] = UTD_creeps[i + 1];
  460. UTD_creepCount--;
  461. }
  462. else
  463. {
  464. // send creep back to start
  465. c->healthLives = ((lives - 1) << 6) | cH;
  466. c->pathPosition = -5;
  467. }
  468. uint16_t *tile = UTD_mapTiles;
  469. // correct tower targets:
  470. for (uint8_t i = 0; i < UTD_MAP_WIDTH * UTD_MAP_HEIGHT; ++i)
  471. {
  472. uint16_t t = *tile;
  473. if (UTD_IS_TOWER(t))
  474. {
  475. uint8_t target = UTD_TOWER_TARGET(t);
  476. if (target == creepIndex)
  477. *tile = UTD_TOWER_ATTACK_PROGRESS_SET(t,0); // remove target
  478. else if (remove && target > creepIndex)
  479. *tile = UTD_TOWER_TARGET_SET(t,target - 1); // because creeps shifted
  480. }
  481. tile++;
  482. }
  483. }
  484. /**
  485. Checks a square and if it contains a tower, it progresses the tower's
  486. attack progress (for speed aura).
  487. */
  488. void UTD_speedUpTower(uint8_t square)
  489. {
  490. uint16_t t = UTD_mapTiles[square];
  491. if (UTD_IS_TOWER(t))
  492. {
  493. uint8_t attackProgress = UTD_TOWER_ATTACK_PROGRESS(t);
  494. if (attackProgress > 1)
  495. UTD_mapTiles[square] =
  496. UTD_TOWER_ATTACK_PROGRESS_SET(t,attackProgress - 1);
  497. }
  498. }
  499. void UTD_applySpeedAura(uint8_t center)
  500. {
  501. if (UTD_updateCounter % 8 != center % 8)
  502. return;
  503. uint8_t x = center % 8;
  504. uint8_t
  505. t = center >= UTD_MAP_WIDTH,
  506. r = x != UTD_MAP_WIDTH - 1,
  507. b = center < UTD_MAP_WIDTH * (UTD_MAP_HEIGHT - 1),
  508. l = x != 0;
  509. if (t)
  510. {
  511. UTD_speedUpTower(center - UTD_MAP_WIDTH);
  512. if (l)
  513. UTD_speedUpTower(center - UTD_MAP_WIDTH - 1);
  514. if (r)
  515. UTD_speedUpTower(center - UTD_MAP_WIDTH + 1);
  516. }
  517. if (l)
  518. UTD_speedUpTower(center - 1);
  519. if (r)
  520. UTD_speedUpTower(center + 1);
  521. if (b)
  522. {
  523. UTD_speedUpTower(center + UTD_MAP_WIDTH);
  524. if (l)
  525. UTD_speedUpTower(center + UTD_MAP_WIDTH - 1);
  526. if (r)
  527. UTD_speedUpTower(center + UTD_MAP_WIDTH + 1);
  528. }
  529. }
  530. void UTD_creepFinishes(uint8_t index)
  531. {
  532. uint8_t cH = 0, cS = 0, cR = 0;
  533. UTD_getCreepStats(UTD_creeps[index].typeFreeze & 0x0f,
  534. &cH,&cS,&cR);
  535. UTD_creepEnds(index);
  536. UTD_lives -= cR;
  537. if (UTD_lives <= 0)
  538. UTD_gameState = UTD_GAME_STATE_LOST;
  539. UTD_playSound(SAF_SOUND_BOOM);
  540. }
  541. void UTD_getTowerStats(uint16_t tower, uint8_t *range, uint8_t *speed,
  542. uint8_t *damage, uint8_t *price, uint8_t *upgrades)
  543. {
  544. // similarly to creep stats we store tower stats as a function code
  545. #define T(t,r,s,d,p,u1,u2) \
  546. case t: \
  547. if (range != 0) *range = r; \
  548. if (speed != 0) *speed = s; \
  549. if (damage != 0) *damage = d; \
  550. if (price != 0) *price = p; \
  551. if (upgrades != 0) *upgrades = u1 | (u2 << 4); break;
  552. switch (tower)
  553. {
  554. T(UTD_TOWER_GUARD, 30,5,2,8, UTD_UPGRADE_RANGE,UTD_UPGRADE_SPEED)
  555. T(UTD_TOWER_CANNON, 27,7,2,8, UTD_UPGRADE_RANGE,UTD_UPGRADE_DAMAGE)
  556. T(UTD_TOWER_ICE, 26,6,0,17, UTD_UPGRADE_SPEED,UTD_UPGRADE_RANGE)
  557. T(UTD_TOWER_ELECTRO,35,7,4,30, UTD_UPGRADE_DAMAGE,UTD_UPGRADE_SHOCK)
  558. T(UTD_TOWER_SNIPER, 60,4,3,45, UTD_UPGRADE_SPEED,UTD_UPGRADE_RANGE)
  559. T(UTD_TOWER_MAGIC, 25,7,2,60, UTD_UPGRADE_DAMAGE,UTD_UPGRADE_SPEED_AURA)
  560. T(UTD_TOWER_WATER, 40,3,7,100,UTD_UPGRADE_RANGE,UTD_UPGRADE_NONE)
  561. T(UTD_TOWER_FIRE, 38,5,8,100,UTD_UPGRADE_RANGE,UTD_UPGRADE_NONE)
  562. default: break;
  563. }
  564. #undef T
  565. }
  566. uint8_t UTD_getUpgradeCost(uint16_t tower)
  567. {
  568. uint8_t tP = 0;
  569. UTD_getTowerStats(UTD_TOWER_TYPE(tower),0,0,0,&tP,0);
  570. return (tP * 2) / 3;
  571. }
  572. void UTD_getTowerInstanceStats(uint16_t towerInstance, uint8_t *range,
  573. uint8_t *damage, uint8_t *speed)
  574. {
  575. uint8_t upgrades = 0;
  576. UTD_getTowerStats(UTD_TOWER_TYPE(towerInstance),
  577. range,speed,damage,0,&upgrades);
  578. for (uint8_t i = 0; i < 2; ++i)
  579. if (towerInstance & (i == 0 ? UTD_UPGRADE1 : UTD_UPGRADE2))
  580. switch (i == 0 ? (upgrades & 0x0f) : (upgrades >> 4))
  581. {
  582. case UTD_UPGRADE_RANGE: if (range != 0) *range += *range / 4; break;
  583. case UTD_UPGRADE_DAMAGE: if (damage != 0) *damage += *damage / 2; break;
  584. case UTD_UPGRADE_SPEED: if (speed != 0) *speed -= 2; break;
  585. default: break;
  586. }
  587. }
  588. /** Gets the play menu item constant as a return value, its image in the
  589. pointer parameter (if non-0) and its name in a pointer parameter (if non-0,
  590. needs to be at least 13 bytes). */
  591. uint16_t UTD_getPlayMenuItem(uint8_t index, uint16_t *imageIndex, char *name)
  592. {
  593. uint16_t result = index;
  594. if (index <= 7) // small tower?
  595. result = (0x8000 | index);
  596. else if (index == 8)
  597. result = UTD_UPGRADE1;
  598. else if (index == 9)
  599. result = UTD_UPGRADE2;
  600. if (imageIndex != 0)
  601. *imageIndex = index <= 5 ?
  602. (UTD_IMAGE_TOWERS + index * 2 + 1) :
  603. (UTD_IMAGE_ICONS + index - 6);
  604. if (name != 0)
  605. {
  606. uint8_t end = 0;
  607. uint8_t price = 0;
  608. if (UTD_IS_TOWER(result))
  609. UTD_getTowerStats(result,0,0,0,&price,0);
  610. switch (result)
  611. {
  612. #define C(n,c) name[n] = c;
  613. case UTD_TOWER_GUARD:
  614. C(0,'G') C(1,'u') C(2,'a') C(3,'r') C(4,'d') end = 5; break;
  615. case UTD_TOWER_CANNON:
  616. C(0,'C') C(1,'a') C(2,'n') C(3,'n') C(4,'o') C(5,'n') end = 6; break;
  617. case UTD_TOWER_ICE:
  618. C(0,'I') C(1,'c') C(2,'e') end = 3; break;
  619. case UTD_TOWER_MAGIC:
  620. C(0,'M') C(1,'a') C(2,'g') C(3,'i') C(4,'c') end = 5; break;
  621. case UTD_TOWER_SNIPER:
  622. C(0,'S') C(1,'n') C(2,'i') C(3,'p') C(4,'e') C(5,'r') end = 6; break;
  623. case UTD_TOWER_ELECTRO:
  624. C(0,'E') C(1,'l') C(2,'e') C(3,'c') C(4,'t') C(5,'r') end = 6; break;
  625. case UTD_TOWER_WATER:
  626. C(0,'W') C(1,'A') C(2,'T') C(3,'E') C(4,'R') end = 5; break;
  627. case UTD_TOWER_FIRE:
  628. C(0,'F') C(1,'I') C(2,'R') C(3,'E') end = 4; break;
  629. case UTD_PLAY_MENU_GO:
  630. C(0,'G') C(1,'o') C(2,'!') end = 3; break;
  631. case UTD_PLAY_MENU_SELL:
  632. C(0,'S') C(1,'e') C(2,'l') C(3,'l') end = 4; break;
  633. case UTD_PLAY_MENU_QUIT:
  634. C(0,'Q') C(1,'u') C(2,'i') C(3,'t') end = 4; break;
  635. case UTD_UPGRADE1:
  636. case UTD_UPGRADE2:
  637. {
  638. uint8_t upgradeType = 0;
  639. uint16_t t = UTD_TOWER_TYPE(UTD_mapTiles[UTD_cursorPos]);
  640. price = UTD_getUpgradeCost(t);
  641. UTD_getTowerStats(t,0,0,0,0,&upgradeType);
  642. upgradeType = result == UTD_UPGRADE1 ?
  643. (upgradeType & 0x0f) : (upgradeType >> 4);
  644. switch (upgradeType)
  645. {
  646. case UTD_UPGRADE_RANGE:
  647. C(0,'+') C(1,'R') C(2,'N') C(3,'G') end = 4; break;
  648. case UTD_UPGRADE_DAMAGE:
  649. C(0,'+') C(1,'D') C(2,'M') C(3,'G') end = 4; break;
  650. case UTD_UPGRADE_SPEED:
  651. C(0,'+') C(1,'S') C(2,'P') C(3,'D') end = 4; break;
  652. case UTD_UPGRADE_SHOCK:
  653. C(0,'+') C(1,'S') C(2,'H') C(3,'O') C(4,'K') end = 5; break;
  654. case UTD_UPGRADE_SPEED_AURA:
  655. C(0,'+') C(1,'A') C(2,'U') C(3,'R') C(4,'A') end = 5; break;
  656. default: *name = 0; price = 0; break;
  657. }
  658. break;
  659. }
  660. default: *name = 0; break;
  661. #undef CH
  662. }
  663. name[end] = 0;
  664. if (price != 0)
  665. {
  666. name += end;
  667. *name = ' ';
  668. name++;
  669. *name = '$';
  670. name++;
  671. SAF_intToStr(price,name);
  672. }
  673. }
  674. return result;
  675. }
  676. uint8_t UTD_playMenuItemAvailable(uint16_t item) // uint8_t index)
  677. {
  678. uint16_t tile = UTD_mapTiles[UTD_cursorPos];
  679. if (UTD_IS_TOWER(item))
  680. {
  681. if (tile != UTD_TILE_NONE)
  682. return 0; // can't build on non-empty tile
  683. uint8_t price;
  684. UTD_getTowerStats(UTD_TOWER_TYPE(item),0,0,0,&price,0);
  685. if (price > UTD_money)
  686. return 0;
  687. if (
  688. (UTD_cursorPos >= UTD_MAP_WIDTH) &&
  689. (UTD_cursorPos % UTD_MAP_WIDTH > 0))
  690. {
  691. // big tower blocks the space?
  692. uint16_t t = UTD_mapTiles[UTD_cursorPos - 1];
  693. if (UTD_IS_TOWER(t) && !UTD_TOWER_IS_SMALL(t)) return 0;
  694. t = UTD_mapTiles[UTD_cursorPos - UTD_MAP_WIDTH];
  695. if (UTD_IS_TOWER(t) && !UTD_TOWER_IS_SMALL(t)) return 0;
  696. t = UTD_mapTiles[UTD_cursorPos - UTD_MAP_WIDTH - 1];
  697. if (UTD_IS_TOWER(t) && !UTD_TOWER_IS_SMALL(t)) return 0;
  698. }
  699. if (!UTD_TOWER_IS_SMALL(item))
  700. {
  701. if ( // big towers need 2x2 squares space
  702. (UTD_cursorPos % UTD_MAP_WIDTH == UTD_MAP_WIDTH - 1) ||
  703. (UTD_cursorPos >= (UTD_MAP_HEIGHT - 1) * UTD_MAP_WIDTH) ||
  704. (UTD_mapTiles[UTD_cursorPos + 1] != UTD_TILE_NONE) ||
  705. (UTD_mapTiles[UTD_cursorPos + UTD_MAP_WIDTH] != UTD_TILE_NONE) ||
  706. (UTD_mapTiles[UTD_cursorPos + UTD_MAP_WIDTH + 1] != UTD_TILE_NONE)
  707. )
  708. return 0;
  709. }
  710. }
  711. else if (item == UTD_UPGRADE1)
  712. return (UTD_IS_TOWER(tile) != 0) && ((tile & UTD_UPGRADE1) == 0) &&
  713. (UTD_getUpgradeCost(UTD_TOWER_TYPE(tile)) <= UTD_money);
  714. else if (item == UTD_UPGRADE2)
  715. return UTD_IS_TOWER(tile) && UTD_TOWER_IS_SMALL(tile) &&
  716. ((tile & UTD_UPGRADE2) == 0) &&
  717. (UTD_getUpgradeCost(UTD_TOWER_TYPE(tile)) <= UTD_money);
  718. else if (item == UTD_PLAY_MENU_SELL) // sell
  719. return (UTD_IS_TOWER(tile) != 0);
  720. return 1;
  721. }
  722. void UTD_addCreep(uint8_t creep, uint8_t pathIndex, int16_t position,
  723. uint8_t lives)
  724. {
  725. if (UTD_creepCount >= UTD_MAX_CREEPS)
  726. return;
  727. UTD_Creep *c = UTD_creeps + UTD_creepCount;
  728. uint8_t cH = 0, cS = 0, cR = 0;
  729. UTD_getCreepStats(creep,&cH,&cS,&cR);
  730. c->typeFreeze = creep;
  731. c->healthLives = (lives << 6) | cH;
  732. c->pathPosition = position;
  733. c->pathStart = 0;
  734. while (pathIndex > 0)
  735. {
  736. while (UTD_map[c->pathStart] != 0 && UTD_map[c->pathStart] != 255)
  737. c->pathStart++;
  738. if (UTD_map[c->pathStart] == 255)
  739. c->pathStart++;
  740. else
  741. c->pathStart = 0;
  742. pathIndex--;
  743. }
  744. UTD_creepCount++;
  745. }
  746. uint8_t UTD_cycleCreeps(uint8_t index, uint8_t levelFrom, uint8_t levelTo)
  747. {
  748. if (levelFrom >= UTD_CREEPS)
  749. levelFrom = UTD_CREEPS - 1;
  750. if (levelTo >= UTD_CREEPS)
  751. levelTo = UTD_CREEPS - 1;
  752. return levelFrom + (index % (levelTo - levelFrom + 1));
  753. }
  754. void UTD_cyclingSpawn(
  755. uint8_t total, // total number of creeps to spawn
  756. uint8_t levelFrom, // minimum level of a creep
  757. uint8_t levelTo, // maximum level of a creep
  758. uint8_t maxLives, // maximum number of lives for a creep
  759. uint8_t positionOffset, // gaps between creeps
  760. uint8_t groupSize // affects additional gaps and creep lives
  761. )
  762. {
  763. total = UTD_minU8(UTD_MAX_CREEPS,total);
  764. int16_t position = -1 * positionOffset;
  765. for (uint8_t i = 0; i < total; ++i)
  766. {
  767. if (i % groupSize == 0)
  768. position -= positionOffset;
  769. UTD_addCreep(
  770. UTD_cycleCreeps(i,levelFrom,levelTo),
  771. i,position,i % groupSize == 0 ? UTD_minU8(3,maxLives) : 0);
  772. position -= positionOffset;
  773. }
  774. }
  775. void UTD_goToMenu(void)
  776. {
  777. UTD_gameState = UTD_GAME_STATE_MENU;
  778. UTD_menuItem = 0;
  779. UTD_backColor = 0xff;
  780. }
  781. void UTD_spawnCreeps(void)
  782. {
  783. UTD_creepCount = 0;
  784. switch (UTD_mapIndex)
  785. {
  786. case 0:
  787. case 1:
  788. {
  789. UTD_cyclingSpawn(1 + UTD_round,UTD_round / 4,
  790. UTD_round < 15 ? UTD_minU8(UTD_CREEP_DINO - 1,UTD_round) : UTD_round,
  791. UTD_round / 5,10,5);
  792. break;
  793. }
  794. case 2:
  795. {
  796. uint8_t total = UTD_minU8(1 + UTD_round,(UTD_MAX_CREEPS * 2) / 3);
  797. int16_t pos = -10;
  798. for (uint8_t i = 0; i < total; ++i)
  799. {
  800. uint8_t c = UTD_cycleCreeps(i,UTD_round / 3,(UTD_round * 2) / 3);
  801. uint8_t l = i % 3 == 0 ? UTD_minU8(3,UTD_round / 7) : 0;
  802. UTD_addCreep(c,0,pos,l);
  803. pos -= 10;
  804. if (i % 2 == 0)
  805. UTD_addCreep(c,1,pos,l);
  806. }
  807. break;
  808. }
  809. case 3:
  810. UTD_cyclingSpawn(3 + UTD_round * 2,UTD_round / 3,(UTD_round * 4) / 5,
  811. UTD_round / 8,7,4);
  812. break;
  813. case 4:
  814. UTD_cyclingSpawn(8 + UTD_round * 2,UTD_round / 3,2 + (UTD_round * 4) / 5,
  815. UTD_round / 8,10,4);
  816. break;
  817. case 5:
  818. {
  819. UTD_cyclingSpawn((1 + UTD_round / 2) * 2,1 + UTD_round / 3,
  820. 1 + UTD_round,UTD_round / 8,15,5);
  821. UTD_cyclingSpawn(UTD_round * 2,UTD_round / 4,UTD_round / 2,
  822. UTD_round / 8,8,4);
  823. break;
  824. }
  825. }
  826. }
  827. void UTD_loadMap(uint8_t index)
  828. {
  829. for (uint8_t i = 0; i < UTD_MAX_MAP_SIZE; ++i)
  830. UTD_map[i] = 0;
  831. UTD_menuItem = 0;
  832. UTD_cursorPos = UTD_MAP_WIDTH + 1;
  833. UTD_cameraPos = 0;
  834. UTD_mapIndex = index;
  835. UTD_creepCount = 0;
  836. UTD_round = 0;
  837. UTD_gameState = UTD_GAME_STATE_PLAYING;
  838. switch (index)
  839. {
  840. // by initializing maps in code we also avoid dealing with PROGMEM
  841. case 0:
  842. UTD_map[0] = 0x11;
  843. UTD_map[1] = 0x25;
  844. UTD_map[2] = 0x12;
  845. UTD_map[3] = 0x05;
  846. UTD_map[4] = 0x18;
  847. UTD_map[5] = 0x22;
  848. UTD_map[6] = 0x35;
  849. UTD_map[7] = 0x23;
  850. UTD_map[8] = 0x17;
  851. UTD_map[9] = 0x05;
  852. UTD_lives = 20;
  853. UTD_money = 10;
  854. UTD_backColor = 0xfe;
  855. break;
  856. case 1:
  857. UTD_map[0] = 0x11;
  858. UTD_map[1] = 0x22;
  859. UTD_map[2] = 0x17;
  860. UTD_map[3] = 0x21;
  861. UTD_map[4] = 0x12;
  862. UTD_map[5] = 0x22;
  863. UTD_map[6] = 0x14;
  864. UTD_map[7] = 0x03;
  865. UTD_map[8] = 0x32;
  866. UTD_map[9] = 0x02;
  867. UTD_map[10] = 0x34;
  868. UTD_map[11] = 0xff;
  869. UTD_map[12] = 0x61;
  870. UTD_map[13] = 0x03;
  871. UTD_map[14] = 0x13;
  872. UTD_map[15] = 0x22;
  873. UTD_map[16] = 0x11;
  874. UTD_map[17] = 0x21;
  875. UTD_map[18] = 0x19;
  876. UTD_map[19] = 0x03;
  877. UTD_map[20] = 0x32;
  878. UTD_map[21] = 0x02;
  879. UTD_map[22] = 0x34;
  880. UTD_lives = 25;
  881. UTD_money = 10;
  882. UTD_backColor = 0xde;
  883. break;
  884. case 2:
  885. UTD_map[0] = 0x11;
  886. UTD_map[1] = 0x25;
  887. UTD_map[2] = 0x12;
  888. UTD_map[3] = 0x02;
  889. UTD_map[4] = 0x12;
  890. UTD_map[5] = 0x22;
  891. UTD_map[6] = 0x13;
  892. UTD_map[7] = 0x03;
  893. UTD_map[8] = 0x32;
  894. UTD_map[9] = 0x02;
  895. UTD_map[10] = 0x33;
  896. UTD_map[11] = 0xff;
  897. UTD_map[12] = 0x1e;
  898. UTD_map[13] = 0x34;
  899. UTD_map[14] = 0x25;
  900. UTD_map[15] = 0x12;
  901. UTD_map[16] = 0x03;
  902. UTD_map[17] = 0x12;
  903. UTD_map[18] = 0x23;
  904. UTD_lives = 20;
  905. UTD_money = 10;
  906. UTD_backColor = 0xdf;
  907. break;
  908. case 3:
  909. UTD_map[0] = 0x72;
  910. UTD_map[1] = 0x02;
  911. UTD_map[2] = 0x1c;
  912. UTD_map[3] = 0x22;
  913. UTD_map[4] = 0x37;
  914. UTD_map[5] = 0x06;
  915. UTD_map[6] = 0x32;
  916. UTD_map[7] = 0x22;
  917. UTD_map[8] = 0x34;
  918. UTD_map[9] = 0x01;
  919. UTD_map[10] = 0x13;
  920. UTD_map[11] = 0xff;
  921. UTD_map[12] = 0x72;
  922. UTD_map[13] = 0x02;
  923. UTD_map[14] = 0x1c;
  924. UTD_map[15] = 0x22;
  925. UTD_map[16] = 0x37;
  926. UTD_map[17] = 0x06;
  927. UTD_map[18] = 0x12;
  928. UTD_map[19] = 0x22;
  929. UTD_map[20] = 0x14;
  930. UTD_map[21] = 0x01;
  931. UTD_map[22] = 0x33;
  932. UTD_lives = 25;
  933. UTD_money = 8;
  934. UTD_backColor = 0xfb;
  935. break;
  936. case 4:
  937. UTD_map[0] = 0x44;
  938. UTD_map[1] = 0x02;
  939. UTD_map[2] = 0x33;
  940. UTD_map[3] = 0x24;
  941. UTD_map[4] = 0x15;
  942. UTD_map[5] = 0x05;
  943. UTD_map[6] = 0x12;
  944. UTD_map[7] = 0x25;
  945. UTD_map[8] = 0x13;
  946. UTD_map[9] = 0x05;
  947. UTD_map[10] = 0x13;
  948. UTD_map[11] = 0xff;
  949. UTD_map[12] = 0x6d;
  950. UTD_map[13] = 0x03;
  951. UTD_map[14] = 0x35;
  952. UTD_map[15] = 0x23;
  953. UTD_map[16] = 0x32;
  954. UTD_map[17] = 0x03;
  955. UTD_map[18] = 0x31;
  956. UTD_map[19] = 0x02;
  957. UTD_map[20] = 0x33;
  958. UTD_map[21] = 0x23;
  959. UTD_lives = 30;
  960. UTD_money = 22;
  961. UTD_backColor = 0xbe;
  962. break;
  963. case 5:
  964. UTD_map[0] = 0x55;
  965. UTD_map[1] = 0x34;
  966. UTD_map[2] = 0x04;
  967. UTD_map[3] = 0x13;
  968. UTD_map[4] = 0x21;
  969. UTD_map[5] = 0x15;
  970. UTD_map[6] = 0x01;
  971. UTD_map[7] = 0x15;
  972. UTD_map[8] = 0x23;
  973. UTD_map[9] = 0x11;
  974. UTD_map[10] = 0x22;
  975. UTD_map[11] = 0x36;
  976. UTD_map[12] = 0x02;
  977. UTD_map[13] = 0x34;
  978. UTD_map[14] = 0xff;
  979. UTD_map[15] = 0x55;
  980. UTD_map[16] = 0x11;
  981. UTD_map[17] = 0x01;
  982. UTD_map[18] = 0x13;
  983. UTD_map[19] = 0x22;
  984. UTD_map[20] = 0x16;
  985. UTD_map[21] = 0x02;
  986. UTD_map[22] = 0x31;
  987. UTD_map[23] = 0x03;
  988. UTD_map[24] = 0x35;
  989. UTD_map[25] = 0x21;
  990. UTD_map[26] = 0x35;
  991. UTD_map[27] = 0x01;
  992. UTD_map[28] = 0x33;
  993. UTD_map[29] = 0x24;
  994. UTD_map[30] = 0x13;
  995. UTD_map[31] = 0x01;
  996. UTD_map[32] = 0x11;
  997. UTD_lives = 1;
  998. UTD_money = 30;
  999. UTD_backColor = 0xda;
  1000. break;
  1001. default: break;
  1002. }
  1003. // create the tilemap:
  1004. for (uint8_t i = 0; i < UTD_MAP_WIDTH * UTD_MAP_HEIGHT; ++i)
  1005. UTD_mapTiles[i] = UTD_TILE_NONE;
  1006. uint8_t *b = UTD_map;
  1007. uint8_t end = 0;
  1008. while (!end) // for each path
  1009. {
  1010. uint16_t *currentTile = UTD_mapTiles +
  1011. (*b >> 4) * UTD_MAP_WIDTH + (*b & 0x0f);
  1012. uint16_t *previousTile = currentTile;
  1013. uint16_t *pathStartTile = currentTile;
  1014. b++;
  1015. while (1) // for each segment
  1016. {
  1017. uint8_t v = *b;
  1018. b++;
  1019. if (v == 0) // end of map?
  1020. {
  1021. end = 1;
  1022. break;
  1023. }
  1024. if (v == 255) // end of segment?
  1025. break;
  1026. int8_t tileShift = 0;
  1027. uint16_t d1 = UTD_TILE_ROAD_U,
  1028. d2 = UTD_TILE_ROAD_D;
  1029. switch (v >> 4)
  1030. {
  1031. case 0:
  1032. tileShift = -1 * UTD_MAP_WIDTH;
  1033. break;
  1034. case 1:
  1035. tileShift = 1;
  1036. d1 = UTD_TILE_ROAD_R;
  1037. d2 = UTD_TILE_ROAD_L;
  1038. break;
  1039. case 2:
  1040. tileShift = UTD_MAP_WIDTH;
  1041. d1 = UTD_TILE_ROAD_D;
  1042. d2 = UTD_TILE_ROAD_U;
  1043. break;
  1044. case 3:
  1045. tileShift = -1;
  1046. d1 = UTD_TILE_ROAD_L;
  1047. d2 = UTD_TILE_ROAD_R;
  1048. break;
  1049. default: break;
  1050. }
  1051. for (uint8_t i = 0; i < (v & 0x0f); ++i)
  1052. {
  1053. previousTile = currentTile;
  1054. currentTile += tileShift;
  1055. *previousTile |= d1;
  1056. *currentTile |= d2;
  1057. }
  1058. }
  1059. *currentTile = UTD_TILE_FINISH;
  1060. *pathStartTile = UTD_TILE_START;
  1061. }
  1062. }
  1063. uint8_t UTD_isWithinRange(int8_t x, int8_t y, int8_t x2, int8_t y2,
  1064. uint8_t range)
  1065. {
  1066. x = (x >= x2) ? (x - x2) : (x2 - x);
  1067. // first quickly test Chebyshev distance:
  1068. if (x <= range)
  1069. {
  1070. y = (y >= y2) ? (y - y2) : (y2 - y);
  1071. // now test proper Euclidean distance:
  1072. if (y <= range && SAF_sqrt(x * x + y * y) <= range)
  1073. return 1;
  1074. }
  1075. return 0;
  1076. }
  1077. #define UTD_DRAW_MODE_WHOLE 0
  1078. #define UTD_DRAW_MODE_TOP 1
  1079. #define UTD_DRAW_MODE_BOTTOM 2
  1080. void UTD_drawImage(uint8_t x, uint8_t y, uint8_t imageIndex, uint8_t color1,
  1081. uint8_t color2, uint8_t transform, uint8_t hasMask, uint8_t mode)
  1082. {
  1083. UTD_loadImage(imageIndex,UTD_ramImage);
  1084. const uint8_t *mask = 0;
  1085. if (hasMask)
  1086. {
  1087. UTD_loadImage(imageIndex + 1,UTD_ramMask);
  1088. mask = UTD_ramMask;
  1089. }
  1090. if (mode == UTD_DRAW_MODE_TOP)
  1091. {
  1092. UTD_ramImage[1] = 4;
  1093. hasMask = 0;
  1094. }
  1095. else if (mode == UTD_DRAW_MODE_BOTTOM)
  1096. {
  1097. UTD_ramImage[1] = 4;
  1098. y += 4;
  1099. hasMask = 0;
  1100. for (uint8_t i = 2; i < 6; ++i)
  1101. UTD_ramImage[i] = UTD_ramImage[i + 4];
  1102. }
  1103. SAF_drawImage1Bit(UTD_ramImage,x,y,mask,color1,color2,transform);
  1104. UTD_ramImage[1] = 8;
  1105. }
  1106. void SAF_init(void)
  1107. {
  1108. UTD_sound = 1;
  1109. UTD_goToMenu();
  1110. }
  1111. void UTD_drawTower(uint16_t tower, uint8_t x, uint8_t y)
  1112. {
  1113. uint16_t imageIndex = UTD_TOWER_IMAGE(tower);
  1114. uint8_t c = UTD_towerColor(UTD_TOWER_TYPE(tower));
  1115. if (UTD_TOWER_IS_SMALL(tower))
  1116. {
  1117. UTD_drawImage(x,y,imageIndex + (tower & UTD_UPGRADE1 ? 1 : 0),
  1118. UTD_backColor,c,0,0,UTD_DRAW_MODE_TOP);
  1119. UTD_drawImage(x,y,imageIndex + (tower & UTD_UPGRADE2 ? 1 : 0),
  1120. UTD_backColor,c,0,0,UTD_DRAW_MODE_BOTTOM);
  1121. }
  1122. else
  1123. {
  1124. if (UTD_TOWER_TYPE(tower) == UTD_TOWER_FIRE)
  1125. imageIndex = UTD_IMAGE_TOWERS_BIG + 8;
  1126. if (tower & UTD_UPGRADE1)
  1127. imageIndex += 4;
  1128. #define drawPart(px,py) \
  1129. UTD_drawImage(x + px,y + py,imageIndex,UTD_backColor,c,\
  1130. 0,0,UTD_DRAW_MODE_WHOLE);\
  1131. imageIndex++;
  1132. drawPart(0,0)
  1133. drawPart(8,0)
  1134. drawPart(0,8)
  1135. drawPart(8,8)
  1136. #undef drawPart
  1137. }
  1138. }
  1139. void UTD_draw(void)
  1140. {
  1141. /* Someone once said: "Do not write functions longer than one screen."
  1142. He was a stupid man. */
  1143. SAF_clearScreen(UTD_backColor);
  1144. switch (UTD_gameState)
  1145. {
  1146. case UTD_GAME_STATE_MENU:
  1147. {
  1148. UTD_drawLogo();
  1149. int8_t pos = 0 - (SAF_frame() % 8);
  1150. for (uint8_t i = 0; i < 9; ++i) // draw the wave
  1151. {
  1152. UTD_drawImage(pos,28,UTD_IMAGE_WAVE,
  1153. SAF_COLOR_WHITE,SAF_COLOR_BLUE_DARK,0,0,0);
  1154. pos += 8;
  1155. }
  1156. SAF_drawRect(0,40,SAF_SCREEN_WIDTH,6,SAF_COLOR_GRAY_DARK,1);
  1157. char t[10] = "MAP x";
  1158. if (UTD_menuItem == UTD_MAPS)
  1159. {
  1160. t[0] = 'S'; t[1] = 'N'; t[2] = 'D'; t[3] = ':';
  1161. t[4] = 'o';
  1162. if (UTD_sound)
  1163. {
  1164. t[5] = 'n'; t[6] = 0;
  1165. }
  1166. else
  1167. {
  1168. t[5] = 'f'; t[6] = 'f'; t[7] = 0;
  1169. }
  1170. }
  1171. else
  1172. t[4] = '1' + UTD_menuItem;
  1173. if (UTD_menuItem < UTD_MAPS)
  1174. {
  1175. SAF_drawText(t,18,41,SAF_COLOR_YELLOW,1);
  1176. t[0] = 'b'; t[1] = 'e'; t[2] = 's'; t[3] = 't'; t[4] = ':'; t[5] = ' ';
  1177. SAF_intToStr(SAF_load(UTD_menuItem),t + 6);
  1178. SAF_drawText(t,1,59,SAF_COLOR_GRAY_DARK,1);
  1179. }
  1180. else
  1181. SAF_drawText(t,15,41,SAF_COLOR_YELLOW,1);
  1182. break;
  1183. }
  1184. case UTD_GAME_STATE_LOST:
  1185. {
  1186. char t[15];
  1187. // prevent storing text in RAM on Arduino with this one weird trick
  1188. t[0] = 'L'; t[1] = 'O'; t[2] = 'S'; t[3] = 'T'; t[4] = ' '; t[5] = 'i';
  1189. t[6] = 'n'; t[7] = '\n'; t[8] = ' '; t[9] = 'r'; t[10] = 'o';
  1190. t[11] = 'u'; t[12] = 'n'; t[13] = 'd'; t[14] = 0;
  1191. SAF_drawText(t,12,14,SAF_COLOR_BLACK,1);
  1192. SAF_drawText(SAF_intToStr(UTD_round,t),UTD_round < 10 ? 29 : 23,31,
  1193. SAF_COLOR_BLACK,2);
  1194. break;
  1195. }
  1196. case UTD_GAME_STATE_CONFIRM_QUIT:
  1197. case UTD_GAME_STATE_PLAYING:
  1198. case UTD_GAME_STATE_PLAYING_MENU:
  1199. case UTD_GAME_STATE_PLAYING_WAVE:
  1200. {
  1201. uint16_t *tile = UTD_mapTiles + UTD_cameraPos;
  1202. // draw tiles:
  1203. for (uint8_t y = 0; y < 64; y += 8)
  1204. {
  1205. for (uint8_t x = 0; x < 64; x += 8)
  1206. {
  1207. uint16_t v = *tile;
  1208. if (v != UTD_TILE_NONE)
  1209. {
  1210. if (UTD_IS_TOWER(v)) // tower?
  1211. UTD_drawTower(v,x,y);
  1212. else // path
  1213. {
  1214. UTD_drawImage(x,y,UTD_IMAGE_TILES - 3 + v,
  1215. UTD_backColor,(v == UTD_TILE_START || v == UTD_TILE_FINISH) ?
  1216. SAF_COLOR_BLACK : SAF_COLOR_GRAY_DARK,0,0,0);
  1217. }
  1218. }
  1219. tile++;
  1220. }
  1221. tile += UTD_MAP_WIDTH - 8;
  1222. }
  1223. UTD_Creep *c = UTD_creeps;
  1224. // draw creeps:
  1225. int8_t cameraPixelOffset = UTD_cameraPos * 8,
  1226. xMin = (UTD_cameraPos - 1) * 8;
  1227. uint8_t
  1228. xMax = (UTD_cameraPos + 8) * 8; // this might not fit into signed byte
  1229. for (uint8_t i = 0; i < UTD_creepCount; ++i)
  1230. {
  1231. int8_t x, y;
  1232. if (UTD_pathPositionTo2D(c->pathStart,c->pathPosition,&x,&y) &&
  1233. x >= xMin && x <= xMax)
  1234. {
  1235. uint8_t cType = c->typeFreeze & 0x0f;
  1236. UTD_drawImage(
  1237. x - cameraPixelOffset - 4,
  1238. y - 5 +
  1239. (((c->pathPosition >> 3) & 0x01) ==
  1240. ((UTD_updateCounter >> 2) & 0x01)),
  1241. UTD_IMAGE_CREEPS + cType * 2,
  1242. #if SAF_PLATFORM_COLOR_COUNT > 2
  1243. 0x77,
  1244. #else
  1245. SAF_COLOR_WHITE,
  1246. #endif
  1247. UTD_creepColor(cType),0,1,0);
  1248. }
  1249. c++;
  1250. }
  1251. tile = UTD_mapTiles;
  1252. uint8_t squarePos = 0;
  1253. // draw projectiles:
  1254. for (uint8_t y = 0; y < 64; y += 8)
  1255. {
  1256. for (uint8_t x = 0; x < 128; x += 8)
  1257. {
  1258. uint16_t v = *tile;
  1259. if (UTD_IS_TOWER(v)) // tower?
  1260. {
  1261. uint8_t attackProgress = UTD_TOWER_ATTACK_PROGRESS(v);
  1262. if (attackProgress != 0)
  1263. {
  1264. UTD_Creep c = UTD_creeps[UTD_TOWER_TARGET(v)];
  1265. int8_t cX, cY, tX, tY;
  1266. UTD_pathPositionTo2D(c.pathStart,c.pathPosition,&cX,&cY);
  1267. UTD_squarePositionTo2D(squarePos,&tX,&tY);
  1268. cX -= cameraPixelOffset;
  1269. tX -= cameraPixelOffset;
  1270. if (!UTD_TOWER_IS_SMALL(v))
  1271. {
  1272. tX += 4;
  1273. tY += 4;
  1274. }
  1275. uint8_t speed;
  1276. UTD_getTowerInstanceStats(v,0,0,&speed);
  1277. switch (UTD_TOWER_TYPE(v))
  1278. {
  1279. case UTD_TOWER_GUARD:
  1280. UTD_interpolateCoords(&cX,&cY,tX,tY,attackProgress,speed);
  1281. SAF_drawPixel(cX,cY,SAF_COLOR_BLACK);
  1282. if (cY != tY && (cX - tX) / (cY - tY) != 0)
  1283. cX++;
  1284. else
  1285. cY++;
  1286. SAF_drawPixel(cX,cY,SAF_COLOR_BLACK);
  1287. break;
  1288. case UTD_TOWER_CANNON:
  1289. UTD_interpolateCoords(&cX,&cY,tX,tY,attackProgress,speed);
  1290. SAF_drawPixel(cX,cY,0x00);
  1291. SAF_drawPixel(cX + 1,cY,0x64);
  1292. SAF_drawPixel(cX,cY + 1,0x64);
  1293. SAF_drawPixel(cX + 1,cY + 1,0x6d);
  1294. break;
  1295. case UTD_TOWER_ICE:
  1296. UTD_interpolateCoords(&cX,&cY,tX,tY,attackProgress,speed);
  1297. SAF_drawPixel(cX,cY,SAF_COLOR_BLUE);
  1298. cX++; cY--;
  1299. SAF_drawPixel(cX,cY,SAF_COLOR_BLUE);
  1300. cY += 2;
  1301. SAF_drawPixel(cX,cY,SAF_COLOR_BLUE);
  1302. cX -= 2;
  1303. SAF_drawPixel(cX,cY,SAF_COLOR_BLUE);
  1304. cY -= 2;
  1305. SAF_drawPixel(cX,cY,SAF_COLOR_BLUE);
  1306. break;
  1307. case UTD_TOWER_MAGIC:
  1308. UTD_interpolateCoords(&cX,&cY,tX,tY,attackProgress,speed);
  1309. SAF_drawCircle(cX - 2, cY - 2,2,SAF_frame(),0);
  1310. break;
  1311. case UTD_TOWER_SNIPER:
  1312. {
  1313. UTD_interpolateCoords(&cX,&cY,tX,tY,attackProgress,speed);
  1314. UTD_interpolateCoords(&tX,&tY,cX,cY,SAF_frame() % 8,8);
  1315. SAF_drawLine(cX,cY,tX,tY,0x62);
  1316. break;
  1317. }
  1318. case UTD_TOWER_ELECTRO:
  1319. if (UTD_TOWER_ATTACK_PROGRESS(v) > 2)
  1320. SAF_drawLine(tX,tY,
  1321. cX - 1 + SAF_frame() % 3,cY - 1 + SAF_frame() % 4,
  1322. SAF_frame() % 2 ? 0x02 : 0x5f);
  1323. break;
  1324. case UTD_TOWER_WATER:
  1325. {
  1326. UTD_interpolateCoords(&cX,&cY,tX,tY,attackProgress,speed);
  1327. SAF_drawCircle(cX - 2 + SAF_frame() % 2,cY - 2,3,0x01 + attackProgress / 2,1);
  1328. break;
  1329. }
  1330. case UTD_TOWER_FIRE:
  1331. {
  1332. UTD_interpolateCoords(&cX,&cY,tX,tY,attackProgress,speed);
  1333. uint8_t r = (9 - attackProgress);
  1334. uint8_t shift = SAF_frame() % 2;
  1335. for (int8_t dX = cX - r / 2 + shift;
  1336. dX < cX - r / 2 + r + shift; ++dX)
  1337. for (int8_t dY = cY - r / 2; dY < cY - r / 2 + r; ++dY)
  1338. if (SAF_random() & 0x10)
  1339. SAF_drawPixel(dX,dY,
  1340. #if SAF_PLATFORM_COLOR_COUNT == 2
  1341. SAF_COLOR_BLACK
  1342. #else
  1343. SAF_COLOR_RED
  1344. #endif
  1345. );
  1346. break;
  1347. }
  1348. default: break;
  1349. }
  1350. }
  1351. }
  1352. squarePos++;
  1353. tile++;
  1354. }
  1355. }
  1356. uint8_t cursorX = ((UTD_cursorPos - UTD_cameraPos) % UTD_MAP_WIDTH) * 8,
  1357. cursorY = (UTD_cursorPos / UTD_MAP_WIDTH) * 8;
  1358. for (uint8_t i = 0; i < 7; ++i) // cursor
  1359. {
  1360. uint8_t c = (((SAF_frame() >> 3) + i) % 3) ?
  1361. SAF_COLOR_BLACK : UTD_backColor;
  1362. SAF_drawPixel(cursorX + i,cursorY,c);
  1363. SAF_drawPixel(cursorX + 7,cursorY + i,c);
  1364. SAF_drawPixel(cursorX + 7 - i,cursorY + 7,c);
  1365. SAF_drawPixel(cursorX,cursorY + 7 - i,c);
  1366. }
  1367. // draw health bars:
  1368. if (SAF_buttonPressed(SAF_BUTTON_B))
  1369. {
  1370. c = UTD_creeps;
  1371. for (uint8_t i = 0; i < UTD_creepCount; ++i)
  1372. {
  1373. int8_t x, y;
  1374. if (UTD_pathPositionTo2D(c->pathStart,c->pathPosition,&x,&y) &&
  1375. x >= xMin && x <= xMax)
  1376. {
  1377. uint8_t cH,cS,cR;
  1378. UTD_getCreepStats(c->typeFreeze & 0x0f,&cH,&cS,&cR);
  1379. x = x - cameraPixelOffset - 4;
  1380. y -= 7;
  1381. SAF_drawRect(x,y,8,3,SAF_COLOR_BLACK,1);
  1382. x++;
  1383. y++;
  1384. uint16_t hp = c->healthLives & 0x3f;
  1385. hp = (hp * 5) / cH;
  1386. SAF_drawLine(x,y,x + hp,y,SAF_COLOR_RED);
  1387. }
  1388. c++;
  1389. }
  1390. }
  1391. // draw bar and menu:
  1392. uint8_t barY = UTD_cursorPos >= UTD_MAP_WIDTH ? 1 : SAF_SCREEN_HEIGHT - 5;
  1393. if (UTD_gameState == UTD_GAME_STATE_PLAYING_MENU ||
  1394. UTD_gameState == UTD_GAME_STATE_CONFIRM_QUIT)
  1395. {
  1396. barY = 1;
  1397. uint8_t menuY = SAF_SCREEN_HEIGHT - 9;
  1398. uint8_t drawRadius = 0;
  1399. uint8_t centerOffset = 4;
  1400. char itemName[13];
  1401. uint16_t item = UTD_getPlayMenuItem(UTD_menuItem,0,itemName);
  1402. if (UTD_playMenuItemAvailable(item))
  1403. {
  1404. if (UTD_IS_TOWER(item))
  1405. {
  1406. if (SAF_frame() & 0x08)
  1407. UTD_drawTower(item,cursorX,cursorY);
  1408. UTD_getTowerStats(item,&drawRadius,0,0,0,0);
  1409. if (!UTD_TOWER_IS_SMALL(item))
  1410. centerOffset = 8;
  1411. }
  1412. else if (item == UTD_UPGRADE1 || item == UTD_UPGRADE2)
  1413. {
  1414. // preview range upgrade
  1415. UTD_getTowerInstanceStats(UTD_mapTiles[UTD_cursorPos] | item,
  1416. &drawRadius,0,0);
  1417. }
  1418. }
  1419. if (drawRadius == 0)
  1420. {
  1421. uint16_t tile = UTD_mapTiles[UTD_cursorPos];
  1422. if (UTD_IS_TOWER(tile))
  1423. {
  1424. uint8_t tD,tS;
  1425. UTD_getTowerInstanceStats(tile,&drawRadius,&tD,&tS);
  1426. }
  1427. }
  1428. if (drawRadius != 0)
  1429. {
  1430. SAF_drawCircle(cursorX + centerOffset,cursorY + centerOffset,drawRadius,
  1431. SAF_COLOR_BLACK,0);
  1432. SAF_drawCircle(cursorX + centerOffset,cursorY + centerOffset,
  1433. SAF_frame() % drawRadius,SAF_COLOR_BLACK,0);
  1434. }
  1435. SAF_drawRect(0,menuY,SAF_SCREEN_WIDTH,
  1436. 9,SAF_COLOR_BLACK,1);
  1437. SAF_drawLine(0,menuY,SAF_SCREEN_WIDTH,menuY,SAF_COLOR_BLACK);
  1438. uint8_t menuOffset = UTD_menuItem > 3 ? UTD_menuItem - 3 : 0;
  1439. if (menuOffset > 5)
  1440. menuOffset = 5;
  1441. menuY++;
  1442. SAF_drawRect(0,SAF_SCREEN_HEIGHT - 15,SAF_SCREEN_WIDTH,6,
  1443. UTD_backColor,1);
  1444. SAF_drawText(itemName,1,SAF_SCREEN_HEIGHT - 14,SAF_COLOR_BLACK,1);
  1445. for (uint8_t i = 0; i < 8; ++i) // play menu items
  1446. {
  1447. uint16_t icon = 0;
  1448. uint8_t itemIndex = i + menuOffset;
  1449. uint16_t item = UTD_getPlayMenuItem(itemIndex,&icon,0);
  1450. uint8_t c1 = SAF_COLOR_WHITE,
  1451. c2 = SAF_COLOR_BLACK;
  1452. uint8_t highlightPos = UTD_menuItem - menuOffset;
  1453. if (highlightPos == i)
  1454. {
  1455. c1 = SAF_COLOR_BLUE_DARK;
  1456. c2 = SAF_COLOR_GREEN;
  1457. }
  1458. uint8_t drawX = i * 8;
  1459. UTD_drawImage(drawX,menuY,icon,c1,c2,0,0,0);
  1460. if (!UTD_playMenuItemAvailable(item))
  1461. for (uint8_t y = menuY; y < menuY + 8; ++y)
  1462. for (uint8_t x = drawX; x < drawX + 8; ++x)
  1463. if (x % 2 == y % 2)
  1464. SAF_drawPixel(x,y,SAF_COLOR_BLACK);
  1465. }
  1466. }
  1467. if (UTD_gameState == UTD_GAME_STATE_CONFIRM_QUIT)
  1468. {
  1469. SAF_drawRect(12,10,40,20,SAF_COLOR_BLACK,1);
  1470. SAF_drawRect(13,11,38,18,SAF_COLOR_WHITE,1);
  1471. SAF_drawText("QUIT?",19,18,SAF_COLOR_BLACK,1);
  1472. }
  1473. char text[8] = "$...";
  1474. SAF_intToStr(UTD_money,text + 1);
  1475. SAF_drawText(text,1,barY,0x0c,1);
  1476. SAF_intToStr(UTD_round,text);
  1477. SAF_drawText(text,37,barY,0x25,1);
  1478. SAF_intToStr(UTD_lives,text);
  1479. SAF_drawText(text,SAF_SCREEN_WIDTH - 10,barY,
  1480. #if SAF_PLATFORM_COLOR_COUNT == 2
  1481. SAF_COLOR_BLACK,
  1482. #else
  1483. 0x80,
  1484. #endif
  1485. 1);
  1486. break;
  1487. }
  1488. }
  1489. }
  1490. void UTD_towerHits(uint16_t tower, uint8_t isSplash)
  1491. {
  1492. uint8_t tD = 0;
  1493. UTD_getTowerInstanceStats(tower,0,&tD,0);
  1494. uint8_t creepIndex = UTD_TOWER_TARGET(tower);
  1495. uint8_t creepLives = UTD_creeps[creepIndex].healthLives & 0x3f;
  1496. uint16_t towerType = UTD_TOWER_TYPE(tower);
  1497. if (towerType == UTD_TOWER_ICE ||
  1498. (towerType == UTD_TOWER_ELECTRO && (tower & UTD_UPGRADE2)
  1499. && (SAF_random() % 4 == 0)))
  1500. {
  1501. // freeze / shock
  1502. UTD_creeps[creepIndex].typeFreeze =
  1503. (UTD_creeps[creepIndex].typeFreeze & 0x0f) |
  1504. ((UTD_creeps[creepIndex].typeFreeze & 0x0f) == UTD_CREEP_WOLF ?
  1505. 0x70 : 0xf0); // ^ wolves get frozen for shorter time
  1506. }
  1507. else if (towerType == UTD_TOWER_WATER && SAF_random() % 10 == 0)
  1508. UTD_creeps[creepIndex].pathPosition -= 10; // a wave can knock creeps back
  1509. if (isSplash)
  1510. {
  1511. tD /= 2; // splash deals half the damage
  1512. if (tD >= creepLives) // splash can't kill, it would complicate the loop
  1513. tD = creepLives - 1;
  1514. }
  1515. else
  1516. {
  1517. if (towerType == UTD_TOWER_CANNON || towerType == UTD_TOWER_FIRE)
  1518. {
  1519. int8_t x, y;
  1520. UTD_Creep *c = UTD_creeps + creepIndex;
  1521. UTD_pathPositionTo2D(c->pathStart,c->pathPosition,&x,&y);
  1522. c = UTD_creeps;
  1523. for (uint8_t i = 0; i < UTD_creepCount; ++i)
  1524. {
  1525. if (i != creepIndex && UTD_canAttack(tower,c->typeFreeze & 0x0f))
  1526. {
  1527. int8_t x2, y2;
  1528. UTD_pathPositionTo2D(c->pathStart,c->pathPosition,&x2,&y2);
  1529. if (UTD_isWithinRange(x,y,x2,y2,UTD_SPLASH_RANGE))
  1530. UTD_towerHits(UTD_TOWER_TARGET_SET(tower,i),1);
  1531. }
  1532. c++;
  1533. }
  1534. }
  1535. }
  1536. if (tD >= creepLives)
  1537. {
  1538. uint8_t c,cH,cS,cR;
  1539. UTD_Creep creep = UTD_creeps[creepIndex];
  1540. c = creep.typeFreeze & 0x0f;
  1541. UTD_getCreepStats(c,&cH,&cS,&cR);
  1542. UTD_money += cR;
  1543. UTD_creepEnds(creepIndex); // creep dies
  1544. UTD_playSound(SAF_SOUND_BUMP);
  1545. if (c == UTD_CREEP_SPIDER_BIG)
  1546. {
  1547. // big spider will spawn two small ones
  1548. UTD_addCreep(UTD_CREEP_SPIDER,creep.pathStart,creep.pathPosition,0);
  1549. UTD_addCreep(UTD_CREEP_SPIDER,creep.pathStart,creep.pathPosition - 5,0);
  1550. }
  1551. }
  1552. else
  1553. {
  1554. creepLives -= tD;
  1555. UTD_creeps[creepIndex].healthLives =
  1556. (UTD_creeps[creepIndex].healthLives & 0xc0) | creepLives;
  1557. }
  1558. }
  1559. void UTD_gameStep(void)
  1560. {
  1561. switch (UTD_gameState)
  1562. {
  1563. case UTD_GAME_STATE_PLAYING_MENU:
  1564. {
  1565. if (SAF_buttonJustPressed(SAF_BUTTON_B))
  1566. {
  1567. UTD_gameState = UTD_GAME_STATE_PLAYING;
  1568. return;
  1569. }
  1570. uint16_t item = UTD_getPlayMenuItem(UTD_menuItem,0,0);
  1571. if (SAF_buttonJustPressed(SAF_BUTTON_A) &&
  1572. UTD_playMenuItemAvailable(item))
  1573. {
  1574. if (UTD_IS_TOWER(item))
  1575. {
  1576. // build tower
  1577. UTD_mapTiles[UTD_cursorPos] = item;
  1578. uint8_t tP;
  1579. UTD_getTowerStats(item,0,0,0,&tP,0);
  1580. UTD_money -= tP;
  1581. UTD_gameState = UTD_GAME_STATE_PLAYING;
  1582. UTD_playSound(SAF_SOUND_CLICK);
  1583. }
  1584. else if (item == UTD_UPGRADE1 || item == UTD_UPGRADE2)
  1585. {
  1586. UTD_mapTiles[UTD_cursorPos] |= item;
  1587. UTD_money -= UTD_getUpgradeCost(UTD_mapTiles[UTD_cursorPos]);
  1588. UTD_gameState = UTD_GAME_STATE_PLAYING;
  1589. UTD_playSound(SAF_SOUND_CLICK);
  1590. }
  1591. else if (item == UTD_PLAY_MENU_SELL)
  1592. {
  1593. uint8_t tP = 0;
  1594. UTD_getTowerStats(UTD_TOWER_TYPE(UTD_mapTiles[UTD_cursorPos]),0,0,0,
  1595. &tP,0);
  1596. UTD_mapTiles[UTD_cursorPos] = UTD_TILE_NONE;
  1597. UTD_money += tP / 2;
  1598. UTD_gameState = UTD_GAME_STATE_PLAYING;
  1599. UTD_playSound(SAF_SOUND_BOOM);
  1600. }
  1601. else if (item == UTD_PLAY_MENU_GO)
  1602. {
  1603. UTD_updateCounter = 0;
  1604. UTD_spawnCreeps();
  1605. UTD_gameState = UTD_GAME_STATE_PLAYING_WAVE;
  1606. UTD_playSound(SAF_SOUND_BEEP);
  1607. }
  1608. else if (item == UTD_PLAY_MENU_QUIT)
  1609. {
  1610. UTD_gameState = UTD_GAME_STATE_CONFIRM_QUIT;
  1611. }
  1612. }
  1613. if (UTD_buttonPressedOrHeld(SAF_BUTTON_RIGHT))
  1614. UTD_menuItem = (UTD_menuItem < UTD_PLAY_MENU_ITEMS - 1) ?
  1615. UTD_menuItem + 1 : 0;
  1616. else if (UTD_buttonPressedOrHeld(SAF_BUTTON_LEFT))
  1617. UTD_menuItem = (UTD_menuItem > 0) ? UTD_menuItem - 1 :
  1618. UTD_PLAY_MENU_ITEMS - 1;
  1619. break;
  1620. }
  1621. case UTD_GAME_STATE_LOST:
  1622. if (SAF_buttonJustPressed(SAF_BUTTON_A) ||
  1623. SAF_buttonJustPressed(SAF_BUTTON_B))
  1624. UTD_goToMenu();
  1625. break;
  1626. case UTD_GAME_STATE_CONFIRM_QUIT:
  1627. if (SAF_buttonJustPressed(SAF_BUTTON_A))
  1628. {
  1629. UTD_goToMenu();
  1630. UTD_playSound(SAF_SOUND_BUMP);
  1631. }
  1632. else if (SAF_buttonJustPressed(SAF_BUTTON_B))
  1633. UTD_gameState = UTD_GAME_STATE_PLAYING_MENU;
  1634. break;
  1635. case UTD_GAME_STATE_MENU:
  1636. {
  1637. uint8_t click = 1;
  1638. if (UTD_buttonPressedOrHeld(SAF_BUTTON_RIGHT))
  1639. UTD_menuItem = (UTD_menuItem + 1) % (UTD_MAPS + 1);
  1640. else if (UTD_buttonPressedOrHeld(SAF_BUTTON_LEFT))
  1641. UTD_menuItem = UTD_menuItem == 0 ? UTD_MAPS : (UTD_menuItem - 1);
  1642. else if (SAF_buttonJustPressed(SAF_BUTTON_A))
  1643. {
  1644. if (UTD_menuItem < UTD_MAPS)
  1645. UTD_loadMap(UTD_menuItem);
  1646. else
  1647. UTD_sound = !UTD_sound;
  1648. }
  1649. else
  1650. click = 0;
  1651. if (click)
  1652. UTD_playSound(SAF_SOUND_CLICK);
  1653. break;
  1654. }
  1655. case UTD_GAME_STATE_PLAYING_WAVE:
  1656. case UTD_GAME_STATE_PLAYING:
  1657. {
  1658. if (UTD_gameState == UTD_GAME_STATE_PLAYING_WAVE)
  1659. {
  1660. UTD_updateCounter++;
  1661. UTD_Creep *c = UTD_creeps;
  1662. // update creeps:
  1663. for (uint8_t i = 0; i < UTD_creepCount; ++i)
  1664. {
  1665. uint8_t cH, cS, cR;
  1666. UTD_getCreepStats(c->typeFreeze & 0x0f,&cH,&cS,&cR);
  1667. uint8_t freeze = c->typeFreeze >> 4;
  1668. if (freeze != 0)
  1669. {
  1670. cS += 15;
  1671. c->typeFreeze = (c->typeFreeze & 0x0f) | ((freeze - 1) << 4);
  1672. }
  1673. if ((UTD_updateCounter % (cS)) == 0)
  1674. c->pathPosition++;
  1675. int8_t dummy1, dummy2;
  1676. if (c->pathPosition >= 0 && !UTD_pathPositionTo2D(
  1677. c->pathStart,c->pathPosition,&dummy1,&dummy2))
  1678. UTD_creepFinishes(i);
  1679. c++;
  1680. }
  1681. uint16_t *t = UTD_mapTiles;
  1682. // update towers:
  1683. if (UTD_updateCounter % 3 == 0)
  1684. for (uint16_t i = 0; i < UTD_MAP_WIDTH * UTD_MAP_HEIGHT; ++i)
  1685. {
  1686. uint16_t tower = *t;
  1687. if (!UTD_IS_TOWER(tower))
  1688. {
  1689. t++;
  1690. continue;
  1691. }
  1692. uint8_t attackProgress = UTD_TOWER_ATTACK_PROGRESS(tower);
  1693. if (UTD_TOWER_TYPE(tower) == UTD_TOWER_MAGIC &&
  1694. (tower & UTD_UPGRADE2))
  1695. UTD_applySpeedAura(i);
  1696. if (attackProgress == 0)
  1697. {
  1698. // look for a new target:
  1699. uint8_t skipFrozen = UTD_TOWER_TYPE(tower) == UTD_TOWER_ICE;
  1700. // ^ ice tower will always look for a new target to freeze
  1701. int8_t towerX, towerY, radius;
  1702. UTD_squarePositionTo2D(i,&towerX,&towerY);
  1703. if (!UTD_TOWER_IS_SMALL(tower))
  1704. {
  1705. towerX += 4;
  1706. towerY += 4;
  1707. }
  1708. uint8_t tR, tS, tD;
  1709. UTD_getTowerInstanceStats(tower,&tR,&tD,&tS);
  1710. radius = tR;
  1711. UTD_Creep *c = UTD_creeps;
  1712. for (uint8_t j = 0; j < UTD_creepCount; ++j)
  1713. {
  1714. int8_t x, y;
  1715. if (UTD_canAttack(tower,c->typeFreeze & 0x0f) &&
  1716. UTD_pathPositionTo2D(c->pathStart,c->pathPosition,&x,&y) &&
  1717. UTD_isWithinRange(x,y,towerX,towerY,radius) &&
  1718. !(skipFrozen && (c->typeFreeze >> 4 != 0)))
  1719. {
  1720. tower = UTD_TOWER_TARGET_SET(tower,j);
  1721. attackProgress = tS;
  1722. break;
  1723. }
  1724. c++;
  1725. }
  1726. }
  1727. else
  1728. {
  1729. if (attackProgress == 1) // hit
  1730. UTD_towerHits(tower,0);
  1731. attackProgress--;
  1732. }
  1733. tower = UTD_TOWER_ATTACK_PROGRESS_SET(tower,attackProgress);
  1734. *t = tower;
  1735. t++;
  1736. }
  1737. if (UTD_gameState != UTD_GAME_STATE_LOST && UTD_creepCount == 0)
  1738. {
  1739. UTD_money += UTD_WAVE_BASE_REWARD + UTD_round / 4;
  1740. UTD_round++;
  1741. UTD_gameState = UTD_GAME_STATE_PLAYING;
  1742. if (UTD_round > SAF_load(UTD_mapIndex))
  1743. SAF_save(UTD_mapIndex,UTD_round); // save high score
  1744. }
  1745. }
  1746. else if (SAF_buttonJustPressed(SAF_BUTTON_A))
  1747. {
  1748. UTD_menuItem = 0;
  1749. if (UTD_mapTiles[UTD_cursorPos] == UTD_TILE_NONE)
  1750. {
  1751. uint8_t cursorNotTop = UTD_cursorPos >= UTD_MAP_WIDTH,
  1752. cursorNotLeft = UTD_cursorPos % UTD_MAP_WIDTH != 0,
  1753. movedLeft = 0;
  1754. if (cursorNotLeft &&
  1755. UTD_IS_TOWER(UTD_mapTiles[UTD_cursorPos - 1]) &&
  1756. !UTD_TOWER_IS_SMALL(UTD_mapTiles[UTD_cursorPos - 1]))
  1757. {
  1758. UTD_cursorPos--;
  1759. movedLeft = 1;
  1760. }
  1761. else if (cursorNotTop &&
  1762. UTD_IS_TOWER(UTD_mapTiles[UTD_cursorPos - UTD_MAP_WIDTH]) &&
  1763. !UTD_TOWER_IS_SMALL(UTD_mapTiles[UTD_cursorPos - UTD_MAP_WIDTH]))
  1764. {
  1765. UTD_cursorPos -= UTD_MAP_WIDTH;
  1766. }
  1767. else if (cursorNotLeft && cursorNotTop &&
  1768. UTD_IS_TOWER(UTD_mapTiles[UTD_cursorPos - UTD_MAP_WIDTH - 1]) &&
  1769. !UTD_TOWER_IS_SMALL(UTD_mapTiles[UTD_cursorPos - UTD_MAP_WIDTH -
  1770. 1]))
  1771. {
  1772. UTD_cursorPos -= UTD_MAP_WIDTH + 1;
  1773. movedLeft = 1;
  1774. }
  1775. if (movedLeft && UTD_cameraPos > 0)
  1776. UTD_cameraPos--;
  1777. }
  1778. UTD_gameState = UTD_GAME_STATE_PLAYING_MENU;
  1779. return;
  1780. }
  1781. if (UTD_buttonPressedOrHeld(SAF_BUTTON_RIGHT))
  1782. {
  1783. UTD_cursorPos += (UTD_cursorPos % UTD_MAP_WIDTH) < UTD_MAP_WIDTH - 1 ?
  1784. 1 : 0;
  1785. if (SAF_buttonPressed(SAF_BUTTON_B) && UTD_cameraPos <
  1786. UTD_MAP_WIDTH / 2)
  1787. UTD_cameraPos++;
  1788. }
  1789. else if (UTD_buttonPressedOrHeld(SAF_BUTTON_LEFT))
  1790. {
  1791. UTD_cursorPos -= (UTD_cursorPos % UTD_MAP_WIDTH) > 0 ?
  1792. 1 : 0;
  1793. if (SAF_buttonPressed(SAF_BUTTON_B) && UTD_cameraPos > 0)
  1794. UTD_cameraPos--;
  1795. }
  1796. else if (UTD_buttonPressedOrHeld(SAF_BUTTON_UP))
  1797. UTD_cursorPos -= UTD_cursorPos >= UTD_MAP_WIDTH ? UTD_MAP_WIDTH : 0;
  1798. else if (UTD_buttonPressedOrHeld(SAF_BUTTON_DOWN))
  1799. UTD_cursorPos += UTD_cursorPos < (UTD_MAP_HEIGHT - 1) * UTD_MAP_WIDTH ?
  1800. UTD_MAP_WIDTH : 0;
  1801. uint8_t cursorX = UTD_cursorPos % UTD_MAP_WIDTH;
  1802. if (cursorX >= UTD_cameraPos + 8)
  1803. UTD_cameraPos++;
  1804. if (cursorX < UTD_cameraPos)
  1805. UTD_cameraPos--;
  1806. break;
  1807. }
  1808. defualt: break;
  1809. }
  1810. }
  1811. uint8_t SAF_loop(void)
  1812. {
  1813. UTD_gameStep();
  1814. UTD_draw();
  1815. return 1;
  1816. }