123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256 |
- /**
- @file utd.h
- microTD (uTD): SAF edition
- This is a SAF rewrite of a tiny tower defense game originally written for
- Arduboy. It adds some extra things such as color graphics and a new map.
- by drummyfish, 2021
- released under CC0 1.0, public domain
- small game manual:
- tower $ range speed damage upgrades notes
- -----------------------------------------------------------------------------
- guard 8 * ** * +range, +speed Shoots arrows.
- cannon 8 * * * +range, +damage Does splash damage.
- ice 17 * * +speed, +range Slows down enemies.
- electro 30 ** * ** +damage, shock Shoots lightning.
- sniper 45 ***** ** ** +speed, +range Covers huge range.
- magic 60 * * * +damage, speed aura Support tower.
- water 100 *** *** **** +range Can knock enemies back.
- fire 100 *** ** **** +range Does splash damage.
- creep hp speed $ notes attacked by: G C I E S M W F
- -----------------------------------------------------------------------------
- spider * ** 1 . . . . . . . .
- lizard * *** 1 . . . . . . . .
- snake ** ** 1 . . . . . . . .
- wolf *** ** 1 Good against cold. . . 50% . . . . .
- bat ** *** 1 . NO . . . . . .
- ent **** * 2 . . . . . . . .
- big spider *** ** 2 Spawns 2 small ones on death. . . . . . . . .
- ghost *** ** 2 NO NO . . NO . . .
- ogre ***** ** 3 . . . . . . . .
- dino ***** *** 3 . . . . . . . .
- demon ***** *** 3 Supposed to make you lose. NO NO NO NO NO NO . .
- TODO: procedurally generated maps?
- */
- #define SAF_PROGRAM_NAME "microTD"
- #define SAF_SETTING_FASTER_1BIT 2
- //#define SAF_SETTING_FORCE_1BIT 1
- #define SAF_SETTING_POKITTO_SCALE 2
- #define UTD_ALTERNATIVE_TILES 0
- #include "../saf.h"
- #define UTD_MAPS 5
- #define UTD_MAX_MAP_SIZE 34 ///< maximum map description size in bytes
- #define UTD_MAX_CREEPS 50 ///< maximum number of creeps present on map at once
- #define UTD_MAP_WIDTH 16 ///< map width in squares
- #define UTD_MAP_HEIGHT 8 ///< map height in squares
- #define UTD_MAPS 6 ///< total number of maps
- #define UTD_SPLASH_RANGE 12
- #define UTD_WAVE_BASE_REWARD 5
- // game states:
- #define UTD_GAME_STATE_MENU 0
- #define UTD_GAME_STATE_PLAYING 1
- #define UTD_GAME_STATE_PLAYING_MENU 2
- #define UTD_GAME_STATE_PLAYING_WAVE 3
- #define UTD_GAME_STATE_CONFIRM_QUIT 4
- #define UTD_GAME_STATE_LOST 5
- uint8_t UTD_gameState;
- uint8_t UTD_mapIndex;
- uint8_t UTD_sound;
- uint8_t UTD_backColor = SAF_COLOR_WHITE;
- uint8_t UTD_menuItem = 0; ///< currently selected menu item
- uint8_t UTD_cameraPos = 0; ///< horizontal camera position in squares
- uint8_t UTD_ramImage[10] = {8,8,0,0,0,0,0,0,0,0}; ///< 8x8 binary image in RAM
- uint8_t UTD_ramMask[10] = {8,8,0,0,0,0,0,0,0,0}; ///< 8x8 binary image mask
- uint8_t UTD_cursorPos = 0; ///< sequential square position of the cursor
- int8_t UTD_lives = 0; ///< player's current lives
- uint16_t UTD_money = 0; ///< player's current money
- uint16_t UTD_round = 0; ///< current round
- uint8_t UTD_updateCounter;
- /// Instance of a creep on the map.
- typedef struct
- {
- uint8_t typeFreeze; ///< lower 4 bits: type, upper 4 bits: freeze counter
- uint8_t healthLives; ///< lower 6 bits: health, upper 2 bits: lives
- uint8_t pathStart; ///< offset to path start within level data
- int16_t pathPosition; ///< position in pixels on path (can be negative)
- } UTD_Creep;
- UTD_Creep UTD_creeps[UTD_MAX_CREEPS]; ///< array of creeps currently on map
- uint8_t UTD_creepCount; ///< current number of creeps on the map
- #if SAF_PLATFORM_HARWARD
- #define UTD_PROGMEM const PROGMEM
- #else
- #define UTD_PROGMEM static const
- #endif
- /// array of 8x8 1bit images
- UTD_PROGMEM uint8_t UTD_images[] =
- {
- #define UTD_IMAGE_CREEPS 0 // creep images, each one followed by mask
- 0xff,0xff,0x99,0xc3,0x81,0xc3,0x99,0xff,
- 0x00,0x00,0x66,0x3c,0x7e,0x3c,0x66,0x00,
- 0xff,0x7f,0x7f,0x31,0x80,0xc3,0x99,0xff,
- 0x00,0x80,0x80,0xce,0x7f,0x3c,0x66,0x00,
- 0xe3,0xc9,0x83,0x9f,0xc3,0xf9,0x83,0xff,
- 0x1c,0x3e,0x7c,0x60,0x3c,0x06,0x7c,0x00,
- 0xff,0x79,0x32,0x80,0xc3,0x9b,0xb9,0xff,
- 0x00,0x86,0xcf,0x7f,0x3c,0x64,0x46,0x00,
- 0xff,0xbd,0x18,0x81,0x81,0xc3,0xdb,0xff,
- 0x00,0x42,0xe7,0x7e,0x7e,0x3c,0x24,0x00,
- 0xc3,0xc3,0xe7,0x81,0x24,0xe7,0xc3,0x99,
- 0x3c,0x3c,0x18,0x7e,0xdb,0x18,0x3c,0x66,
- 0xff,0x24,0x81,0xc3,0x00,0xc3,0x81,0x3c,
- 0x00,0xdb,0x7e,0x3c,0xff,0x3c,0x7e,0xc3,
- 0xe7,0xc3,0x5a,0x00,0x81,0x81,0xa5,0xff,
- 0x18,0x3c,0xbd,0xff,0x7e,0x7e,0x5a,0x00,
- 0x67,0x67,0x01,0x80,0xc2,0xc3,0xdb,0x99,
- 0x98,0x98,0xfe,0x7f,0x3d,0x3c,0x24,0x66,
- 0x73,0x71,0x24,0x00,0x83,0xc7,0x93,0xc9,
- 0x8c,0x8e,0xdf,0xff,0x7c,0x38,0x6c,0x36,
- 0x42,0x00,0xdb,0x81,0x00,0x42,0xc3,0x99,
- 0xbd,0xff,0x3c,0x7e,0xff,0xbd,0x3c,0x66,
- #define UTD_IMAGE_TILES 22
- #if UTD_ALTERNATIVE_TILES
- 0x7f,0xff,0x7f,0xff,0x7f,0xff,0x7f,0xaa,
- 0xff,0xc3,0x81,0x81,0x81,0x99,0xbd,0xff,
- 0x7f,0xfe,0x7f,0xfe,0x7f,0xfe,0x7f,0xfe,
- 0x55,0xff,0x7f,0xff,0x7f,0xff,0x7f,0xfe,
- 0x7f,0xff,0x7f,0xff,0x7f,0xff,0x7f,0xfe,
- 0xff,0xc3,0x99,0xbd,0xbd,0xbd,0xbd,0xff,
- 0x7f,0xfe,0xff,0xfe,0xff,0xfe,0xff,0xaa,
- 0x55,0xff,0xff,0xff,0xff,0xff,0xff,0xaa,
- 0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xaa,
- 0x55,0xfe,0xff,0xfe,0xff,0xfe,0xff,0xfe,
- 0x7f,0xfe,0xff,0xfe,0xff,0xfe,0xff,0xfe,
- 0x55,0xff,0xff,0xff,0xff,0xff,0xff,0xfe,
- 0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xfe,
- #else
- 0xef,0xf7,0xeb,0xf5,0xfa,0xff,0xff,0xff,
- 0xff,0xc3,0x81,0x81,0x81,0x99,0xbd,0xff,
- 0xef,0xf7,0xef,0xf7,0xef,0xf7,0xef,0xf7,
- 0xff,0xff,0xff,0xfd,0xfa,0xf5,0xeb,0xf7,
- 0xef,0xf7,0xef,0xf5,0xea,0xf7,0xef,0xf7,
- 0xff,0xc3,0x99,0xbd,0xbd,0xbd,0xbd,0xff,
- 0xef,0xd7,0xaf,0x5f,0xbf,0xff,0xff,0xff,
- 0xff,0xff,0xff,0x55,0xaa,0xff,0xff,0xff,
- 0xef,0xf7,0xef,0x55,0xaa,0xff,0xff,0xff,
- 0xff,0xff,0xff,0x5f,0xaf,0xd7,0xef,0xf7,
- 0xef,0xf7,0xef,0x57,0xaf,0xf7,0xef,0xf7,
- 0xff,0xff,0xff,0x55,0xaa,0xf7,0xef,0xf7,
- 0xef,0xf7,0xef,0x55,0xaa,0xf7,0xef,0xf7,
- #endif
- #define UTD_IMAGE_TOWERS (UTD_IMAGE_TILES + 13)
- 0xff,0xe7,0xdb,0xbd,0x99,0xdb,0xc3,0xff,
- 0xff,0xe7,0xc3,0x81,0x81,0xc3,0xc3,0xff,
- 0xff,0x81,0xbd,0xdb,0xdb,0xbd,0x81,0xff,
- 0xff,0x81,0x81,0xc3,0xc3,0x81,0x81,0xff,
- 0xff,0xe7,0xdb,0xdb,0xbd,0xbd,0x81,0xff,
- 0xff,0xe7,0xc3,0xc3,0x81,0x81,0x81,0xff,
- 0xff,0xdb,0xa5,0xbd,0xdb,0xbd,0x81,0xff,
- 0xff,0xdb,0x81,0x81,0xc3,0x81,0x81,0xff,
- 0xff,0x99,0xa5,0xbd,0xdb,0xdb,0xc3,0xff,
- 0xff,0x99,0x81,0x81,0xc3,0xc3,0xc3,0xff,
- 0xff,0xc3,0xbd,0xdb,0xbd,0xdb,0xc3,0xff,
- 0xff,0xc3,0x81,0xc3,0x81,0xc3,0xc3,0xff,
- #define UTD_IMAGE_TOWERS_BIG (UTD_IMAGE_TOWERS + 12)
- 0xff,0xfc,0xf9,0xf2,0xf6,0xf3,0xc1,0xcf,
- 0xff,0x3f,0x9f,0x4f,0x6f,0xcf,0x83,0xf3,
- 0xe1,0xfb,0xfb,0xf1,0xef,0xe5,0xe0,0xff,
- 0x87,0xdf,0xdf,0x8f,0xf7,0xa7,0x07,0xff,
- 0xff,0xfc,0xf8,0xf1,0xf1,0xf0,0xc0,0xdf,
- 0xff,0x3f,0x1f,0x8f,0x8f,0x0f,0x03,0xfb,
- 0xe0,0xf8,0xf8,0xf0,0xe0,0xea,0xe0,0xff,
- 0x07,0x1f,0x1f,0x0f,0x07,0x57,0x07,0xff,
- 0xff,0xf3,0xe5,0xec,0xe6,0xf3,0xfb,0xf9,
- 0xff,0xcf,0xa7,0x37,0x67,0xcf,0xdf,0x9f,
- 0xf3,0xe1,0xcf,0xe1,0xf7,0xed,0xe0,0xff,
- 0xcf,0x87,0xf3,0x87,0xef,0xb7,0x07,0xff,
- 0xff,0xf3,0xe1,0xec,0xe6,0xf2,0xf8,0xf8,
- 0xff,0xcf,0x87,0x37,0x67,0x4f,0x1f,0x1f,
- 0xf0,0xe1,0xdf,0xe1,0xf0,0xea,0xe0,0xff,
- 0x0f,0x87,0xfb,0x87,0x0f,0x57,0x07,0xff,
- #define UTD_IMAGE_ICONS (UTD_IMAGE_TOWERS_BIG + 16)
- 0xff,0xc3,0x99,0xa5,0xa5,0x99,0xdb,0xff,
- 0xff,0x99,0x81,0xa5,0xbd,0x99,0xdb,0xff,
- 0xff,0xef,0xc7,0xfd,0xf9,0xfd,0xfd,0xff,
- 0xff,0xdf,0x8f,0xf9,0xfd,0xfb,0xf9,0xff,
- 0xff,0xff,0x99,0xc3,0xe7,0xc3,0x99,0xff,
- 0xff,0xef,0xe7,0xe3,0xe3,0xe7,0xef,0xff,
- 0xff,0xc3,0x99,0xad,0xb5,0x99,0xc3,0xff,
- #define UTD_IMAGE_WAVE (UTD_IMAGE_ICONS + 7)
- 0xff,0xff,0xc7,0x93,0x39,0x7c,0xff,0xff
- };
- UTD_PROGMEM uint8_t UTD_logo[59] = // logo image, not 8x8
- {
- 0x19,0x12,0xfd,0xd5,0xdf,0xfc,0x60,0xc7,0xec,0x10,0x41,0xe3,0x1c,0x71,0xdb,
- 0x8e,0x38,0xc7,0x00,0x00,0x17,0x7f,0xff,0xf7,0x40,0x00,0x05,0x4f,0xcf,0xb9,
- 0x0d,0xb5,0x56,0x96,0xd8,0x89,0x43,0x6c,0x44,0xa5,0x8e,0x22,0x50,0xdf,0x11,
- 0x69,0x3f,0x08,0xe5,0x40,0x00,0x05,0xd5,0x55,0x55,0xf0,0x00,0x01,0xc0
- };
- // map tile constants:
- #define UTD_TILE_NONE 0x0000
- #define UTD_TILE_ROAD_U 0x0001 // road tiles can be combine with |
- #define UTD_TILE_ROAD_R 0x0002
- #define UTD_TILE_ROAD_D 0x0004
- #define UTD_TILE_ROAD_L 0x0008
- #define UTD_TILE_START 0x0004
- #define UTD_TILE_FINISH 0x0008
- #define UTD_TILE_ROAD_UD (UTD_TILE_ROAD_U | UTD_TILE_ROAD_D)
- #define UTD_TILE_ROAD_LR (UTD_TILE_ROAD_L | UTD_TILE_ROAD_R)
- /* Tower tile constants also server as tower IDs. The tower constant value
- format is following:
- MSB abcdefgh ijklmnop LSB
- - a: always 1
- - bcdefgh: targeted creep ID, only valid if attack progress != 0
- - ij: upgrade info
- - klm: attack progress, 0 or 1 = looking for target, 0 = no target
- - nop: tower type */
- #define UTD_TOWER_GUARD 0x8000
- #define UTD_TOWER_CANNON 0x8001
- #define UTD_TOWER_ICE 0x8002
- #define UTD_TOWER_ELECTRO 0x8003
- #define UTD_TOWER_SNIPER 0x8004
- #define UTD_TOWER_MAGIC 0x8005
- #define UTD_TOWER_WATER 0x8006
- #define UTD_TOWER_FIRE 0x8007
- #define UTD_TOWER_TYPE(v) (v & 0x8007)
- #define UTD_IS_TOWER(v) (v & 0x8000)
- #define UTD_TOWER_IMAGE(v) (UTD_IMAGE_TOWERS + (v & 0x07) * 2)
- #define UTD_TOWER_IS_SMALL(v) (((v) & 0x8007) < UTD_TOWER_WATER)
- #define UTD_TOWER_TARGET(v) (((v) >> 8) & 0x7f)
- #define UTD_TOWER_TARGET_SET(v,x) (((v) & 0x80ff) | (x) << 8)
- #define UTD_TOWER_ATTACK_PROGRESS(v) (((v) >> 3) & 0x07)
- #define UTD_TOWER_ATTACK_PROGRESS_SET(v,x) (((v) & 0xffc7) | ((x) << 3))
- #define UTD_TOWERS_SMALL 6 ///< total number of small tower types
- #define UTD_TOWERS (UTD_TOWERS_SMALL + 2)
- // play menu items:
- #define UTD_UPGRADE1 0x0080
- #define UTD_UPGRADE2 0x0040
- #define UTD_PLAY_MENU_SELL 0x000a
- #define UTD_PLAY_MENU_GO 0x000b
- #define UTD_PLAY_MENU_QUIT 0x000c
- #define UTD_UPGRADE_NONE 0x00
- #define UTD_UPGRADE_RANGE 0x01
- #define UTD_UPGRADE_SPEED 0x02
- #define UTD_UPGRADE_DAMAGE 0x03
- #define UTD_UPGRADE_SHOCK 0x04
- #define UTD_UPGRADE_SPEED_AURA 0x05
- #define UTD_PLAY_MENU_ITEMS 13
- #define UTD_CREEP_SPIDER 0x00
- #define UTD_CREEP_LIZARD 0x01
- #define UTD_CREEP_SNAKE 0x02
- #define UTD_CREEP_WOLF 0x03 ///< freeze is less effective
- #define UTD_CREEP_BAT 0x04 ///< immune to cannon
- #define UTD_CREEP_ENT 0x05
- #define UTD_CREEP_SPIDER_BIG 0x06 ///< spawns two small spiders when killed
- #define UTD_CREEP_GHOST 0x07 ///< immune to physical attack
- #define UTD_CREEP_OGRE 0x08
- #define UTD_CREEP_DINO 0x09
- #define UTD_CREEP_DEMON 0x0a ///< only attackable by fire/water
- #define UTD_CREEPS 11 ///< total number of creep types
-
- void UTD_loadImage(uint8_t index, uint8_t *dest)
- {
- const uint8_t *p = UTD_images + 8 * index;
- dest += 2;
- for (uint8_t i = 0; i < 8; ++i)
- {
- #if SAF_PLATFORM_HARWARD
- *dest = pgm_read_byte(p);
- #else
- *dest = *p;
- #endif
- p++;
- dest++;
- }
- }
- void UTD_drawLogo(void)
- {
- #if SAF_PLATFORM_HARWARD
- uint8_t img[sizeof(UTD_logo)];
- for (uint8_t i = 0; i < sizeof(UTD_logo); ++i)
- img[i] = pgm_read_byte(UTD_logo + i);
- #else
- const uint8_t *img = UTD_logo;
- #endif
- SAF_drawImage1Bit(img,19,6,0,SAF_COLOR_WHITE,
- #if SAF_PLATFORM_COLOR_COUNT > 2
- SAF_COLOR_RED_DARK,
- #else
- SAF_COLOR_BLACK,
- #endif
- 0);
- }
- /** Current map description. The format is following:
- Map specifies the layout of creep paths as a series of bytes. The format is
- following:
- - The map is a sequence of one of more path definitions terminated by a 0
- byte.
- - A path definition starts with starting square coordinates in a single byte,
- lower 4 bits are X, upper 4 bits are Y.
- - Path segments follow, each one as a single byte of two parts: the 4 highest
- bits are the direction (0 = up, 1 = right, 2 = down, 3 = left) and the 4
- lowest bits are a square distance towards the next segment.
- - The path definition is either terminated by a 0 byte (end of map definition)
- or 255 byte (end of path definition, another will follow). */
- uint8_t UTD_map[UTD_MAX_MAP_SIZE];
- /// 2D array of current map tiles computed from the map description.
- uint16_t UTD_mapTiles[UTD_MAP_WIDTH * UTD_MAP_HEIGHT];
- uint8_t UTD_minU8(uint8_t a, uint8_t b)
- {
- return a < b ? a : b;
- }
- void UTD_playSound(uint8_t index)
- {
- if (UTD_sound)
- SAF_playSound(index);
- }
- uint8_t UTD_buttonPressedOrHeld(uint8_t key)
- {
- uint8_t b = SAF_buttonPressed(key);
- return (b == 1) || (b >= 18);
- }
- void UTD_squarePositionTo2D(uint8_t squarePosition, int8_t *x, int8_t *y)
- {
- *x = (squarePosition % UTD_MAP_WIDTH) * 8 + 4;
- *y = (squarePosition / UTD_MAP_WIDTH) * 8 + 4;
- }
- void UTD_interpolateCoords(int8_t *x1, int8_t *y1, int8_t x2, int8_t y2,
- uint8_t t, uint8_t tMax)
- {
- *x1 += (((int16_t) (x2 - *x1)) * t) / tMax;
- *y1 += (((int16_t) (y2 - *y1)) * t) / tMax;
- }
- uint8_t UTD_canAttack(uint16_t tower, uint8_t creepType)
- {
- tower = UTD_TOWER_TYPE(tower);
- return
- // cannon can't attack flying
- !(tower == UTD_TOWER_CANNON && creepType == UTD_CREEP_BAT) &&
- // physical can't attack ghost
- !(creepType == UTD_CREEP_GHOST &&
- (tower == UTD_TOWER_CANNON ||
- tower == UTD_TOWER_GUARD ||
- tower == UTD_TOWER_SNIPER)) &&
- // demon can only be attacked by water or fire
- !(creepType == UTD_CREEP_DEMON &&
- tower != UTD_TOWER_WATER &&
- tower != UTD_TOWER_FIRE);
- }
- uint8_t UTD_towerColor(uint16_t towerType)
- {
- #if SAF_PLATFORM_COLOR_COUNT > 2
- switch (towerType)
- {
- case UTD_TOWER_GUARD: return 0x08; break;
- case UTD_TOWER_CANNON: return 0x20; break;
- case UTD_TOWER_ICE: return 0x01; break;
- case UTD_TOWER_ELECTRO: return 0x21; break;
- case UTD_TOWER_SNIPER: return 0x00; break;
- case UTD_TOWER_MAGIC: return 0x25; break;
- case UTD_TOWER_FIRE: return 0x40; break;
- case UTD_TOWER_WATER: return 0x05; break;
- default: break;
- }
- #else
- _SAF_UNUSED(towerType);
- #endif
- return SAF_COLOR_BLACK;
- }
- uint8_t UTD_creepColor(uint8_t creepType)
- {
- #if SAF_PLATFORM_COLOR_COUNT > 2
- switch (creepType)
- {
- case UTD_CREEP_SPIDER: return 0x20; break;
- case UTD_CREEP_LIZARD: return 0x04; break;
- case UTD_CREEP_SNAKE: return 0x01; break;
- case UTD_CREEP_WOLF: return 0x25; break;
- case UTD_CREEP_BAT: return 0x25; break;
- case UTD_CREEP_ENT: return 0x20; break;
- case UTD_CREEP_SPIDER_BIG: return 0x24; break;
- case UTD_CREEP_GHOST: return 0x25; break;
- case UTD_CREEP_OGRE: return 0x20; break;
- case UTD_CREEP_DINO: return 0x04; break;
- case UTD_CREEP_DEMON: return SAF_COLOR_BLACK; break;
- default: break;
- }
- #else
- _SAF_UNUSED(creepType);
- #endif
- return SAF_COLOR_BLACK;
- }
- void UTD_getCreepStats(uint8_t creep, uint8_t *hp, uint8_t *speed,
- uint8_t *reward)
- {
- // creep stats in a function (vs a table) avoids dealing with PROGMEM
- switch (creep)
- {
- #define C(c,h,s,r) case c: *hp = h; *speed = s; *reward = r; break;
- C(UTD_CREEP_SPIDER,7,2,1)
- C(UTD_CREEP_LIZARD,8,1,1)
- C(UTD_CREEP_SNAKE,12,2,1)
- C(UTD_CREEP_WOLF,20,2,1)
- C(UTD_CREEP_BAT,13,1,1)
- C(UTD_CREEP_ENT,43,4,2)
- C(UTD_CREEP_SPIDER_BIG,20,2,2)
- C(UTD_CREEP_GHOST,30,3,2)
- C(UTD_CREEP_OGRE,58,2,3)
- C(UTD_CREEP_DINO,63,1,3)
- C(UTD_CREEP_DEMON,63,1,3)
- #undef C
- default: break;
- }
- }
- /** Converts a position along a path to 2D pixel position on the map. Returns
- 0 if the position is not withing the path finish (before or after),
- otherwise 1. */
- uint8_t UTD_pathPositionTo2D(uint8_t pathStart, int16_t pathPosition,
- int8_t *x, int8_t *y)
- {
- uint8_t *s = UTD_map + pathStart;
- *x = (*s & 0x0f) * 8 + 4;
- *y = (*s >> 4) * 8 + 4;
- s++;
- if (pathPosition < 0)
- return 0;
- while (1) // for each segment
- {
- uint8_t segment = *s;
- if (segment == 0 || segment == 255) // no more segments?
- break;
- int8_t dx = 0, dy = 0;
- switch (segment >> 4)
- {
- case 0: dy = -1; break;
- case 1: dx = 1; break;
- case 2: dy = 1; break;
- case 3: dx = -1; break;
- default: break;
- }
- uint8_t segmentLength = (segment & 0x0f) * 8;
- if (pathPosition > segmentLength)
- {
- *x += dx * segmentLength;
- *y += dy * segmentLength;
- pathPosition -= segmentLength;
- }
- else
- {
- *x += dx * pathPosition;
- *y += dy * pathPosition;
- return 1;
- }
- s++;
- }
- return 0;
- }
- /**
- Decreases creep's lives and potentially sends it back to the start.
- */
- void UTD_creepEnds(uint8_t creepIndex)
- {
- uint8_t cH = 0, cS = 0, cR = 0;
- UTD_getCreepStats(UTD_creeps[creepIndex].typeFreeze & 0x0f,
- &cH,&cS,&cR);
- UTD_Creep *c = UTD_creeps + creepIndex;
- uint8_t lives = c->healthLives >> 6;
- uint8_t remove = lives == 0;
- if (remove)
- {
- // remove creep
- for (uint8_t i = creepIndex; i < UTD_creepCount - 1; ++i)
- UTD_creeps[i] = UTD_creeps[i + 1];
- UTD_creepCount--;
- }
- else
- {
- // send creep back to start
- c->healthLives = ((lives - 1) << 6) | cH;
- c->pathPosition = -5;
- }
- uint16_t *tile = UTD_mapTiles;
- // correct tower targets:
- for (uint8_t i = 0; i < UTD_MAP_WIDTH * UTD_MAP_HEIGHT; ++i)
- {
- uint16_t t = *tile;
- if (UTD_IS_TOWER(t))
- {
- uint8_t target = UTD_TOWER_TARGET(t);
- if (target == creepIndex)
- *tile = UTD_TOWER_ATTACK_PROGRESS_SET(t,0); // remove target
- else if (remove && target > creepIndex)
- *tile = UTD_TOWER_TARGET_SET(t,target - 1); // because creeps shifted
- }
- tile++;
- }
- }
- /**
- Checks a square and if it contains a tower, it progresses the tower's
- attack progress (for speed aura).
- */
- void UTD_speedUpTower(uint8_t square)
- {
- uint16_t t = UTD_mapTiles[square];
- if (UTD_IS_TOWER(t))
- {
- uint8_t attackProgress = UTD_TOWER_ATTACK_PROGRESS(t);
- if (attackProgress > 1)
- UTD_mapTiles[square] =
- UTD_TOWER_ATTACK_PROGRESS_SET(t,attackProgress - 1);
- }
- }
- void UTD_applySpeedAura(uint8_t center)
- {
- if (UTD_updateCounter % 8 != center % 8)
- return;
- uint8_t x = center % 8;
- uint8_t
- t = center >= UTD_MAP_WIDTH,
- r = x != UTD_MAP_WIDTH - 1,
- b = center < UTD_MAP_WIDTH * (UTD_MAP_HEIGHT - 1),
- l = x != 0;
- if (t)
- {
- UTD_speedUpTower(center - UTD_MAP_WIDTH);
- if (l)
- UTD_speedUpTower(center - UTD_MAP_WIDTH - 1);
- if (r)
- UTD_speedUpTower(center - UTD_MAP_WIDTH + 1);
- }
- if (l)
- UTD_speedUpTower(center - 1);
- if (r)
- UTD_speedUpTower(center + 1);
- if (b)
- {
- UTD_speedUpTower(center + UTD_MAP_WIDTH);
- if (l)
- UTD_speedUpTower(center + UTD_MAP_WIDTH - 1);
- if (r)
- UTD_speedUpTower(center + UTD_MAP_WIDTH + 1);
- }
- }
- void UTD_creepFinishes(uint8_t index)
- {
- uint8_t cH = 0, cS = 0, cR = 0;
- UTD_getCreepStats(UTD_creeps[index].typeFreeze & 0x0f,
- &cH,&cS,&cR);
- UTD_creepEnds(index);
- UTD_lives -= cR;
- if (UTD_lives <= 0)
- UTD_gameState = UTD_GAME_STATE_LOST;
- UTD_playSound(SAF_SOUND_BOOM);
- }
- void UTD_getTowerStats(uint16_t tower, uint8_t *range, uint8_t *speed,
- uint8_t *damage, uint8_t *price, uint8_t *upgrades)
- {
- // similarly to creep stats we store tower stats as a function code
- #define T(t,r,s,d,p,u1,u2) \
- case t: \
- if (range != 0) *range = r; \
- if (speed != 0) *speed = s; \
- if (damage != 0) *damage = d; \
- if (price != 0) *price = p; \
- if (upgrades != 0) *upgrades = u1 | (u2 << 4); break;
- switch (tower)
- {
- T(UTD_TOWER_GUARD, 30,5,2,8, UTD_UPGRADE_RANGE,UTD_UPGRADE_SPEED)
- T(UTD_TOWER_CANNON, 27,7,2,8, UTD_UPGRADE_RANGE,UTD_UPGRADE_DAMAGE)
- T(UTD_TOWER_ICE, 26,6,0,17, UTD_UPGRADE_SPEED,UTD_UPGRADE_RANGE)
- T(UTD_TOWER_ELECTRO,35,7,4,30, UTD_UPGRADE_DAMAGE,UTD_UPGRADE_SHOCK)
- T(UTD_TOWER_SNIPER, 60,4,3,45, UTD_UPGRADE_SPEED,UTD_UPGRADE_RANGE)
- T(UTD_TOWER_MAGIC, 25,7,2,60, UTD_UPGRADE_DAMAGE,UTD_UPGRADE_SPEED_AURA)
- T(UTD_TOWER_WATER, 40,3,7,100,UTD_UPGRADE_RANGE,UTD_UPGRADE_NONE)
- T(UTD_TOWER_FIRE, 38,5,8,100,UTD_UPGRADE_RANGE,UTD_UPGRADE_NONE)
- default: break;
- }
- #undef T
- }
- uint8_t UTD_getUpgradeCost(uint16_t tower)
- {
- uint8_t tP = 0;
- UTD_getTowerStats(UTD_TOWER_TYPE(tower),0,0,0,&tP,0);
- return (tP * 2) / 3;
- }
- void UTD_getTowerInstanceStats(uint16_t towerInstance, uint8_t *range,
- uint8_t *damage, uint8_t *speed)
- {
- uint8_t upgrades = 0;
- UTD_getTowerStats(UTD_TOWER_TYPE(towerInstance),
- range,speed,damage,0,&upgrades);
- for (uint8_t i = 0; i < 2; ++i)
- if (towerInstance & (i == 0 ? UTD_UPGRADE1 : UTD_UPGRADE2))
- switch (i == 0 ? (upgrades & 0x0f) : (upgrades >> 4))
- {
- case UTD_UPGRADE_RANGE: if (range != 0) *range += *range / 4; break;
- case UTD_UPGRADE_DAMAGE: if (damage != 0) *damage += *damage / 2; break;
- case UTD_UPGRADE_SPEED: if (speed != 0) *speed -= 2; break;
- default: break;
- }
- }
- /** Gets the play menu item constant as a return value, its image in the
- pointer parameter (if non-0) and its name in a pointer parameter (if non-0,
- needs to be at least 13 bytes). */
- uint16_t UTD_getPlayMenuItem(uint8_t index, uint16_t *imageIndex, char *name)
- {
- uint16_t result = index;
- if (index <= 7) // small tower?
- result = (0x8000 | index);
- else if (index == 8)
- result = UTD_UPGRADE1;
- else if (index == 9)
- result = UTD_UPGRADE2;
- if (imageIndex != 0)
- *imageIndex = index <= 5 ?
- (UTD_IMAGE_TOWERS + index * 2 + 1) :
- (UTD_IMAGE_ICONS + index - 6);
- if (name != 0)
- {
- uint8_t end = 0;
- uint8_t price = 0;
- if (UTD_IS_TOWER(result))
- UTD_getTowerStats(result,0,0,0,&price,0);
- switch (result)
- {
- #define C(n,c) name[n] = c;
- case UTD_TOWER_GUARD:
- C(0,'G') C(1,'u') C(2,'a') C(3,'r') C(4,'d') end = 5; break;
- case UTD_TOWER_CANNON:
- C(0,'C') C(1,'a') C(2,'n') C(3,'n') C(4,'o') C(5,'n') end = 6; break;
- case UTD_TOWER_ICE:
- C(0,'I') C(1,'c') C(2,'e') end = 3; break;
- case UTD_TOWER_MAGIC:
- C(0,'M') C(1,'a') C(2,'g') C(3,'i') C(4,'c') end = 5; break;
- case UTD_TOWER_SNIPER:
- C(0,'S') C(1,'n') C(2,'i') C(3,'p') C(4,'e') C(5,'r') end = 6; break;
- case UTD_TOWER_ELECTRO:
- C(0,'E') C(1,'l') C(2,'e') C(3,'c') C(4,'t') C(5,'r') end = 6; break;
- case UTD_TOWER_WATER:
- C(0,'W') C(1,'A') C(2,'T') C(3,'E') C(4,'R') end = 5; break;
- case UTD_TOWER_FIRE:
- C(0,'F') C(1,'I') C(2,'R') C(3,'E') end = 4; break;
- case UTD_PLAY_MENU_GO:
- C(0,'G') C(1,'o') C(2,'!') end = 3; break;
- case UTD_PLAY_MENU_SELL:
- C(0,'S') C(1,'e') C(2,'l') C(3,'l') end = 4; break;
- case UTD_PLAY_MENU_QUIT:
- C(0,'Q') C(1,'u') C(2,'i') C(3,'t') end = 4; break;
- case UTD_UPGRADE1:
- case UTD_UPGRADE2:
- {
- uint8_t upgradeType = 0;
- uint16_t t = UTD_TOWER_TYPE(UTD_mapTiles[UTD_cursorPos]);
- price = UTD_getUpgradeCost(t);
- UTD_getTowerStats(t,0,0,0,0,&upgradeType);
- upgradeType = result == UTD_UPGRADE1 ?
- (upgradeType & 0x0f) : (upgradeType >> 4);
- switch (upgradeType)
- {
- case UTD_UPGRADE_RANGE:
- C(0,'+') C(1,'R') C(2,'N') C(3,'G') end = 4; break;
- case UTD_UPGRADE_DAMAGE:
- C(0,'+') C(1,'D') C(2,'M') C(3,'G') end = 4; break;
- case UTD_UPGRADE_SPEED:
- C(0,'+') C(1,'S') C(2,'P') C(3,'D') end = 4; break;
- case UTD_UPGRADE_SHOCK:
- C(0,'+') C(1,'S') C(2,'H') C(3,'O') C(4,'K') end = 5; break;
- case UTD_UPGRADE_SPEED_AURA:
- C(0,'+') C(1,'A') C(2,'U') C(3,'R') C(4,'A') end = 5; break;
- default: *name = 0; price = 0; break;
- }
-
- break;
- }
- default: *name = 0; break;
- #undef CH
- }
- name[end] = 0;
- if (price != 0)
- {
- name += end;
- *name = ' ';
- name++;
- *name = '$';
- name++;
- SAF_intToStr(price,name);
- }
- }
- return result;
- }
- uint8_t UTD_playMenuItemAvailable(uint16_t item) // uint8_t index)
- {
- uint16_t tile = UTD_mapTiles[UTD_cursorPos];
- if (UTD_IS_TOWER(item))
- {
- if (tile != UTD_TILE_NONE)
- return 0; // can't build on non-empty tile
- uint8_t price;
- UTD_getTowerStats(UTD_TOWER_TYPE(item),0,0,0,&price,0);
- if (price > UTD_money)
- return 0;
- if (
- (UTD_cursorPos >= UTD_MAP_WIDTH) &&
- (UTD_cursorPos % UTD_MAP_WIDTH > 0))
- {
- // big tower blocks the space?
-
- uint16_t t = UTD_mapTiles[UTD_cursorPos - 1];
- if (UTD_IS_TOWER(t) && !UTD_TOWER_IS_SMALL(t)) return 0;
-
- t = UTD_mapTiles[UTD_cursorPos - UTD_MAP_WIDTH];
- if (UTD_IS_TOWER(t) && !UTD_TOWER_IS_SMALL(t)) return 0;
-
- t = UTD_mapTiles[UTD_cursorPos - UTD_MAP_WIDTH - 1];
- if (UTD_IS_TOWER(t) && !UTD_TOWER_IS_SMALL(t)) return 0;
- }
- if (!UTD_TOWER_IS_SMALL(item))
- {
- if ( // big towers need 2x2 squares space
- (UTD_cursorPos % UTD_MAP_WIDTH == UTD_MAP_WIDTH - 1) ||
- (UTD_cursorPos >= (UTD_MAP_HEIGHT - 1) * UTD_MAP_WIDTH) ||
- (UTD_mapTiles[UTD_cursorPos + 1] != UTD_TILE_NONE) ||
- (UTD_mapTiles[UTD_cursorPos + UTD_MAP_WIDTH] != UTD_TILE_NONE) ||
- (UTD_mapTiles[UTD_cursorPos + UTD_MAP_WIDTH + 1] != UTD_TILE_NONE)
- )
- return 0;
- }
- }
- else if (item == UTD_UPGRADE1)
- return (UTD_IS_TOWER(tile) != 0) && ((tile & UTD_UPGRADE1) == 0) &&
- (UTD_getUpgradeCost(UTD_TOWER_TYPE(tile)) <= UTD_money);
- else if (item == UTD_UPGRADE2)
- return UTD_IS_TOWER(tile) && UTD_TOWER_IS_SMALL(tile) &&
- ((tile & UTD_UPGRADE2) == 0) &&
- (UTD_getUpgradeCost(UTD_TOWER_TYPE(tile)) <= UTD_money);
- else if (item == UTD_PLAY_MENU_SELL) // sell
- return (UTD_IS_TOWER(tile) != 0);
- return 1;
- }
- void UTD_addCreep(uint8_t creep, uint8_t pathIndex, int16_t position,
- uint8_t lives)
- {
- if (UTD_creepCount >= UTD_MAX_CREEPS)
- return;
- UTD_Creep *c = UTD_creeps + UTD_creepCount;
- uint8_t cH = 0, cS = 0, cR = 0;
- UTD_getCreepStats(creep,&cH,&cS,&cR);
- c->typeFreeze = creep;
- c->healthLives = (lives << 6) | cH;
- c->pathPosition = position;
- c->pathStart = 0;
- while (pathIndex > 0)
- {
- while (UTD_map[c->pathStart] != 0 && UTD_map[c->pathStart] != 255)
- c->pathStart++;
- if (UTD_map[c->pathStart] == 255)
- c->pathStart++;
- else
- c->pathStart = 0;
- pathIndex--;
- }
- UTD_creepCount++;
- }
- uint8_t UTD_cycleCreeps(uint8_t index, uint8_t levelFrom, uint8_t levelTo)
- {
- if (levelFrom >= UTD_CREEPS)
- levelFrom = UTD_CREEPS - 1;
- if (levelTo >= UTD_CREEPS)
- levelTo = UTD_CREEPS - 1;
- return levelFrom + (index % (levelTo - levelFrom + 1));
- }
- void UTD_cyclingSpawn(
- uint8_t total, // total number of creeps to spawn
- uint8_t levelFrom, // minimum level of a creep
- uint8_t levelTo, // maximum level of a creep
- uint8_t maxLives, // maximum number of lives for a creep
- uint8_t positionOffset, // gaps between creeps
- uint8_t groupSize // affects additional gaps and creep lives
- )
- {
- total = UTD_minU8(UTD_MAX_CREEPS,total);
- int16_t position = -1 * positionOffset;
- for (uint8_t i = 0; i < total; ++i)
- {
- if (i % groupSize == 0)
- position -= positionOffset;
- UTD_addCreep(
- UTD_cycleCreeps(i,levelFrom,levelTo),
- i,position,i % groupSize == 0 ? UTD_minU8(3,maxLives) : 0);
- position -= positionOffset;
- }
- }
- void UTD_goToMenu(void)
- {
- UTD_gameState = UTD_GAME_STATE_MENU;
- UTD_menuItem = 0;
- UTD_backColor = 0xff;
- }
- void UTD_spawnCreeps(void)
- {
- UTD_creepCount = 0;
- switch (UTD_mapIndex)
- {
- case 0:
- case 1:
- {
- UTD_cyclingSpawn(1 + UTD_round,UTD_round / 4,
- UTD_round < 15 ? UTD_minU8(UTD_CREEP_DINO - 1,UTD_round) : UTD_round,
- UTD_round / 5,10,5);
- break;
- }
- case 2:
- {
- uint8_t total = UTD_minU8(1 + UTD_round,(UTD_MAX_CREEPS * 2) / 3);
- int16_t pos = -10;
- for (uint8_t i = 0; i < total; ++i)
- {
- uint8_t c = UTD_cycleCreeps(i,UTD_round / 3,(UTD_round * 2) / 3);
- uint8_t l = i % 3 == 0 ? UTD_minU8(3,UTD_round / 7) : 0;
- UTD_addCreep(c,0,pos,l);
- pos -= 10;
- if (i % 2 == 0)
- UTD_addCreep(c,1,pos,l);
- }
- break;
- }
- case 3:
- UTD_cyclingSpawn(3 + UTD_round * 2,UTD_round / 3,(UTD_round * 4) / 5,
- UTD_round / 8,7,4);
- break;
- case 4:
- UTD_cyclingSpawn(8 + UTD_round * 2,UTD_round / 3,2 + (UTD_round * 4) / 5,
- UTD_round / 8,10,4);
- break;
- case 5:
- {
- UTD_cyclingSpawn((1 + UTD_round / 2) * 2,1 + UTD_round / 3,
- 1 + UTD_round,UTD_round / 8,15,5);
- UTD_cyclingSpawn(UTD_round * 2,UTD_round / 4,UTD_round / 2,
- UTD_round / 8,8,4);
- break;
- }
- }
- }
- void UTD_loadMap(uint8_t index)
- {
- for (uint8_t i = 0; i < UTD_MAX_MAP_SIZE; ++i)
- UTD_map[i] = 0;
- UTD_menuItem = 0;
- UTD_cursorPos = UTD_MAP_WIDTH + 1;
- UTD_cameraPos = 0;
- UTD_mapIndex = index;
- UTD_creepCount = 0;
- UTD_round = 0;
- UTD_gameState = UTD_GAME_STATE_PLAYING;
- switch (index)
- {
- // by initializing maps in code we also avoid dealing with PROGMEM
- case 0:
- UTD_map[0] = 0x11;
- UTD_map[1] = 0x25;
- UTD_map[2] = 0x12;
- UTD_map[3] = 0x05;
- UTD_map[4] = 0x18;
- UTD_map[5] = 0x22;
- UTD_map[6] = 0x35;
- UTD_map[7] = 0x23;
- UTD_map[8] = 0x17;
- UTD_map[9] = 0x05;
- UTD_lives = 20;
- UTD_money = 10;
- UTD_backColor = 0xfe;
- break;
- case 1:
- UTD_map[0] = 0x11;
- UTD_map[1] = 0x22;
- UTD_map[2] = 0x17;
- UTD_map[3] = 0x21;
- UTD_map[4] = 0x12;
- UTD_map[5] = 0x22;
- UTD_map[6] = 0x14;
- UTD_map[7] = 0x03;
- UTD_map[8] = 0x32;
- UTD_map[9] = 0x02;
- UTD_map[10] = 0x34;
- UTD_map[11] = 0xff;
- UTD_map[12] = 0x61;
- UTD_map[13] = 0x03;
- UTD_map[14] = 0x13;
- UTD_map[15] = 0x22;
- UTD_map[16] = 0x11;
- UTD_map[17] = 0x21;
- UTD_map[18] = 0x19;
- UTD_map[19] = 0x03;
- UTD_map[20] = 0x32;
- UTD_map[21] = 0x02;
- UTD_map[22] = 0x34;
- UTD_lives = 25;
- UTD_money = 10;
- UTD_backColor = 0xde;
- break;
- case 2:
- UTD_map[0] = 0x11;
- UTD_map[1] = 0x25;
- UTD_map[2] = 0x12;
- UTD_map[3] = 0x02;
- UTD_map[4] = 0x12;
- UTD_map[5] = 0x22;
- UTD_map[6] = 0x13;
- UTD_map[7] = 0x03;
- UTD_map[8] = 0x32;
- UTD_map[9] = 0x02;
- UTD_map[10] = 0x33;
- UTD_map[11] = 0xff;
- UTD_map[12] = 0x1e;
- UTD_map[13] = 0x34;
- UTD_map[14] = 0x25;
- UTD_map[15] = 0x12;
- UTD_map[16] = 0x03;
- UTD_map[17] = 0x12;
- UTD_map[18] = 0x23;
- UTD_lives = 20;
- UTD_money = 10;
- UTD_backColor = 0xdf;
- break;
-
- case 3:
- UTD_map[0] = 0x72;
- UTD_map[1] = 0x02;
- UTD_map[2] = 0x1c;
- UTD_map[3] = 0x22;
- UTD_map[4] = 0x37;
- UTD_map[5] = 0x06;
- UTD_map[6] = 0x32;
- UTD_map[7] = 0x22;
- UTD_map[8] = 0x34;
- UTD_map[9] = 0x01;
- UTD_map[10] = 0x13;
- UTD_map[11] = 0xff;
- UTD_map[12] = 0x72;
- UTD_map[13] = 0x02;
- UTD_map[14] = 0x1c;
- UTD_map[15] = 0x22;
- UTD_map[16] = 0x37;
- UTD_map[17] = 0x06;
- UTD_map[18] = 0x12;
- UTD_map[19] = 0x22;
- UTD_map[20] = 0x14;
- UTD_map[21] = 0x01;
- UTD_map[22] = 0x33;
- UTD_lives = 25;
- UTD_money = 8;
- UTD_backColor = 0xfb;
- break;
- case 4:
- UTD_map[0] = 0x44;
- UTD_map[1] = 0x02;
- UTD_map[2] = 0x33;
- UTD_map[3] = 0x24;
- UTD_map[4] = 0x15;
- UTD_map[5] = 0x05;
- UTD_map[6] = 0x12;
- UTD_map[7] = 0x25;
- UTD_map[8] = 0x13;
- UTD_map[9] = 0x05;
- UTD_map[10] = 0x13;
- UTD_map[11] = 0xff;
- UTD_map[12] = 0x6d;
- UTD_map[13] = 0x03;
- UTD_map[14] = 0x35;
- UTD_map[15] = 0x23;
- UTD_map[16] = 0x32;
- UTD_map[17] = 0x03;
- UTD_map[18] = 0x31;
- UTD_map[19] = 0x02;
- UTD_map[20] = 0x33;
- UTD_map[21] = 0x23;
- UTD_lives = 30;
- UTD_money = 22;
- UTD_backColor = 0xbe;
- break;
-
- case 5:
- UTD_map[0] = 0x55;
- UTD_map[1] = 0x34;
- UTD_map[2] = 0x04;
- UTD_map[3] = 0x13;
- UTD_map[4] = 0x21;
- UTD_map[5] = 0x15;
- UTD_map[6] = 0x01;
- UTD_map[7] = 0x15;
- UTD_map[8] = 0x23;
- UTD_map[9] = 0x11;
- UTD_map[10] = 0x22;
- UTD_map[11] = 0x36;
- UTD_map[12] = 0x02;
- UTD_map[13] = 0x34;
- UTD_map[14] = 0xff;
- UTD_map[15] = 0x55;
- UTD_map[16] = 0x11;
- UTD_map[17] = 0x01;
- UTD_map[18] = 0x13;
- UTD_map[19] = 0x22;
- UTD_map[20] = 0x16;
- UTD_map[21] = 0x02;
- UTD_map[22] = 0x31;
- UTD_map[23] = 0x03;
- UTD_map[24] = 0x35;
- UTD_map[25] = 0x21;
- UTD_map[26] = 0x35;
- UTD_map[27] = 0x01;
- UTD_map[28] = 0x33;
- UTD_map[29] = 0x24;
- UTD_map[30] = 0x13;
- UTD_map[31] = 0x01;
- UTD_map[32] = 0x11;
- UTD_lives = 1;
- UTD_money = 30;
- UTD_backColor = 0xda;
- break;
- default: break;
- }
- // create the tilemap:
- for (uint8_t i = 0; i < UTD_MAP_WIDTH * UTD_MAP_HEIGHT; ++i)
- UTD_mapTiles[i] = UTD_TILE_NONE;
- uint8_t *b = UTD_map;
- uint8_t end = 0;
- while (!end) // for each path
- {
- uint16_t *currentTile = UTD_mapTiles +
- (*b >> 4) * UTD_MAP_WIDTH + (*b & 0x0f);
- uint16_t *previousTile = currentTile;
- uint16_t *pathStartTile = currentTile;
- b++;
- while (1) // for each segment
- {
- uint8_t v = *b;
- b++;
- if (v == 0) // end of map?
- {
- end = 1;
- break;
- }
- if (v == 255) // end of segment?
- break;
- int8_t tileShift = 0;
- uint16_t d1 = UTD_TILE_ROAD_U,
- d2 = UTD_TILE_ROAD_D;
- switch (v >> 4)
- {
- case 0:
- tileShift = -1 * UTD_MAP_WIDTH;
- break;
- case 1:
- tileShift = 1;
- d1 = UTD_TILE_ROAD_R;
- d2 = UTD_TILE_ROAD_L;
- break;
- case 2:
- tileShift = UTD_MAP_WIDTH;
- d1 = UTD_TILE_ROAD_D;
- d2 = UTD_TILE_ROAD_U;
- break;
- case 3:
- tileShift = -1;
- d1 = UTD_TILE_ROAD_L;
- d2 = UTD_TILE_ROAD_R;
- break;
- default: break;
- }
- for (uint8_t i = 0; i < (v & 0x0f); ++i)
- {
- previousTile = currentTile;
- currentTile += tileShift;
- *previousTile |= d1;
- *currentTile |= d2;
- }
- }
- *currentTile = UTD_TILE_FINISH;
- *pathStartTile = UTD_TILE_START;
- }
- }
- uint8_t UTD_isWithinRange(int8_t x, int8_t y, int8_t x2, int8_t y2,
- uint8_t range)
- {
- x = (x >= x2) ? (x - x2) : (x2 - x);
- // first quickly test Chebyshev distance:
- if (x <= range)
- {
- y = (y >= y2) ? (y - y2) : (y2 - y);
- // now test proper Euclidean distance:
- if (y <= range && SAF_sqrt(x * x + y * y) <= range)
- return 1;
- }
-
- return 0;
- }
- #define UTD_DRAW_MODE_WHOLE 0
- #define UTD_DRAW_MODE_TOP 1
- #define UTD_DRAW_MODE_BOTTOM 2
- void UTD_drawImage(uint8_t x, uint8_t y, uint8_t imageIndex, uint8_t color1,
- uint8_t color2, uint8_t transform, uint8_t hasMask, uint8_t mode)
- {
- UTD_loadImage(imageIndex,UTD_ramImage);
- const uint8_t *mask = 0;
- if (hasMask)
- {
- UTD_loadImage(imageIndex + 1,UTD_ramMask);
- mask = UTD_ramMask;
- }
- if (mode == UTD_DRAW_MODE_TOP)
- {
- UTD_ramImage[1] = 4;
- hasMask = 0;
- }
- else if (mode == UTD_DRAW_MODE_BOTTOM)
- {
- UTD_ramImage[1] = 4;
- y += 4;
- hasMask = 0;
- for (uint8_t i = 2; i < 6; ++i)
- UTD_ramImage[i] = UTD_ramImage[i + 4];
- }
- SAF_drawImage1Bit(UTD_ramImage,x,y,mask,color1,color2,transform);
- UTD_ramImage[1] = 8;
- }
- void SAF_init(void)
- {
- UTD_sound = 1;
- UTD_goToMenu();
- }
- void UTD_drawTower(uint16_t tower, uint8_t x, uint8_t y)
- {
- uint16_t imageIndex = UTD_TOWER_IMAGE(tower);
- uint8_t c = UTD_towerColor(UTD_TOWER_TYPE(tower));
- if (UTD_TOWER_IS_SMALL(tower))
- {
- UTD_drawImage(x,y,imageIndex + (tower & UTD_UPGRADE1 ? 1 : 0),
- UTD_backColor,c,0,0,UTD_DRAW_MODE_TOP);
- UTD_drawImage(x,y,imageIndex + (tower & UTD_UPGRADE2 ? 1 : 0),
- UTD_backColor,c,0,0,UTD_DRAW_MODE_BOTTOM);
- }
- else
- {
- if (UTD_TOWER_TYPE(tower) == UTD_TOWER_FIRE)
- imageIndex = UTD_IMAGE_TOWERS_BIG + 8;
- if (tower & UTD_UPGRADE1)
- imageIndex += 4;
- #define drawPart(px,py) \
- UTD_drawImage(x + px,y + py,imageIndex,UTD_backColor,c,\
- 0,0,UTD_DRAW_MODE_WHOLE);\
- imageIndex++;
- drawPart(0,0)
- drawPart(8,0)
- drawPart(0,8)
- drawPart(8,8)
- #undef drawPart
- }
- }
- void UTD_draw(void)
- {
- /* Someone once said: "Do not write functions longer than one screen."
- He was a stupid man. */
- SAF_clearScreen(UTD_backColor);
- switch (UTD_gameState)
- {
- case UTD_GAME_STATE_MENU:
- {
- UTD_drawLogo();
- int8_t pos = 0 - (SAF_frame() % 8);
- for (uint8_t i = 0; i < 9; ++i) // draw the wave
- {
- UTD_drawImage(pos,28,UTD_IMAGE_WAVE,
- SAF_COLOR_WHITE,SAF_COLOR_BLUE_DARK,0,0,0);
- pos += 8;
- }
- SAF_drawRect(0,40,SAF_SCREEN_WIDTH,6,SAF_COLOR_GRAY_DARK,1);
- char t[10] = "MAP x";
- if (UTD_menuItem == UTD_MAPS)
- {
- t[0] = 'S'; t[1] = 'N'; t[2] = 'D'; t[3] = ':';
- t[4] = 'o';
- if (UTD_sound)
- {
- t[5] = 'n'; t[6] = 0;
- }
- else
- {
- t[5] = 'f'; t[6] = 'f'; t[7] = 0;
- }
- }
- else
- t[4] = '1' + UTD_menuItem;
- if (UTD_menuItem < UTD_MAPS)
- {
- SAF_drawText(t,18,41,SAF_COLOR_YELLOW,1);
- t[0] = 'b'; t[1] = 'e'; t[2] = 's'; t[3] = 't'; t[4] = ':'; t[5] = ' ';
-
- SAF_intToStr(SAF_load(UTD_menuItem),t + 6);
-
- SAF_drawText(t,1,59,SAF_COLOR_GRAY_DARK,1);
- }
- else
- SAF_drawText(t,15,41,SAF_COLOR_YELLOW,1);
- break;
- }
- case UTD_GAME_STATE_LOST:
- {
- char t[15];
-
- // prevent storing text in RAM on Arduino with this one weird trick
- t[0] = 'L'; t[1] = 'O'; t[2] = 'S'; t[3] = 'T'; t[4] = ' '; t[5] = 'i';
- t[6] = 'n'; t[7] = '\n'; t[8] = ' '; t[9] = 'r'; t[10] = 'o';
- t[11] = 'u'; t[12] = 'n'; t[13] = 'd'; t[14] = 0;
-
- SAF_drawText(t,12,14,SAF_COLOR_BLACK,1);
- SAF_drawText(SAF_intToStr(UTD_round,t),UTD_round < 10 ? 29 : 23,31,
- SAF_COLOR_BLACK,2);
- break;
- }
- case UTD_GAME_STATE_CONFIRM_QUIT:
- case UTD_GAME_STATE_PLAYING:
- case UTD_GAME_STATE_PLAYING_MENU:
- case UTD_GAME_STATE_PLAYING_WAVE:
- {
- uint16_t *tile = UTD_mapTiles + UTD_cameraPos;
- // draw tiles:
- for (uint8_t y = 0; y < 64; y += 8)
- {
- for (uint8_t x = 0; x < 64; x += 8)
- {
- uint16_t v = *tile;
- if (v != UTD_TILE_NONE)
- {
- if (UTD_IS_TOWER(v)) // tower?
- UTD_drawTower(v,x,y);
- else // path
- {
- UTD_drawImage(x,y,UTD_IMAGE_TILES - 3 + v,
- UTD_backColor,(v == UTD_TILE_START || v == UTD_TILE_FINISH) ?
- SAF_COLOR_BLACK : SAF_COLOR_GRAY_DARK,0,0,0);
- }
- }
- tile++;
- }
- tile += UTD_MAP_WIDTH - 8;
- }
- UTD_Creep *c = UTD_creeps;
-
- // draw creeps:
- int8_t cameraPixelOffset = UTD_cameraPos * 8,
- xMin = (UTD_cameraPos - 1) * 8;
- uint8_t
- xMax = (UTD_cameraPos + 8) * 8; // this might not fit into signed byte
- for (uint8_t i = 0; i < UTD_creepCount; ++i)
- {
- int8_t x, y;
- if (UTD_pathPositionTo2D(c->pathStart,c->pathPosition,&x,&y) &&
- x >= xMin && x <= xMax)
- {
- uint8_t cType = c->typeFreeze & 0x0f;
- UTD_drawImage(
- x - cameraPixelOffset - 4,
- y - 5 +
- (((c->pathPosition >> 3) & 0x01) ==
- ((UTD_updateCounter >> 2) & 0x01)),
- UTD_IMAGE_CREEPS + cType * 2,
- #if SAF_PLATFORM_COLOR_COUNT > 2
- 0x77,
- #else
- SAF_COLOR_WHITE,
- #endif
- UTD_creepColor(cType),0,1,0);
- }
- c++;
- }
- tile = UTD_mapTiles;
- uint8_t squarePos = 0;
- // draw projectiles:
- for (uint8_t y = 0; y < 64; y += 8)
- {
- for (uint8_t x = 0; x < 128; x += 8)
- {
- uint16_t v = *tile;
- if (UTD_IS_TOWER(v)) // tower?
- {
- uint8_t attackProgress = UTD_TOWER_ATTACK_PROGRESS(v);
- if (attackProgress != 0)
- {
- UTD_Creep c = UTD_creeps[UTD_TOWER_TARGET(v)];
- int8_t cX, cY, tX, tY;
- UTD_pathPositionTo2D(c.pathStart,c.pathPosition,&cX,&cY);
- UTD_squarePositionTo2D(squarePos,&tX,&tY);
- cX -= cameraPixelOffset;
- tX -= cameraPixelOffset;
- if (!UTD_TOWER_IS_SMALL(v))
- {
- tX += 4;
- tY += 4;
- }
- uint8_t speed;
- UTD_getTowerInstanceStats(v,0,0,&speed);
- switch (UTD_TOWER_TYPE(v))
- {
- case UTD_TOWER_GUARD:
- UTD_interpolateCoords(&cX,&cY,tX,tY,attackProgress,speed);
- SAF_drawPixel(cX,cY,SAF_COLOR_BLACK);
- if (cY != tY && (cX - tX) / (cY - tY) != 0)
- cX++;
- else
- cY++;
- SAF_drawPixel(cX,cY,SAF_COLOR_BLACK);
- break;
- case UTD_TOWER_CANNON:
- UTD_interpolateCoords(&cX,&cY,tX,tY,attackProgress,speed);
- SAF_drawPixel(cX,cY,0x00);
- SAF_drawPixel(cX + 1,cY,0x64);
- SAF_drawPixel(cX,cY + 1,0x64);
- SAF_drawPixel(cX + 1,cY + 1,0x6d);
- break;
- case UTD_TOWER_ICE:
- UTD_interpolateCoords(&cX,&cY,tX,tY,attackProgress,speed);
- SAF_drawPixel(cX,cY,SAF_COLOR_BLUE);
- cX++; cY--;
- SAF_drawPixel(cX,cY,SAF_COLOR_BLUE);
- cY += 2;
- SAF_drawPixel(cX,cY,SAF_COLOR_BLUE);
- cX -= 2;
- SAF_drawPixel(cX,cY,SAF_COLOR_BLUE);
- cY -= 2;
- SAF_drawPixel(cX,cY,SAF_COLOR_BLUE);
- break;
- case UTD_TOWER_MAGIC:
- UTD_interpolateCoords(&cX,&cY,tX,tY,attackProgress,speed);
- SAF_drawCircle(cX - 2, cY - 2,2,SAF_frame(),0);
- break;
- case UTD_TOWER_SNIPER:
- {
- UTD_interpolateCoords(&cX,&cY,tX,tY,attackProgress,speed);
- UTD_interpolateCoords(&tX,&tY,cX,cY,SAF_frame() % 8,8);
- SAF_drawLine(cX,cY,tX,tY,0x62);
- break;
- }
-
- case UTD_TOWER_ELECTRO:
- if (UTD_TOWER_ATTACK_PROGRESS(v) > 2)
- SAF_drawLine(tX,tY,
- cX - 1 + SAF_frame() % 3,cY - 1 + SAF_frame() % 4,
- SAF_frame() % 2 ? 0x02 : 0x5f);
- break;
- case UTD_TOWER_WATER:
- {
- UTD_interpolateCoords(&cX,&cY,tX,tY,attackProgress,speed);
- SAF_drawCircle(cX - 2 + SAF_frame() % 2,cY - 2,3,0x01 + attackProgress / 2,1);
- break;
- }
- case UTD_TOWER_FIRE:
- {
- UTD_interpolateCoords(&cX,&cY,tX,tY,attackProgress,speed);
- uint8_t r = (9 - attackProgress);
- uint8_t shift = SAF_frame() % 2;
- for (int8_t dX = cX - r / 2 + shift;
- dX < cX - r / 2 + r + shift; ++dX)
- for (int8_t dY = cY - r / 2; dY < cY - r / 2 + r; ++dY)
- if (SAF_random() & 0x10)
- SAF_drawPixel(dX,dY,
- #if SAF_PLATFORM_COLOR_COUNT == 2
- SAF_COLOR_BLACK
- #else
- SAF_COLOR_RED
- #endif
- );
- break;
- }
- default: break;
- }
- }
- }
- squarePos++;
- tile++;
- }
- }
- uint8_t cursorX = ((UTD_cursorPos - UTD_cameraPos) % UTD_MAP_WIDTH) * 8,
- cursorY = (UTD_cursorPos / UTD_MAP_WIDTH) * 8;
- for (uint8_t i = 0; i < 7; ++i) // cursor
- {
- uint8_t c = (((SAF_frame() >> 3) + i) % 3) ?
- SAF_COLOR_BLACK : UTD_backColor;
- SAF_drawPixel(cursorX + i,cursorY,c);
- SAF_drawPixel(cursorX + 7,cursorY + i,c);
- SAF_drawPixel(cursorX + 7 - i,cursorY + 7,c);
- SAF_drawPixel(cursorX,cursorY + 7 - i,c);
- }
- // draw health bars:
- if (SAF_buttonPressed(SAF_BUTTON_B))
- {
- c = UTD_creeps;
- for (uint8_t i = 0; i < UTD_creepCount; ++i)
- {
- int8_t x, y;
-
- if (UTD_pathPositionTo2D(c->pathStart,c->pathPosition,&x,&y) &&
- x >= xMin && x <= xMax)
- {
- uint8_t cH,cS,cR;
- UTD_getCreepStats(c->typeFreeze & 0x0f,&cH,&cS,&cR);
- x = x - cameraPixelOffset - 4;
- y -= 7;
- SAF_drawRect(x,y,8,3,SAF_COLOR_BLACK,1);
- x++;
- y++;
- uint16_t hp = c->healthLives & 0x3f;
- hp = (hp * 5) / cH;
- SAF_drawLine(x,y,x + hp,y,SAF_COLOR_RED);
- }
- c++;
- }
- }
- // draw bar and menu:
- uint8_t barY = UTD_cursorPos >= UTD_MAP_WIDTH ? 1 : SAF_SCREEN_HEIGHT - 5;
- if (UTD_gameState == UTD_GAME_STATE_PLAYING_MENU ||
- UTD_gameState == UTD_GAME_STATE_CONFIRM_QUIT)
- {
- barY = 1;
- uint8_t menuY = SAF_SCREEN_HEIGHT - 9;
- uint8_t drawRadius = 0;
- uint8_t centerOffset = 4;
- char itemName[13];
- uint16_t item = UTD_getPlayMenuItem(UTD_menuItem,0,itemName);
- if (UTD_playMenuItemAvailable(item))
- {
- if (UTD_IS_TOWER(item))
- {
- if (SAF_frame() & 0x08)
- UTD_drawTower(item,cursorX,cursorY);
- UTD_getTowerStats(item,&drawRadius,0,0,0,0);
- if (!UTD_TOWER_IS_SMALL(item))
- centerOffset = 8;
- }
- else if (item == UTD_UPGRADE1 || item == UTD_UPGRADE2)
- {
- // preview range upgrade
- UTD_getTowerInstanceStats(UTD_mapTiles[UTD_cursorPos] | item,
- &drawRadius,0,0);
- }
- }
- if (drawRadius == 0)
- {
- uint16_t tile = UTD_mapTiles[UTD_cursorPos];
- if (UTD_IS_TOWER(tile))
- {
- uint8_t tD,tS;
- UTD_getTowerInstanceStats(tile,&drawRadius,&tD,&tS);
- }
- }
- if (drawRadius != 0)
- {
- SAF_drawCircle(cursorX + centerOffset,cursorY + centerOffset,drawRadius,
- SAF_COLOR_BLACK,0);
-
- SAF_drawCircle(cursorX + centerOffset,cursorY + centerOffset,
- SAF_frame() % drawRadius,SAF_COLOR_BLACK,0);
- }
- SAF_drawRect(0,menuY,SAF_SCREEN_WIDTH,
- 9,SAF_COLOR_BLACK,1);
- SAF_drawLine(0,menuY,SAF_SCREEN_WIDTH,menuY,SAF_COLOR_BLACK);
-
- uint8_t menuOffset = UTD_menuItem > 3 ? UTD_menuItem - 3 : 0;
- if (menuOffset > 5)
- menuOffset = 5;
- menuY++;
- SAF_drawRect(0,SAF_SCREEN_HEIGHT - 15,SAF_SCREEN_WIDTH,6,
- UTD_backColor,1);
- SAF_drawText(itemName,1,SAF_SCREEN_HEIGHT - 14,SAF_COLOR_BLACK,1);
- for (uint8_t i = 0; i < 8; ++i) // play menu items
- {
- uint16_t icon = 0;
- uint8_t itemIndex = i + menuOffset;
- uint16_t item = UTD_getPlayMenuItem(itemIndex,&icon,0);
- uint8_t c1 = SAF_COLOR_WHITE,
- c2 = SAF_COLOR_BLACK;
- uint8_t highlightPos = UTD_menuItem - menuOffset;
- if (highlightPos == i)
- {
- c1 = SAF_COLOR_BLUE_DARK;
- c2 = SAF_COLOR_GREEN;
- }
- uint8_t drawX = i * 8;
- UTD_drawImage(drawX,menuY,icon,c1,c2,0,0,0);
- if (!UTD_playMenuItemAvailable(item))
- for (uint8_t y = menuY; y < menuY + 8; ++y)
- for (uint8_t x = drawX; x < drawX + 8; ++x)
- if (x % 2 == y % 2)
- SAF_drawPixel(x,y,SAF_COLOR_BLACK);
- }
- }
-
- if (UTD_gameState == UTD_GAME_STATE_CONFIRM_QUIT)
- {
- SAF_drawRect(12,10,40,20,SAF_COLOR_BLACK,1);
- SAF_drawRect(13,11,38,18,SAF_COLOR_WHITE,1);
- SAF_drawText("QUIT?",19,18,SAF_COLOR_BLACK,1);
- }
- char text[8] = "$...";
- SAF_intToStr(UTD_money,text + 1);
- SAF_drawText(text,1,barY,0x0c,1);
- SAF_intToStr(UTD_round,text);
- SAF_drawText(text,37,barY,0x25,1);
- SAF_intToStr(UTD_lives,text);
- SAF_drawText(text,SAF_SCREEN_WIDTH - 10,barY,
- #if SAF_PLATFORM_COLOR_COUNT == 2
- SAF_COLOR_BLACK,
- #else
- 0x80,
- #endif
- 1);
- break;
- }
- }
- }
- void UTD_towerHits(uint16_t tower, uint8_t isSplash)
- {
- uint8_t tD = 0;
- UTD_getTowerInstanceStats(tower,0,&tD,0);
- uint8_t creepIndex = UTD_TOWER_TARGET(tower);
- uint8_t creepLives = UTD_creeps[creepIndex].healthLives & 0x3f;
-
- uint16_t towerType = UTD_TOWER_TYPE(tower);
- if (towerType == UTD_TOWER_ICE ||
- (towerType == UTD_TOWER_ELECTRO && (tower & UTD_UPGRADE2)
- && (SAF_random() % 4 == 0)))
- {
- // freeze / shock
- UTD_creeps[creepIndex].typeFreeze =
- (UTD_creeps[creepIndex].typeFreeze & 0x0f) |
- ((UTD_creeps[creepIndex].typeFreeze & 0x0f) == UTD_CREEP_WOLF ?
- 0x70 : 0xf0); // ^ wolves get frozen for shorter time
- }
- else if (towerType == UTD_TOWER_WATER && SAF_random() % 10 == 0)
- UTD_creeps[creepIndex].pathPosition -= 10; // a wave can knock creeps back
- if (isSplash)
- {
- tD /= 2; // splash deals half the damage
- if (tD >= creepLives) // splash can't kill, it would complicate the loop
- tD = creepLives - 1;
- }
- else
- {
- if (towerType == UTD_TOWER_CANNON || towerType == UTD_TOWER_FIRE)
- {
- int8_t x, y;
- UTD_Creep *c = UTD_creeps + creepIndex;
- UTD_pathPositionTo2D(c->pathStart,c->pathPosition,&x,&y);
-
- c = UTD_creeps;
-
- for (uint8_t i = 0; i < UTD_creepCount; ++i)
- {
- if (i != creepIndex && UTD_canAttack(tower,c->typeFreeze & 0x0f))
- {
- int8_t x2, y2;
- UTD_pathPositionTo2D(c->pathStart,c->pathPosition,&x2,&y2);
- if (UTD_isWithinRange(x,y,x2,y2,UTD_SPLASH_RANGE))
- UTD_towerHits(UTD_TOWER_TARGET_SET(tower,i),1);
- }
- c++;
- }
- }
- }
- if (tD >= creepLives)
- {
- uint8_t c,cH,cS,cR;
- UTD_Creep creep = UTD_creeps[creepIndex];
- c = creep.typeFreeze & 0x0f;
- UTD_getCreepStats(c,&cH,&cS,&cR);
- UTD_money += cR;
- UTD_creepEnds(creepIndex); // creep dies
- UTD_playSound(SAF_SOUND_BUMP);
- if (c == UTD_CREEP_SPIDER_BIG)
- {
- // big spider will spawn two small ones
- UTD_addCreep(UTD_CREEP_SPIDER,creep.pathStart,creep.pathPosition,0);
- UTD_addCreep(UTD_CREEP_SPIDER,creep.pathStart,creep.pathPosition - 5,0);
- }
- }
- else
- {
- creepLives -= tD;
- UTD_creeps[creepIndex].healthLives =
- (UTD_creeps[creepIndex].healthLives & 0xc0) | creepLives;
- }
- }
- void UTD_gameStep(void)
- {
- switch (UTD_gameState)
- {
- case UTD_GAME_STATE_PLAYING_MENU:
- {
- if (SAF_buttonJustPressed(SAF_BUTTON_B))
- {
- UTD_gameState = UTD_GAME_STATE_PLAYING;
- return;
- }
-
- uint16_t item = UTD_getPlayMenuItem(UTD_menuItem,0,0);
-
- if (SAF_buttonJustPressed(SAF_BUTTON_A) &&
- UTD_playMenuItemAvailable(item))
- {
- if (UTD_IS_TOWER(item))
- {
- // build tower
- UTD_mapTiles[UTD_cursorPos] = item;
-
- uint8_t tP;
- UTD_getTowerStats(item,0,0,0,&tP,0);
-
- UTD_money -= tP;
- UTD_gameState = UTD_GAME_STATE_PLAYING;
- UTD_playSound(SAF_SOUND_CLICK);
- }
- else if (item == UTD_UPGRADE1 || item == UTD_UPGRADE2)
- {
- UTD_mapTiles[UTD_cursorPos] |= item;
- UTD_money -= UTD_getUpgradeCost(UTD_mapTiles[UTD_cursorPos]);
- UTD_gameState = UTD_GAME_STATE_PLAYING;
- UTD_playSound(SAF_SOUND_CLICK);
- }
- else if (item == UTD_PLAY_MENU_SELL)
- {
- uint8_t tP = 0;
- UTD_getTowerStats(UTD_TOWER_TYPE(UTD_mapTiles[UTD_cursorPos]),0,0,0,
- &tP,0);
- UTD_mapTiles[UTD_cursorPos] = UTD_TILE_NONE;
- UTD_money += tP / 2;
- UTD_gameState = UTD_GAME_STATE_PLAYING;
- UTD_playSound(SAF_SOUND_BOOM);
- }
- else if (item == UTD_PLAY_MENU_GO)
- {
- UTD_updateCounter = 0;
- UTD_spawnCreeps();
- UTD_gameState = UTD_GAME_STATE_PLAYING_WAVE;
- UTD_playSound(SAF_SOUND_BEEP);
- }
- else if (item == UTD_PLAY_MENU_QUIT)
- {
- UTD_gameState = UTD_GAME_STATE_CONFIRM_QUIT;
- }
- }
-
- if (UTD_buttonPressedOrHeld(SAF_BUTTON_RIGHT))
- UTD_menuItem = (UTD_menuItem < UTD_PLAY_MENU_ITEMS - 1) ?
- UTD_menuItem + 1 : 0;
- else if (UTD_buttonPressedOrHeld(SAF_BUTTON_LEFT))
- UTD_menuItem = (UTD_menuItem > 0) ? UTD_menuItem - 1 :
- UTD_PLAY_MENU_ITEMS - 1;
- break;
- }
- case UTD_GAME_STATE_LOST:
- if (SAF_buttonJustPressed(SAF_BUTTON_A) ||
- SAF_buttonJustPressed(SAF_BUTTON_B))
- UTD_goToMenu();
- break;
- case UTD_GAME_STATE_CONFIRM_QUIT:
- if (SAF_buttonJustPressed(SAF_BUTTON_A))
- {
- UTD_goToMenu();
- UTD_playSound(SAF_SOUND_BUMP);
- }
- else if (SAF_buttonJustPressed(SAF_BUTTON_B))
- UTD_gameState = UTD_GAME_STATE_PLAYING_MENU;
- break;
- case UTD_GAME_STATE_MENU:
- {
- uint8_t click = 1;
- if (UTD_buttonPressedOrHeld(SAF_BUTTON_RIGHT))
- UTD_menuItem = (UTD_menuItem + 1) % (UTD_MAPS + 1);
- else if (UTD_buttonPressedOrHeld(SAF_BUTTON_LEFT))
- UTD_menuItem = UTD_menuItem == 0 ? UTD_MAPS : (UTD_menuItem - 1);
- else if (SAF_buttonJustPressed(SAF_BUTTON_A))
- {
- if (UTD_menuItem < UTD_MAPS)
- UTD_loadMap(UTD_menuItem);
- else
- UTD_sound = !UTD_sound;
- }
- else
- click = 0;
- if (click)
- UTD_playSound(SAF_SOUND_CLICK);
- break;
- }
- case UTD_GAME_STATE_PLAYING_WAVE:
- case UTD_GAME_STATE_PLAYING:
- {
- if (UTD_gameState == UTD_GAME_STATE_PLAYING_WAVE)
- {
- UTD_updateCounter++;
- UTD_Creep *c = UTD_creeps;
- // update creeps:
- for (uint8_t i = 0; i < UTD_creepCount; ++i)
- {
- uint8_t cH, cS, cR;
- UTD_getCreepStats(c->typeFreeze & 0x0f,&cH,&cS,&cR);
- uint8_t freeze = c->typeFreeze >> 4;
- if (freeze != 0)
- {
- cS += 15;
- c->typeFreeze = (c->typeFreeze & 0x0f) | ((freeze - 1) << 4);
- }
- if ((UTD_updateCounter % (cS)) == 0)
- c->pathPosition++;
- int8_t dummy1, dummy2;
- if (c->pathPosition >= 0 && !UTD_pathPositionTo2D(
- c->pathStart,c->pathPosition,&dummy1,&dummy2))
- UTD_creepFinishes(i);
- c++;
- }
- uint16_t *t = UTD_mapTiles;
- // update towers:
- if (UTD_updateCounter % 3 == 0)
- for (uint16_t i = 0; i < UTD_MAP_WIDTH * UTD_MAP_HEIGHT; ++i)
- {
- uint16_t tower = *t;
- if (!UTD_IS_TOWER(tower))
- {
- t++;
- continue;
- }
- uint8_t attackProgress = UTD_TOWER_ATTACK_PROGRESS(tower);
- if (UTD_TOWER_TYPE(tower) == UTD_TOWER_MAGIC &&
- (tower & UTD_UPGRADE2))
- UTD_applySpeedAura(i);
- if (attackProgress == 0)
- {
- // look for a new target:
- uint8_t skipFrozen = UTD_TOWER_TYPE(tower) == UTD_TOWER_ICE;
- // ^ ice tower will always look for a new target to freeze
- int8_t towerX, towerY, radius;
- UTD_squarePositionTo2D(i,&towerX,&towerY);
- if (!UTD_TOWER_IS_SMALL(tower))
- {
- towerX += 4;
- towerY += 4;
- }
- uint8_t tR, tS, tD;
- UTD_getTowerInstanceStats(tower,&tR,&tD,&tS);
- radius = tR;
- UTD_Creep *c = UTD_creeps;
- for (uint8_t j = 0; j < UTD_creepCount; ++j)
- {
- int8_t x, y;
- if (UTD_canAttack(tower,c->typeFreeze & 0x0f) &&
- UTD_pathPositionTo2D(c->pathStart,c->pathPosition,&x,&y) &&
- UTD_isWithinRange(x,y,towerX,towerY,radius) &&
- !(skipFrozen && (c->typeFreeze >> 4 != 0)))
- {
- tower = UTD_TOWER_TARGET_SET(tower,j);
- attackProgress = tS;
- break;
- }
- c++;
- }
- }
- else
- {
- if (attackProgress == 1) // hit
- UTD_towerHits(tower,0);
- attackProgress--;
- }
- tower = UTD_TOWER_ATTACK_PROGRESS_SET(tower,attackProgress);
- *t = tower;
- t++;
- }
- if (UTD_gameState != UTD_GAME_STATE_LOST && UTD_creepCount == 0)
- {
- UTD_money += UTD_WAVE_BASE_REWARD + UTD_round / 4;
- UTD_round++;
- UTD_gameState = UTD_GAME_STATE_PLAYING;
-
- if (UTD_round > SAF_load(UTD_mapIndex))
- SAF_save(UTD_mapIndex,UTD_round); // save high score
- }
- }
- else if (SAF_buttonJustPressed(SAF_BUTTON_A))
- {
- UTD_menuItem = 0;
- if (UTD_mapTiles[UTD_cursorPos] == UTD_TILE_NONE)
- {
- uint8_t cursorNotTop = UTD_cursorPos >= UTD_MAP_WIDTH,
- cursorNotLeft = UTD_cursorPos % UTD_MAP_WIDTH != 0,
- movedLeft = 0;
- if (cursorNotLeft &&
- UTD_IS_TOWER(UTD_mapTiles[UTD_cursorPos - 1]) &&
- !UTD_TOWER_IS_SMALL(UTD_mapTiles[UTD_cursorPos - 1]))
- {
- UTD_cursorPos--;
- movedLeft = 1;
- }
- else if (cursorNotTop &&
- UTD_IS_TOWER(UTD_mapTiles[UTD_cursorPos - UTD_MAP_WIDTH]) &&
- !UTD_TOWER_IS_SMALL(UTD_mapTiles[UTD_cursorPos - UTD_MAP_WIDTH]))
- {
- UTD_cursorPos -= UTD_MAP_WIDTH;
- }
- else if (cursorNotLeft && cursorNotTop &&
- UTD_IS_TOWER(UTD_mapTiles[UTD_cursorPos - UTD_MAP_WIDTH - 1]) &&
- !UTD_TOWER_IS_SMALL(UTD_mapTiles[UTD_cursorPos - UTD_MAP_WIDTH -
- 1]))
- {
- UTD_cursorPos -= UTD_MAP_WIDTH + 1;
- movedLeft = 1;
- }
- if (movedLeft && UTD_cameraPos > 0)
- UTD_cameraPos--;
- }
- UTD_gameState = UTD_GAME_STATE_PLAYING_MENU;
- return;
- }
- if (UTD_buttonPressedOrHeld(SAF_BUTTON_RIGHT))
- {
- UTD_cursorPos += (UTD_cursorPos % UTD_MAP_WIDTH) < UTD_MAP_WIDTH - 1 ?
- 1 : 0;
- if (SAF_buttonPressed(SAF_BUTTON_B) && UTD_cameraPos <
- UTD_MAP_WIDTH / 2)
- UTD_cameraPos++;
- }
- else if (UTD_buttonPressedOrHeld(SAF_BUTTON_LEFT))
- {
- UTD_cursorPos -= (UTD_cursorPos % UTD_MAP_WIDTH) > 0 ?
- 1 : 0;
- if (SAF_buttonPressed(SAF_BUTTON_B) && UTD_cameraPos > 0)
- UTD_cameraPos--;
- }
- else if (UTD_buttonPressedOrHeld(SAF_BUTTON_UP))
- UTD_cursorPos -= UTD_cursorPos >= UTD_MAP_WIDTH ? UTD_MAP_WIDTH : 0;
- else if (UTD_buttonPressedOrHeld(SAF_BUTTON_DOWN))
- UTD_cursorPos += UTD_cursorPos < (UTD_MAP_HEIGHT - 1) * UTD_MAP_WIDTH ?
- UTD_MAP_WIDTH : 0;
- uint8_t cursorX = UTD_cursorPos % UTD_MAP_WIDTH;
- if (cursorX >= UTD_cameraPos + 8)
- UTD_cameraPos++;
- if (cursorX < UTD_cameraPos)
- UTD_cameraPos--;
- break;
- }
- defualt: break;
- }
- }
- uint8_t SAF_loop(void)
- {
- UTD_gameStep();
- UTD_draw();
- return 1;
- }
|