game.h 138 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025
  1. /**
  2. @file game.h
  3. Main source file of Anarch the game that puts together all the pieces. main
  4. game logic is implemented here.
  5. physics notes (you can break this when messing around with game constants):
  6. - Lowest ceiling under which player can fit is 4 height steps.
  7. - Widest hole over which player can run without jumping is 1 square.
  8. - Widest hole over which the player can jump is 3 squares.
  9. - Highest step a player can walk onto without jumping is 2 height steps.
  10. - Highest step a player can jump onto is 3 height steps.
  11. by Miloslav Ciz (drummyfish), 2019
  12. Released under CC0 1.0 (https://creativecommons.org/publicdomain/zero/1.0/)
  13. plus a waiver of all other intellectual property. The goal of this work is
  14. be and remain completely in the public domain forever, available for any use
  15. whatsoever.
  16. */
  17. #ifndef _SFG_GAME_H
  18. #define _SFG_GAME_H
  19. #include <stdint.h> // Needed for fixed width types, can easily be replaced.
  20. /*
  21. The following keys are mandatory to be implemented on any platform in order
  22. for the game to be playable. Enums are bloat.
  23. */
  24. #define SFG_KEY_UP 0
  25. #define SFG_KEY_RIGHT 1
  26. #define SFG_KEY_DOWN 2
  27. #define SFG_KEY_LEFT 3
  28. #define SFG_KEY_A 4 ///< fire, confirm
  29. #define SFG_KEY_B 5 ///< cancel, strafe, look up/down
  30. #define SFG_KEY_C 6 ///< menu, jump, switch weapons
  31. /*
  32. The following keys are optional for a platform to implement. They just make
  33. the controls more comfortable.
  34. */
  35. #define SFG_KEY_JUMP 7
  36. #define SFG_KEY_STRAFE_LEFT 8
  37. #define SFG_KEY_STRAFE_RIGHT 9
  38. #define SFG_KEY_MAP 10
  39. #define SFG_KEY_TOGGLE_FREELOOK 11
  40. #define SFG_KEY_NEXT_WEAPON 12
  41. #define SFG_KEY_PREVIOUS_WEAPON 13
  42. #define SFG_KEY_MENU 14
  43. #define SFG_KEY_CYCLE_WEAPON 15
  44. #define SFG_KEY_COUNT 16 ///< Number of keys.
  45. /* ============================= PORTING =================================== */
  46. /* When porting, do the following:
  47. - Include this file (and possibly other optional files, like sounds.h) in
  48. your main_*.c frontend source.
  49. - Implement the following functions in your frontend source.
  50. - Call SFG_init() from your frontend initialization code.
  51. - Call SFG_mainLoopBody() from within your frontend main loop.
  52. If your platform is an AVR CPU (e.g. some Arduinos) and so has Harvard
  53. architecture, define #SFG_AVR 1 before including this file in your frontend
  54. source. */
  55. #ifndef SFG_LOG
  56. #define SFG_LOG(str) {} ///< Can be redefined to log game messages.
  57. #endif
  58. #ifndef SFG_CPU_LOAD
  59. #define SFG_CPU_LOAD(percent) {} ///< Can be redefined to check CPU load in %.
  60. #endif
  61. #ifndef SFG_GAME_STEP_COMMAND
  62. #define SFG_GAME_STEP_COMMAND {} /**< Will be called each simulation step
  63. (good for creating deterministic behavior
  64. such as demos (SFG_mainLoopBody() calls
  65. potentially multiple simulation steps). */
  66. #endif
  67. /**
  68. Returns 1 (0) if given key is pressed (not pressed). At least the mandatory
  69. keys have to be implemented, the optional keys don't have to ever return 1.
  70. See the key constant definitions to see which ones are mandatory.
  71. */
  72. int8_t SFG_keyPressed(uint8_t key);
  73. /**
  74. Optional function for mouse/joystick/analog controls, gets mouse x and y
  75. offset in pixels from the game screen center (to achieve classic FPS mouse
  76. controls the platform should center the mouse after this call). If the
  77. platform isn't using a mouse, this function can simply return [0,0] offset at
  78. each call, or even do nothing at all (leave the variables as are).
  79. */
  80. void SFG_getMouseOffset(int16_t *x, int16_t *y);
  81. /**
  82. Returns time in milliseconds sice program start.
  83. */
  84. uint32_t SFG_getTimeMs(void);
  85. /**
  86. Sleep (yield CPU) for specified amount of ms. This is used to relieve CPU
  87. usage. If your platform doesn't need this or handles it in other way, this
  88. function can do nothing.
  89. */
  90. void SFG_sleepMs(uint16_t timeMs);
  91. /**
  92. Set specified screen pixel. ColorIndex is the index of the game's palette.
  93. The function doesn't have to (and shouldn't, for the sake of performance)
  94. check whether the coordinates are within screen bounds.
  95. */
  96. static inline void SFG_setPixel(uint16_t x, uint16_t y, uint8_t colorIndex);
  97. /**
  98. Play given sound effect (SFX). This function may or may not use the sound
  99. samples provided in sounds.h, and it may or may not ignore the (logarithmic)
  100. volume parameter (0 to 255). Depending on the platform, the function can play
  101. completely different samples or even e.g. just beeps. If the platform can't
  102. play sounds, this function implementation can simply be left empty. This
  103. function doesn't have to implement safety measures, the back end takes cares
  104. of them.
  105. */
  106. void SFG_playSound(uint8_t soundIndex, uint8_t volume);
  107. #define SFG_MUSIC_TURN_OFF 0
  108. #define SFG_MUSIC_TURN_ON 1
  109. #define SFG_MUSIC_NEXT 2
  110. /**
  111. Informs the frontend how music should play, e.g. turn on/off, change track,
  112. ... See SFG_MUSIC_* constants. Playing music is optional and the frontend may
  113. ignore this. If a frontend wants to implement music, it can use the bytebeat
  114. provided in sounds.h or use its own.
  115. */
  116. void SFG_setMusic(uint8_t value);
  117. #define SFG_EVENT_VIBRATE 0 ///< the controller should vibrate (or blink etc.)
  118. #define SFG_EVENT_PLAYER_HURT 1
  119. #define SFG_EVENT_PLAYER_DIES 2
  120. #define SFG_EVENT_LEVEL_STARTS 3
  121. #define SFG_EVENT_LEVEL_WON 4
  122. #define SFG_EVENT_MONSTER_DIES 5
  123. #define SFG_EVENT_PLAYER_TAKES_ITEM 6
  124. #define SFG_EVENT_EXPLOSION 7
  125. #define SFG_EVENT_PLAYER_TELEPORTS 8
  126. #define SFG_EVENT_PLAYER_CHANGES_WEAPON 9
  127. /**
  128. This is an optional function that informs the frontend about special events
  129. which may trigger something special on the platform, such as a controller
  130. vibration, logging etc. The implementation of this function may be left empty.
  131. */
  132. void SFG_processEvent(uint8_t event, uint8_t data);
  133. #define SFG_SAVE_SIZE 12 ///< size of the save in bytes
  134. /**
  135. Optional function for permanently saving the game state. Platforms that don't
  136. have permanent storage (HDD, EEPROM etc.) may let this function simply do
  137. nothing. If implemented, the function should save the passed data into its
  138. permanent storage, e.g. a file, a cookie etc.
  139. */
  140. void SFG_save(uint8_t data[SFG_SAVE_SIZE]);
  141. /**
  142. Optional function for retrieving game data that were saved to permanent
  143. storage. Platforms without permanent storage may let this function do nothing.
  144. If implemented, the function should fill the passed array with data from
  145. permanent storage, e.g. a file, a cookie etc.
  146. If this function is called before SFG_save was ever called and no data is
  147. present in permanent memory, this function should do nothing (leave the data
  148. array as is).
  149. This function should return 1 if saving/loading is possible or 0 if not (this
  150. will be used by the game to detect saving/loading capability).
  151. */
  152. uint8_t SFG_load(uint8_t data[SFG_SAVE_SIZE]);
  153. /* ========================================================================= */
  154. /**
  155. Main game loop body, call this inside your platform's specific main loop.
  156. Returns 1 if the game continues or 0 if the game was exited and program should
  157. halt. This functions handles reaching the target FPS and sleeping for
  158. relieving CPU, so don't do this.
  159. */
  160. uint8_t SFG_mainLoopBody(void);
  161. /**
  162. Initializes the game, call this in the platform's initialization code.
  163. */
  164. void SFG_init(void);
  165. #include "settings.h"
  166. #if SFG_AVR
  167. #include <avr/pgmspace.h>
  168. #define SFG_PROGRAM_MEMORY const PROGMEM
  169. #define SFG_PROGRAM_MEMORY_U8(addr) pgm_read_byte(addr)
  170. #else
  171. #define SFG_PROGRAM_MEMORY static const
  172. #define SFG_PROGRAM_MEMORY_U8(addr) ((uint8_t) (*(addr)))
  173. #endif
  174. #include "images.h" // don't change the order of these includes
  175. #include "levels.h"
  176. #include "texts.h"
  177. #include "palette.h"
  178. #if SFG_TEXTURE_DISTANCE == 0
  179. #define RCL_COMPUTE_WALL_TEXCOORDS 0
  180. #endif
  181. #define RCL_PIXEL_FUNCTION SFG_pixelFunc
  182. #define RCL_TEXTURE_VERTICAL_STRETCH 0
  183. #define RCL_CAMERA_COLL_HEIGHT_BELOW 800
  184. #define RCL_CAMERA_COLL_HEIGHT_ABOVE 200
  185. #define RCL_HORIZONTAL_FOV SFG_FOV_HORIZONTAL
  186. #define RCL_VERTICAL_FOV SFG_FOV_VERTICAL
  187. #include "raycastlib.h"
  188. #include "constants.h"
  189. typedef struct
  190. {
  191. uint8_t coords[2];
  192. uint8_t state; /**< door state in format:
  193. MSB ccbaaaaa LSB
  194. aaaaa: current door height (how much they're open)
  195. b: whether currently going up (0) or down (1)
  196. cc: by which card (key) the door is unlocked, 00
  197. means no card (unlocked), 1 means card 0 etc. */
  198. } SFG_DoorRecord;
  199. #define SFG_SPRITE_SIZE(size0to3) \
  200. (((size0to3 + 3) * SFG_BASE_SPRITE_SIZE) / 4)
  201. #define SFG_SPRITE_SIZE_TO_HEIGHT_ABOVE_GROUND(size0to3) \
  202. (SFG_SPRITE_SIZE(size0to3) / 2)
  203. #define SFG_SPRITE_SIZE_PIXELS(size0to3) \
  204. ((SFG_SPRITE_SIZE(size0to3) * SFG_SPRITE_MAX_SIZE) / RCL_UNITS_PER_SQUARE)
  205. /**
  206. Holds information about one instance of a level item (a type of level element,
  207. e.g. pickable items, decorations etc.). The format is following:
  208. MSB abbbbbbb LSB
  209. a: active flag, 1 means the item is nearby to player and is active
  210. bbbbbbb: index to elements array of the current level, pointing to element
  211. representing this item
  212. */
  213. typedef uint8_t SFG_ItemRecord;
  214. #define SFG_ITEM_RECORD_ACTIVE_MASK 0x80
  215. #define SFG_ITEM_RECORD_LEVEL_ELEMENT(itemRecord) \
  216. (SFG_currentLevel.levelPointer->elements[itemRecord & \
  217. ~SFG_ITEM_RECORD_ACTIVE_MASK])
  218. typedef struct
  219. {
  220. uint8_t stateType; /**< Holds state (lower 4 bits) and type of monster (upper
  221. 4 bits). */
  222. uint8_t coords[2]; /**< monster position, in 1/4s of a square */
  223. uint8_t health;
  224. } SFG_MonsterRecord;
  225. #define SFG_MR_STATE(mr) ((mr).stateType & SFG_MONSTER_MASK_STATE)
  226. #define SFG_MR_TYPE(mr) \
  227. (SFG_MONSTER_INDEX_TO_TYPE(((mr).stateType & SFG_MONSTER_MASK_TYPE) >> 4))
  228. #define SFG_MONSTER_COORD_TO_RCL_UNITS(c) ((RCL_UNITS_PER_SQUARE / 8) + c * 256)
  229. #define SFG_MONSTER_COORD_TO_SQUARES(c) (c / 4)
  230. #define SFG_ELEMENT_COORD_TO_RCL_UNITS(c) \
  231. (c * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2)
  232. #define SFG_MONSTER_MASK_STATE 0x0f
  233. #define SFG_MONSTER_MASK_TYPE 0xf0
  234. #define SFG_MONSTER_STATE_INACTIVE 0 ///< Not nearby, not actively updated.
  235. #define SFG_MONSTER_STATE_IDLE 1
  236. #define SFG_MONSTER_STATE_ATTACKING 2
  237. #define SFG_MONSTER_STATE_HURTING 3
  238. #define SFG_MONSTER_STATE_DYING 4
  239. #define SFG_MONSTER_STATE_GOING_N 5
  240. #define SFG_MONSTER_STATE_GOING_NE 6
  241. #define SFG_MONSTER_STATE_GOING_E 7
  242. #define SFG_MONSTER_STATE_GOING_SE 8
  243. #define SFG_MONSTER_STATE_GOING_S 9
  244. #define SFG_MONSTER_STATE_GOING_SW 10
  245. #define SFG_MONSTER_STATE_GOING_W 11
  246. #define SFG_MONSTER_STATE_GOING_NW 12
  247. #define SFG_MONSTER_STATE_DEAD 13
  248. typedef struct
  249. {
  250. uint8_t type;
  251. uint8_t doubleFramesToLive; /**< This number times two (because 255 could be
  252. too little at high FPS) says after how many
  253. frames the projectile is destroyed. */
  254. uint16_t position[3]; /**< Current position, stored as u16 to save space, as
  255. that is exactly enough to store position on 64x64
  256. map. */
  257. int16_t direction[3]; /**< Added to position each game step. */
  258. } SFG_ProjectileRecord;
  259. #define SFG_GAME_STATE_INIT 0 ///< first state, waiting for key releases
  260. #define SFG_GAME_STATE_PLAYING 1
  261. #define SFG_GAME_STATE_WIN 2
  262. #define SFG_GAME_STATE_LOSE 3
  263. #define SFG_GAME_STATE_INTRO 4
  264. #define SFG_GAME_STATE_OUTRO 5
  265. #define SFG_GAME_STATE_MAP 6
  266. #define SFG_GAME_STATE_LEVEL_START 7
  267. #define SFG_GAME_STATE_MENU 8
  268. #define SFG_MENU_ITEM_CONTINUE 0
  269. #define SFG_MENU_ITEM_MAP 1
  270. #define SFG_MENU_ITEM_PLAY 2
  271. #define SFG_MENU_ITEM_LOAD 3
  272. #define SFG_MENU_ITEM_SOUND 4
  273. #define SFG_MENU_ITEM_SHEAR 5
  274. #define SFG_MENU_ITEM_EXIT 6
  275. #define SFG_MENU_ITEM_NONE 255
  276. /*
  277. GLOBAL VARIABLES
  278. ===============================================================================
  279. */
  280. /**
  281. Groups global variables related to the game as such in a single struct. There
  282. are still other global structs for player, level etc.
  283. */
  284. struct
  285. {
  286. uint8_t state; ///< Current game state.
  287. uint32_t stateTime; ///< Time in ms from last state change.
  288. uint8_t currentRandom; ///< for RNG
  289. uint8_t spriteAnimationFrame;
  290. uint8_t soundsPlayedThisFrame; /**< Each bit says whether given sound was
  291. played this frame, prevents playing too many
  292. sounds at once. */
  293. RCL_RayConstraints rayConstraints; ///< Ray constraints for rendering.
  294. RCL_RayConstraints visibilityRayConstraints; ///< Constraints for visibility.
  295. uint8_t keyStates[SFG_KEY_COUNT]; /**< Pressed states of keys, each value
  296. stores the number of frames for which the
  297. key has been held. */
  298. uint8_t zBuffer[SFG_Z_BUFFER_SIZE];
  299. uint8_t textureAverageColors[SFG_WALL_TEXTURE_COUNT]; /**< Contains average
  300. color for each wall texture. */
  301. int8_t backgroundScaleMap[SFG_GAME_RESOLUTION_Y];
  302. uint16_t backgroundScroll;
  303. uint8_t spriteSamplingPoints[SFG_MAX_SPRITE_SIZE]; /**< Helper for
  304. precomputing sprite
  305. sampling positions for
  306. drawing. */
  307. uint32_t frameTime; ///< time (in ms) of the current frame start
  308. uint32_t frame; ///< frame number
  309. uint8_t selectedMenuItem;
  310. uint8_t selectedLevel; ///< level to play selected in the main menu
  311. uint8_t antiSpam; ///< Prevents log message spamming.
  312. uint8_t settings; /**< dynamic game settings (can be changed at runtime),
  313. bit meaning:
  314. MSB -------- LSB
  315. ||||
  316. |||\_ sound (SFX)
  317. ||\__ music
  318. |\___ shearing
  319. \____ freelook (shearing not sliding back) */
  320. uint8_t blink; ///< Says whether blinkg is currently on or off.
  321. uint8_t saved; /**< Helper variable to know if game was saved. Can be
  322. 0 (not saved), 1 (just saved) or 255 (can't save).*/
  323. uint8_t cheatState; /**< Highest bit say whether cheat is enabled, other bits
  324. represent the state of typing the cheat code. */
  325. uint8_t save[SFG_SAVE_SIZE]; /**< Stores the game save state that's kept in
  326. the persistent memory.
  327. The save format is binary and platform independent.
  328. The save contains game settings, game progress and a
  329. saved position. The format is as follows:
  330. 0 4b (less signif.) highest level that has been reached
  331. 0 4b (more signif.) level number of the saved position (0: no save)
  332. 1 8b game settings (SFG_game.settings)
  333. 2 8b health at saved position
  334. 3 8b bullet ammo at saved position
  335. 4 8b rocket ammo at saved position
  336. 5 8b plasma ammo at saved position
  337. 6 32b little endian total play time, in 10ths of sec
  338. 10 16b little endian total enemies killed from start */
  339. uint8_t continues; ///< Whether the game continues or was exited.
  340. } SFG_game;
  341. #define SFG_SAVE_TOTAL_TIME (SFG_game.save[6] + SFG_game.save[7] * 256 + \
  342. SFG_game.save[8] * 65536 + SFG_game.save[9] * 4294967296)
  343. /**
  344. Stores player state.
  345. */
  346. struct
  347. {
  348. RCL_Camera camera;
  349. int8_t squarePosition[2];
  350. RCL_Vector2D direction;
  351. RCL_Unit verticalSpeed;
  352. RCL_Unit previousVerticalSpeed; /**< Vertical speed in previous frame, needed
  353. for determining whether player is in the
  354. air. */
  355. uint16_t headBobFrame;
  356. uint8_t weapon; ///< currently selected weapon
  357. uint8_t health;
  358. uint32_t weaponCooldownFrames; ///< frames left for weapon cooldown
  359. uint32_t lastHurtFrame;
  360. uint32_t lastItemTakenFrame;
  361. uint8_t ammo[SFG_AMMO_TOTAL];
  362. uint8_t cards; /**< Lowest 3 bits say which access cards
  363. have been taken, the next 3 bits say
  364. which cards should be blinking in the HUD,
  365. the last 2 bits are a blink reset counter. */
  366. uint8_t justTeleported;
  367. int8_t previousWeaponDirection; ///< Direction (+/0/-) of previous weapon.
  368. } SFG_player;
  369. /**
  370. Stores the current level and helper precomputed values for better performance.
  371. */
  372. struct
  373. {
  374. const SFG_Level *levelPointer;
  375. uint8_t levelNumber;
  376. const uint8_t* textures[7]; ///< textures the level is using
  377. uint32_t timeStart;
  378. uint32_t frameStart;
  379. uint32_t completionTime10sOfS; ///< completion time in 10ths of second
  380. uint8_t floorColor;
  381. uint8_t ceilingColor;
  382. SFG_DoorRecord doorRecords[SFG_MAX_DOORS];
  383. uint8_t doorRecordCount;
  384. uint8_t checkedDoorIndex; ///< Says which door are currently being checked.
  385. SFG_ItemRecord itemRecords[SFG_MAX_ITEMS]; ///< Holds level items.
  386. uint8_t itemRecordCount;
  387. uint8_t checkedItemIndex; ///< Same as checkedDoorIndex, but for items.
  388. SFG_MonsterRecord monsterRecords[SFG_MAX_MONSTERS];
  389. uint8_t monsterRecordCount;
  390. uint8_t checkedMonsterIndex;
  391. SFG_ProjectileRecord projectileRecords[SFG_MAX_PROJECTILES];
  392. uint8_t projectileRecordCount;
  393. uint8_t bossCount;
  394. uint8_t monstersDead;
  395. uint8_t backgroundImage;
  396. uint8_t teleporterCount;
  397. uint16_t mapRevealMask; /**< Bits say which parts of the map have been
  398. revealed. */
  399. uint8_t itemCollisionMap[(SFG_MAP_SIZE * SFG_MAP_SIZE) / 8];
  400. /**< Bit array, for each map square says whether there
  401. is a colliding item or not. */
  402. } SFG_currentLevel;
  403. #if SFG_AVR
  404. /**
  405. Copy of the current level that is stored in RAM. This is only done on Arduino
  406. because accessing it in program memory (PROGMEM) directly would be a pain.
  407. Because of this Arduino needs more RAM.
  408. */
  409. SFG_Level SFG_ramLevel;
  410. #endif
  411. /**
  412. Helper function for accessing the itemCollisionMap bits.
  413. */
  414. void SFG_getItemCollisionMapIndex(
  415. uint8_t x, uint8_t y, uint16_t *byte, uint8_t *bit)
  416. {
  417. uint16_t index = y * SFG_MAP_SIZE + x;
  418. *byte = index / 8;
  419. *bit = index % 8;
  420. }
  421. void SFG_setItemCollisionMapBit(uint8_t x, uint8_t y, uint8_t value)
  422. {
  423. uint16_t byte;
  424. uint8_t bit;
  425. SFG_getItemCollisionMapIndex(x,y,&byte,&bit);
  426. SFG_currentLevel.itemCollisionMap[byte] &= ~(0x01 << bit);
  427. SFG_currentLevel.itemCollisionMap[byte] |= (value & 0x01) << bit;
  428. }
  429. uint8_t SFG_getItemCollisionMapBit(uint8_t x, uint8_t y)
  430. {
  431. uint16_t byte;
  432. uint8_t bit;
  433. SFG_getItemCollisionMapIndex(x,y,&byte,&bit);
  434. return (SFG_currentLevel.itemCollisionMap[byte] >> bit) & 0x01;
  435. }
  436. #if SFG_DITHERED_SHADOW
  437. static const uint8_t SFG_ditheringPatterns[] =
  438. {
  439. 0,0,0,0,
  440. 0,0,0,0,
  441. 0,0,0,0,
  442. 0,1,0,0,
  443. 0,0,0,0,
  444. 0,1,0,1,
  445. 1,0,1,0,
  446. 0,1,0,0,
  447. 1,0,1,0,
  448. 0,1,0,1,
  449. 1,0,1,0,
  450. 0,1,1,1,
  451. 1,1,1,1,
  452. 0,1,0,1,
  453. 1,1,1,1,
  454. 0,1,1,1,
  455. 1,1,1,1,
  456. 1,1,1,1
  457. };
  458. #endif
  459. /*
  460. FUNCTIONS
  461. ===============================================================================
  462. */
  463. /**
  464. Returns a pseudorandom byte. This is a very simple congruent generator, its
  465. parameters have been chosen so that each number (0-255) is included in the
  466. output exactly once!
  467. */
  468. uint8_t SFG_random(void)
  469. {
  470. SFG_game.currentRandom *= 13;
  471. SFG_game.currentRandom += 7;
  472. return SFG_game.currentRandom;
  473. }
  474. void SFG_playGameSound(uint8_t soundIndex, uint8_t volume)
  475. {
  476. if (!(SFG_game.settings & 0x01))
  477. return;
  478. uint8_t mask = 0x01 << soundIndex;
  479. if (!(SFG_game.soundsPlayedThisFrame & mask))
  480. {
  481. SFG_playSound(soundIndex,volume);
  482. SFG_game.soundsPlayedThisFrame |= mask;
  483. }
  484. }
  485. /**
  486. Returns a damage value for specific attack type (SFG_WEAPON_FIRE_TYPE_...),
  487. with added randomness (so the values will differ). For explosion pass
  488. SFG_WEAPON_FIRE_TYPE_FIREBALL.
  489. */
  490. uint8_t SFG_getDamageValue(uint8_t attackType)
  491. {
  492. if (attackType >= SFG_WEAPON_FIRE_TYPES_TOTAL)
  493. return 0;
  494. int32_t value = SFG_attackDamageTable[attackType]; // has to be signed
  495. int32_t maxAdd = (value * SFG_DAMAGE_RANDOMNESS) / 256;
  496. value = value + (maxAdd / 2) - (SFG_random() * maxAdd / 256);
  497. if (value < 0)
  498. value = 0;
  499. return value;
  500. }
  501. /**
  502. Saves game data to persistent storage.
  503. */
  504. void SFG_gameSave(void)
  505. {
  506. if (SFG_game.saved == SFG_CANT_SAVE)
  507. return;
  508. SFG_LOG("saving game data");
  509. SFG_save(SFG_game.save);
  510. }
  511. /**
  512. Loads game data from persistent storage.
  513. */
  514. void SFG_gameLoad(void)
  515. {
  516. if (SFG_game.saved == SFG_CANT_SAVE)
  517. return;
  518. SFG_LOG("loading game data");
  519. uint8_t result = SFG_load(SFG_game.save);
  520. if (result == 0)
  521. SFG_game.saved = SFG_CANT_SAVE;
  522. }
  523. /**
  524. Returns ammo type for given weapon.
  525. */
  526. uint8_t SFG_weaponAmmo(uint8_t weapon)
  527. {
  528. if (weapon == SFG_WEAPON_KNIFE)
  529. return SFG_AMMO_NONE;
  530. if (weapon == SFG_WEAPON_MACHINE_GUN ||
  531. weapon == SFG_WEAPON_SHOTGUN)
  532. return SFG_AMMO_BULLETS;
  533. else if (weapon == SFG_WEAPON_ROCKET_LAUNCHER)
  534. return SFG_AMMO_ROCKETS;
  535. else
  536. return SFG_AMMO_PLASMA;
  537. }
  538. RCL_Unit SFG_taxicabDistance(
  539. RCL_Unit x0, RCL_Unit y0, RCL_Unit z0, RCL_Unit x1, RCL_Unit y1, RCL_Unit z1)
  540. {
  541. return (RCL_abs(x0 - x1) + RCL_abs(y0 - y1) + RCL_abs(z0 - z1));
  542. }
  543. uint8_t SFG_isInActiveDistanceFromPlayer(RCL_Unit x, RCL_Unit y, RCL_Unit z)
  544. {
  545. return SFG_taxicabDistance(
  546. x,y,z,SFG_player.camera.position.x,SFG_player.camera.position.y,
  547. SFG_player.camera.height) <= SFG_LEVEL_ELEMENT_ACTIVE_DISTANCE;
  548. }
  549. /**
  550. Function called when a level end to compute the stats etc.
  551. */
  552. void SFG_levelEnds(void)
  553. {
  554. SFG_currentLevel.completionTime10sOfS = (SFG_MS_PER_FRAME *
  555. (SFG_game.frame - SFG_currentLevel.frameStart)) / 100;
  556. if (
  557. (SFG_player.health != 0) &&
  558. (SFG_currentLevel.levelNumber >= (SFG_game.save[0] & 0x0f)) &&
  559. ((SFG_currentLevel.levelNumber + 1) < SFG_NUMBER_OF_LEVELS))
  560. {
  561. SFG_game.save[0] = // save progress
  562. (SFG_game.save[0] & 0xf0) | (SFG_currentLevel.levelNumber + 1);
  563. SFG_gameSave();
  564. }
  565. SFG_currentLevel.monstersDead = 0;
  566. for (uint16_t i = 0; i < SFG_currentLevel.monsterRecordCount; ++i)
  567. if (SFG_currentLevel.monsterRecords[i].health == 0)
  568. SFG_currentLevel.monstersDead++;
  569. uint32_t totalTime = SFG_SAVE_TOTAL_TIME;
  570. if ((SFG_currentLevel.levelNumber == 0) || (totalTime != 0))
  571. {
  572. SFG_LOG("Updating save totals.");
  573. totalTime += SFG_currentLevel.completionTime10sOfS;
  574. for (uint8_t i = 0; i < 4; ++i)
  575. {
  576. SFG_game.save[6 + i] = totalTime % 256;
  577. totalTime /= 256;
  578. }
  579. SFG_game.save[10] += SFG_currentLevel.monstersDead % 256;
  580. SFG_game.save[11] += SFG_currentLevel.monstersDead / 256;
  581. }
  582. SFG_game.save[2] = SFG_player.health;
  583. SFG_game.save[3] = SFG_player.ammo[0];
  584. SFG_game.save[4] = SFG_player.ammo[1];
  585. SFG_game.save[5] = SFG_player.ammo[2];
  586. }
  587. static inline uint8_t SFG_RCLUnitToZBuffer(RCL_Unit x)
  588. {
  589. x /= (RCL_UNITS_PER_SQUARE / 8);
  590. uint8_t okay = x < 256;
  591. return okay * (x + 1) - 1;
  592. }
  593. const uint8_t *SFG_getMonsterSprite(
  594. uint8_t monsterType, uint8_t state, uint8_t frame)
  595. {
  596. uint8_t index =
  597. state == SFG_MONSTER_STATE_DEAD ? 18 : 17;
  598. // ^ makes the compiled binary smaller compared to returning pointers directly
  599. if ((state != SFG_MONSTER_STATE_DYING) && (state != SFG_MONSTER_STATE_DEAD))
  600. switch (monsterType)
  601. {
  602. case SFG_LEVEL_ELEMENT_MONSTER_SPIDER:
  603. switch (state)
  604. {
  605. case SFG_MONSTER_STATE_ATTACKING: index = 1; break;
  606. case SFG_MONSTER_STATE_IDLE: index = 0; break;
  607. default: index = frame ? 0 : 2; break;
  608. }
  609. break;
  610. case SFG_LEVEL_ELEMENT_MONSTER_WARRIOR:
  611. index = state != SFG_MONSTER_STATE_ATTACKING ? 6 : 7;
  612. break;
  613. case SFG_LEVEL_ELEMENT_MONSTER_DESTROYER:
  614. switch (state)
  615. {
  616. case SFG_MONSTER_STATE_ATTACKING: index = 4; break;
  617. case SFG_MONSTER_STATE_IDLE: index = 3; break;
  618. default: index = frame ? 3 : 5; break;
  619. }
  620. break;
  621. case SFG_LEVEL_ELEMENT_MONSTER_PLASMABOT:
  622. index = state != SFG_MONSTER_STATE_ATTACKING ? 8 : 9;
  623. break;
  624. case SFG_LEVEL_ELEMENT_MONSTER_ENDER:
  625. switch (state)
  626. {
  627. case SFG_MONSTER_STATE_ATTACKING: index = 12; break;
  628. case SFG_MONSTER_STATE_IDLE: index = 10; break;
  629. default: index = frame ? 10 : 11; break;
  630. }
  631. break;
  632. case SFG_LEVEL_ELEMENT_MONSTER_TURRET:
  633. switch (state)
  634. {
  635. case SFG_MONSTER_STATE_ATTACKING: index = 15; break;
  636. case SFG_MONSTER_STATE_IDLE: index = 13; break;
  637. default: index = frame ? 13 : 14; break;
  638. }
  639. break;
  640. case SFG_LEVEL_ELEMENT_MONSTER_EXPLODER:
  641. default:
  642. index = 16;
  643. break;
  644. }
  645. return SFG_monsterSprites + index * SFG_TEXTURE_STORE_SIZE;
  646. }
  647. /**
  648. Says whether given key is currently pressed (down). This should be preferred
  649. to SFG_keyPressed().
  650. */
  651. uint8_t SFG_keyIsDown(uint8_t key)
  652. {
  653. return SFG_game.keyStates[key] != 0;
  654. }
  655. /**
  656. Says whether given key has been pressed in the current frame.
  657. */
  658. uint8_t SFG_keyJustPressed(uint8_t key)
  659. {
  660. return (SFG_game.keyStates[key]) == 1;
  661. }
  662. /**
  663. Says whether a key is being repeated after being held for certain time.
  664. */
  665. uint8_t SFG_keyRepeated(uint8_t key)
  666. {
  667. return
  668. ((SFG_game.keyStates[key] >= SFG_KEY_REPEAT_DELAY_FRAMES) ||
  669. (SFG_game.keyStates[key] == 255)) &&
  670. (SFG_game.frame % SFG_KEY_REPEAT_PERIOD_FRAMES == 0);
  671. }
  672. uint16_t SFG_keyRegisters(uint8_t key)
  673. {
  674. return SFG_keyJustPressed(key) || SFG_keyRepeated(key);
  675. }
  676. #if SFG_RESOLUTION_SCALEDOWN == 1
  677. #define SFG_setGamePixel SFG_setPixel
  678. #else
  679. /**
  680. Sets the game pixel (a pixel that can potentially be bigger than the screen
  681. pixel).
  682. */
  683. static inline void SFG_setGamePixel(uint16_t x, uint16_t y, uint8_t colorIndex)
  684. {
  685. uint16_t screenY = y * SFG_RESOLUTION_SCALEDOWN;
  686. uint16_t screenX = x * SFG_RESOLUTION_SCALEDOWN;
  687. for (uint16_t j = screenY; j < screenY + SFG_RESOLUTION_SCALEDOWN; ++j)
  688. for (uint16_t i = screenX; i < screenX + SFG_RESOLUTION_SCALEDOWN; ++i)
  689. SFG_setPixel(i,j,colorIndex);
  690. }
  691. #endif
  692. void SFG_recomputePLayerDirection(void)
  693. {
  694. SFG_player.camera.direction =
  695. RCL_wrap(SFG_player.camera.direction,RCL_UNITS_PER_SQUARE);
  696. SFG_player.direction = RCL_angleToDirection(SFG_player.camera.direction);
  697. SFG_player.direction.x =
  698. (SFG_player.direction.x * SFG_PLAYER_MOVE_UNITS_PER_FRAME)
  699. / RCL_UNITS_PER_SQUARE;
  700. SFG_player.direction.y =
  701. (SFG_player.direction.y * SFG_PLAYER_MOVE_UNITS_PER_FRAME)
  702. / RCL_UNITS_PER_SQUARE;
  703. SFG_game.backgroundScroll =
  704. ((SFG_player.camera.direction * 8) * SFG_GAME_RESOLUTION_Y)
  705. / RCL_UNITS_PER_SQUARE;
  706. }
  707. #if SFG_BACKGROUND_BLUR != 0
  708. uint8_t SFG_backgroundBlurIndex = 0;
  709. static const int8_t SFG_backgroundBlurOffsets[8] =
  710. {
  711. 0 * SFG_BACKGROUND_BLUR,
  712. 16 * SFG_BACKGROUND_BLUR,
  713. 7 * SFG_BACKGROUND_BLUR,
  714. 17 * SFG_BACKGROUND_BLUR,
  715. 1 * SFG_BACKGROUND_BLUR,
  716. 4 * SFG_BACKGROUND_BLUR,
  717. 15 * SFG_BACKGROUND_BLUR,
  718. 9 * SFG_BACKGROUND_BLUR,
  719. };
  720. #endif
  721. static inline uint8_t SFG_fogValueDiminish(RCL_Unit depth)
  722. {
  723. return depth / SFG_FOG_DIMINISH_STEP;
  724. }
  725. static inline uint8_t
  726. SFG_getTexelFull(uint8_t textureIndex,RCL_Unit u, RCL_Unit v)
  727. {
  728. return
  729. SFG_getTexel(
  730. textureIndex != 255 ?
  731. SFG_currentLevel.textures[textureIndex] :
  732. (SFG_wallTextures + SFG_currentLevel.levelPointer->doorTextureIndex
  733. * SFG_TEXTURE_STORE_SIZE),
  734. u / (RCL_UNITS_PER_SQUARE / SFG_TEXTURE_SIZE),
  735. v / (RCL_UNITS_PER_SQUARE / SFG_TEXTURE_SIZE));
  736. }
  737. static inline uint8_t SFG_getTexelAverage(uint8_t textureIndex)
  738. {
  739. return
  740. textureIndex != 255 ?
  741. SFG_game.textureAverageColors[
  742. SFG_currentLevel.levelPointer->textureIndices[textureIndex]]
  743. :
  744. (
  745. SFG_game.textureAverageColors[
  746. SFG_currentLevel.levelPointer->doorTextureIndex]
  747. + 1 // to distinguish from normal walls
  748. );
  749. }
  750. void SFG_pixelFunc(RCL_PixelInfo *pixel)
  751. {
  752. uint8_t color;
  753. uint8_t shadow = 0;
  754. if (pixel->isHorizon && pixel->depth > RCL_UNITS_PER_SQUARE * 16)
  755. {
  756. color = SFG_TRANSPARENT_COLOR;
  757. }
  758. else if (pixel->isWall)
  759. {
  760. uint8_t textureIndex =
  761. pixel->isFloor ?
  762. (
  763. ((pixel->hit.type & SFG_TILE_PROPERTY_MASK) != SFG_TILE_PROPERTY_DOOR) ?
  764. (pixel->hit.type & 0x7)
  765. :
  766. (
  767. (pixel->texCoords.y > RCL_UNITS_PER_SQUARE) ?
  768. (pixel->hit.type & 0x7) : 255
  769. )
  770. ):
  771. ((pixel->hit.type & 0x38) >> 3);
  772. #if SFG_TEXTURE_DISTANCE != 0
  773. RCL_Unit textureV = pixel->texCoords.y;
  774. if ((pixel->hit.type & SFG_TILE_PROPERTY_MASK) ==
  775. SFG_TILE_PROPERTY_SQUEEZER)
  776. textureV += pixel->wallHeight;
  777. #endif
  778. color =
  779. textureIndex != SFG_TILE_TEXTURE_TRANSPARENT ?
  780. (
  781. #if SFG_TEXTURE_DISTANCE >= 65535
  782. SFG_getTexelFull(textureIndex,pixel->texCoords.x,textureV)
  783. #elif SFG_TEXTURE_DISTANCE == 0
  784. SFG_getTexelAverage(textureIndex)
  785. #else
  786. pixel->depth <= SFG_TEXTURE_DISTANCE ?
  787. SFG_getTexelFull(textureIndex,pixel->texCoords.x,textureV) :
  788. SFG_getTexelAverage(textureIndex)
  789. #endif
  790. )
  791. :
  792. SFG_TRANSPARENT_COLOR;
  793. shadow = pixel->hit.direction >> 1;
  794. }
  795. else // floor/ceiling
  796. {
  797. color = pixel->isFloor ?
  798. (
  799. #if SFG_DIFFERENT_FLOOR_CEILING_COLORS
  800. 2 + (pixel->height / SFG_WALL_HEIGHT_STEP) % 4
  801. #else
  802. SFG_currentLevel.floorColor
  803. #endif
  804. ) :
  805. (pixel->height < SFG_CEILING_MAX_HEIGHT ?
  806. (
  807. #if SFG_DIFFERENT_FLOOR_CEILING_COLORS
  808. 18 + (pixel->height / SFG_WALL_HEIGHT_STEP) % 4
  809. #else
  810. SFG_currentLevel.ceilingColor
  811. #endif
  812. )
  813. : SFG_TRANSPARENT_COLOR);
  814. }
  815. if (color != SFG_TRANSPARENT_COLOR)
  816. {
  817. #if SFG_DITHERED_SHADOW
  818. uint8_t fogShadow = (pixel->depth * 8) / SFG_FOG_DIMINISH_STEP;
  819. uint8_t fogShadowPart = fogShadow & 0x07;
  820. fogShadow /= 8;
  821. uint8_t xMod4 = pixel->position.x & 0x03;
  822. uint8_t yMod2 = pixel->position.y & 0x01;
  823. shadow +=
  824. fogShadow + SFG_ditheringPatterns[fogShadowPart * 8 + yMod2 * 4 + xMod4];
  825. #else
  826. shadow += SFG_fogValueDiminish(pixel->depth);
  827. #endif
  828. #if SFG_ENABLE_FOG
  829. color = palette_minusValue(color,shadow);
  830. #endif
  831. }
  832. else
  833. {
  834. #if SFG_DRAW_LEVEL_BACKGROUND
  835. color = SFG_getTexel(SFG_backgroundImages +
  836. SFG_currentLevel.backgroundImage * SFG_TEXTURE_STORE_SIZE,
  837. SFG_game.backgroundScaleMap[((pixel->position.x
  838. #if SFG_BACKGROUND_BLUR != 0
  839. + SFG_backgroundBlurOffsets[SFG_backgroundBlurIndex]
  840. #endif
  841. ) * SFG_RAYCASTING_SUBSAMPLE + SFG_game.backgroundScroll) % SFG_GAME_RESOLUTION_Y],
  842. (SFG_game.backgroundScaleMap[(pixel->position.y // ^ TODO: get rid of mod?
  843. #if SFG_BACKGROUND_BLUR != 0
  844. + SFG_backgroundBlurOffsets[SFG_backgroundBlurIndex + 1]
  845. #endif
  846. ) % SFG_GAME_RESOLUTION_Y])
  847. );
  848. #if SFG_BACKGROUND_BLUR != 0
  849. SFG_backgroundBlurIndex = (SFG_backgroundBlurIndex + 1) % 8;
  850. #endif
  851. #else
  852. color = 1;
  853. #endif
  854. }
  855. #if SFG_BRIGHTNESS > 0
  856. color = palette_plusValue(color,SFG_BRIGHTNESS);
  857. #elif SFG_BRIGHTNESS < 0
  858. color = palette_minusValue(color,-1 * SFG_BRIGHTNESS);
  859. #endif
  860. #if SFG_RAYCASTING_SUBSAMPLE == 1
  861. // the other version will probably get optimized to this, but just in case
  862. SFG_setGamePixel(pixel->position.x,pixel->position.y,color);
  863. #else
  864. RCL_Unit screenX = pixel->position.x * SFG_RAYCASTING_SUBSAMPLE;
  865. for (int_fast8_t i = 0; i < SFG_RAYCASTING_SUBSAMPLE; ++i)
  866. {
  867. SFG_setGamePixel(screenX,pixel->position.y,color);
  868. screenX++;
  869. }
  870. #endif
  871. }
  872. /**
  873. Draws image on screen, with transparency. This is faster than sprite drawing.
  874. For performance sake drawing near screen edges is not pixel perfect.
  875. */
  876. void SFG_blitImage(
  877. const uint8_t *image,
  878. int16_t posX,
  879. int16_t posY,
  880. uint8_t scale)
  881. {
  882. if (scale == 0)
  883. return;
  884. uint16_t x0 = posX,
  885. x1,
  886. y0 = posY,
  887. y1;
  888. uint8_t u0 = 0, v0 = 0;
  889. if (posX < 0)
  890. {
  891. x0 = 0;
  892. u0 = (-1 * posX) / scale;
  893. }
  894. posX += scale * SFG_TEXTURE_SIZE;
  895. uint16_t limitX = SFG_GAME_RESOLUTION_X - scale;
  896. uint16_t limitY = SFG_GAME_RESOLUTION_Y - scale;
  897. x1 = posX >= 0 ?
  898. (posX <= limitX ? posX : limitX)
  899. : 0;
  900. if (x1 >= SFG_GAME_RESOLUTION_X)
  901. x1 = SFG_GAME_RESOLUTION_X - 1;
  902. if (posY < 0)
  903. {
  904. y0 = 0;
  905. v0 = (-1 * posY) / scale;
  906. }
  907. posY += scale * SFG_TEXTURE_SIZE;
  908. y1 = posY >= 0 ? (posY <= limitY ? posY : limitY) : 0;
  909. if (y1 >= SFG_GAME_RESOLUTION_Y)
  910. y1 = SFG_GAME_RESOLUTION_Y - 1;
  911. uint8_t v = v0;
  912. for (uint16_t y = y0; y < y1; y += scale)
  913. {
  914. uint8_t u = u0;
  915. for (uint16_t x = x0; x < x1; x += scale)
  916. {
  917. uint8_t color = SFG_getTexel(image,u,v);
  918. if (color != SFG_TRANSPARENT_COLOR)
  919. {
  920. uint16_t sY = y;
  921. for (uint8_t j = 0; j < scale; ++j)
  922. {
  923. uint16_t sX = x;
  924. for (uint8_t i = 0; i < scale; ++i)
  925. {
  926. SFG_setGamePixel(sX,sY,color);
  927. sX++;
  928. }
  929. sY++;
  930. }
  931. }
  932. u++;
  933. }
  934. v++;
  935. }
  936. }
  937. void SFG_drawScaledSprite(
  938. const uint8_t *image,
  939. int16_t centerX,
  940. int16_t centerY,
  941. int16_t size,
  942. uint8_t minusValue,
  943. RCL_Unit distance)
  944. {
  945. if (size == 0)
  946. return;
  947. if (size > SFG_MAX_SPRITE_SIZE)
  948. size = SFG_MAX_SPRITE_SIZE;
  949. uint16_t halfSize = size / 2;
  950. int16_t topLeftX = centerX - halfSize;
  951. int16_t topLeftY = centerY - halfSize;
  952. int16_t x0, u0;
  953. if (topLeftX < 0)
  954. {
  955. u0 = -1 * topLeftX;
  956. x0 = 0;
  957. }
  958. else
  959. {
  960. u0 = 0;
  961. x0 = topLeftX;
  962. }
  963. int16_t x1 = topLeftX + size - 1;
  964. if (x1 >= SFG_GAME_RESOLUTION_X)
  965. x1 = SFG_GAME_RESOLUTION_X - 1;
  966. int16_t y0, v0;
  967. if (topLeftY < 0)
  968. {
  969. v0 = -1 * topLeftY;
  970. y0 = 0;
  971. }
  972. else
  973. {
  974. v0 = 0;
  975. y0 = topLeftY;
  976. }
  977. int16_t y1 = topLeftY + size - 1;
  978. if (y1 >= SFG_GAME_RESOLUTION_Y)
  979. y1 = SFG_GAME_RESOLUTION_Y - 1;
  980. if ((x0 > x1) || (y0 > y1) || (u0 >= size) || (v0 >= size)) // outside screen?
  981. return;
  982. int16_t u1 = u0 + (x1 - x0);
  983. int16_t v1 = v0 + (y1 - y0);
  984. // precompute sampling positions:
  985. int16_t uMin = RCL_min(u0,u1);
  986. int16_t vMin = RCL_min(v0,v1);
  987. int16_t uMax = RCL_max(u0,u1);
  988. int16_t vMax = RCL_max(v0,v1);
  989. int16_t precompFrom = RCL_min(uMin,vMin);
  990. int16_t precompTo = RCL_max(uMax,vMax);
  991. precompFrom = RCL_max(0,precompFrom);
  992. precompTo = RCL_min(SFG_MAX_SPRITE_SIZE - 1,precompTo);
  993. #define PRECOMP_SCALE 512
  994. int16_t precompStepScaled = ((SFG_TEXTURE_SIZE) * PRECOMP_SCALE) / size;
  995. int16_t precompPosScaled = precompFrom * precompStepScaled;
  996. for (int16_t i = precompFrom; i <= precompTo; ++i)
  997. {
  998. SFG_game.spriteSamplingPoints[i] = precompPosScaled / PRECOMP_SCALE;
  999. precompPosScaled += precompStepScaled;
  1000. }
  1001. #undef PRECOMP_SCALE
  1002. uint8_t zDistance = SFG_RCLUnitToZBuffer(distance);
  1003. for (int16_t x = x0, u = u0; x <= x1; ++x, ++u)
  1004. {
  1005. if (SFG_game.zBuffer[x] >= zDistance)
  1006. {
  1007. int8_t columnTransparent = 1;
  1008. for (int16_t y = y0, v = v0; y <= y1; ++y, ++v)
  1009. {
  1010. uint8_t color =
  1011. SFG_getTexel(image,SFG_game.spriteSamplingPoints[u],
  1012. SFG_game.spriteSamplingPoints[v]);
  1013. if (color != SFG_TRANSPARENT_COLOR)
  1014. {
  1015. #if SFG_DIMINISH_SPRITES
  1016. color = palette_minusValue(color,minusValue);
  1017. #endif
  1018. columnTransparent = 0;
  1019. SFG_setGamePixel(x,y,color);
  1020. }
  1021. }
  1022. if (!columnTransparent)
  1023. SFG_game.zBuffer[x] = zDistance;
  1024. }
  1025. }
  1026. }
  1027. RCL_Unit SFG_texturesAt(int16_t x, int16_t y)
  1028. {
  1029. uint8_t p;
  1030. SFG_TileDefinition tile =
  1031. SFG_getMapTile(SFG_currentLevel.levelPointer,x,y,&p);
  1032. return
  1033. SFG_TILE_FLOOR_TEXTURE(tile) | (SFG_TILE_CEILING_TEXTURE(tile) << 3) | p;
  1034. // ^ store both textures (floor and ceiling) and properties in one number
  1035. }
  1036. RCL_Unit SFG_movingWallHeight
  1037. (
  1038. RCL_Unit low,
  1039. RCL_Unit high,
  1040. uint32_t time
  1041. )
  1042. {
  1043. RCL_Unit height = RCL_nonZero(high - low);
  1044. RCL_Unit halfHeight = height / 2;
  1045. RCL_Unit sinArg =
  1046. (time * ((SFG_MOVING_WALL_SPEED * RCL_UNITS_PER_SQUARE) / 1000)) / height;
  1047. return
  1048. low + halfHeight + (RCL_sin(sinArg) * halfHeight) / RCL_UNITS_PER_SQUARE;
  1049. }
  1050. RCL_Unit SFG_floorHeightAt(int16_t x, int16_t y)
  1051. {
  1052. uint8_t properties;
  1053. SFG_TileDefinition tile =
  1054. SFG_getMapTile(SFG_currentLevel.levelPointer,x,y,&properties);
  1055. RCL_Unit doorHeight = 0;
  1056. if (properties == SFG_TILE_PROPERTY_DOOR)
  1057. {
  1058. for (uint8_t i = 0; i < SFG_currentLevel.doorRecordCount; ++i)
  1059. {
  1060. SFG_DoorRecord *door = &(SFG_currentLevel.doorRecords[i]);
  1061. if ((door->coords[0] == x) && (door->coords[1] == y))
  1062. {
  1063. doorHeight = door->state & SFG_DOOR_VERTICAL_POSITION_MASK;
  1064. doorHeight = doorHeight != (0xff & SFG_DOOR_VERTICAL_POSITION_MASK) ?
  1065. doorHeight * SFG_DOOR_HEIGHT_STEP : RCL_UNITS_PER_SQUARE;
  1066. break;
  1067. }
  1068. }
  1069. }
  1070. else if (properties == SFG_TILE_PROPERTY_ELEVATOR)
  1071. {
  1072. RCL_Unit height =
  1073. SFG_TILE_FLOOR_HEIGHT(tile) * SFG_WALL_HEIGHT_STEP;
  1074. return SFG_movingWallHeight(
  1075. height,
  1076. height + SFG_TILE_CEILING_HEIGHT(tile) * SFG_WALL_HEIGHT_STEP,
  1077. SFG_game.frameTime - SFG_currentLevel.timeStart);
  1078. }
  1079. return SFG_TILE_FLOOR_HEIGHT(tile) * SFG_WALL_HEIGHT_STEP - doorHeight;
  1080. }
  1081. /**
  1082. Like SFG_floorCollisionHeightAt, but takes into account colliding items on
  1083. the map, so the squares that have these items are higher. The former function
  1084. is for rendering, this one is for collision checking.
  1085. */
  1086. RCL_Unit SFG_floorCollisionHeightAt(int16_t x, int16_t y)
  1087. {
  1088. return SFG_floorHeightAt(x,y) +
  1089. SFG_getItemCollisionMapBit(x,y) * RCL_UNITS_PER_SQUARE;
  1090. }
  1091. void SFG_getPlayerWeaponInfo(
  1092. uint8_t *ammoType, uint8_t *projectileCount, uint8_t *canShoot)
  1093. {
  1094. *ammoType = SFG_weaponAmmo(SFG_player.weapon);
  1095. *projectileCount = SFG_GET_WEAPON_PROJECTILE_COUNT(SFG_player.weapon);
  1096. #if SFG_INFINITE_AMMO
  1097. *canShoot = 1;
  1098. #else
  1099. *canShoot =
  1100. ((*ammoType == SFG_AMMO_NONE) ||
  1101. (SFG_player.ammo[*ammoType] >= *projectileCount) ||
  1102. (SFG_game.cheatState & 0x80));
  1103. #endif
  1104. }
  1105. void SFG_playerRotateWeapon(uint8_t next)
  1106. {
  1107. uint8_t initialWeapon = SFG_player.weapon;
  1108. int8_t increment = next ? 1 : -1;
  1109. while (1)
  1110. {
  1111. SFG_player.weapon =
  1112. (SFG_WEAPONS_TOTAL + SFG_player.weapon + increment) % SFG_WEAPONS_TOTAL;
  1113. if (SFG_player.weapon == initialWeapon)
  1114. break;
  1115. uint8_t ammo, projectileCount, canShoot;
  1116. SFG_getPlayerWeaponInfo(&ammo,&projectileCount,&canShoot);
  1117. if (canShoot)
  1118. break;
  1119. }
  1120. }
  1121. void SFG_initPlayer(void)
  1122. {
  1123. RCL_initCamera(&SFG_player.camera);
  1124. SFG_player.camera.resolution.x =
  1125. SFG_GAME_RESOLUTION_X / SFG_RAYCASTING_SUBSAMPLE;
  1126. SFG_player.camera.resolution.y = SFG_GAME_RESOLUTION_Y - SFG_HUD_BAR_HEIGHT;
  1127. SFG_player.camera.position.x = RCL_UNITS_PER_SQUARE / 2 +
  1128. SFG_currentLevel.levelPointer->playerStart[0] * RCL_UNITS_PER_SQUARE;
  1129. SFG_player.camera.position.y = RCL_UNITS_PER_SQUARE / 2 +
  1130. SFG_currentLevel.levelPointer->playerStart[1] * RCL_UNITS_PER_SQUARE;
  1131. SFG_player.squarePosition[0] =
  1132. SFG_player.camera.position.x / RCL_UNITS_PER_SQUARE;
  1133. SFG_player.squarePosition[1] =
  1134. SFG_player.camera.position.y / RCL_UNITS_PER_SQUARE;
  1135. SFG_player.camera.height = SFG_floorHeightAt(
  1136. SFG_currentLevel.levelPointer->playerStart[0],
  1137. SFG_currentLevel.levelPointer->playerStart[1]) +
  1138. RCL_CAMERA_COLL_HEIGHT_BELOW;
  1139. SFG_player.camera.direction = SFG_currentLevel.levelPointer->playerStart[2] *
  1140. (RCL_UNITS_PER_SQUARE / 256);
  1141. SFG_recomputePLayerDirection();
  1142. SFG_player.previousVerticalSpeed = 0;
  1143. SFG_player.headBobFrame = 0;
  1144. SFG_player.weapon = SFG_WEAPON_KNIFE;
  1145. SFG_player.weaponCooldownFrames = 0;
  1146. SFG_player.lastHurtFrame = SFG_game.frame;
  1147. SFG_player.lastItemTakenFrame = SFG_game.frame;
  1148. SFG_player.health = SFG_PLAYER_START_HEALTH;
  1149. SFG_player.previousWeaponDirection = 0;
  1150. SFG_player.cards =
  1151. #if SFG_UNLOCK_DOOR
  1152. 0x07;
  1153. #else
  1154. 0;
  1155. #endif
  1156. SFG_player.justTeleported = 0;
  1157. for (uint8_t i = 0; i < SFG_AMMO_TOTAL; ++i)
  1158. SFG_player.ammo[i] = 0;
  1159. }
  1160. RCL_Unit SFG_ceilingHeightAt(int16_t x, int16_t y)
  1161. {
  1162. uint8_t properties;
  1163. SFG_TileDefinition tile =
  1164. SFG_getMapTile(SFG_currentLevel.levelPointer,x,y,&properties);
  1165. if (properties == SFG_TILE_PROPERTY_ELEVATOR)
  1166. return SFG_CEILING_MAX_HEIGHT;
  1167. uint8_t height = SFG_TILE_CEILING_HEIGHT(tile);
  1168. return properties != SFG_TILE_PROPERTY_SQUEEZER ?
  1169. (
  1170. height != SFG_TILE_CEILING_MAX_HEIGHT ?
  1171. ((SFG_TILE_FLOOR_HEIGHT(tile) + height) * SFG_WALL_HEIGHT_STEP) :
  1172. SFG_CEILING_MAX_HEIGHT
  1173. ) :
  1174. SFG_movingWallHeight(
  1175. SFG_TILE_FLOOR_HEIGHT(tile) * SFG_WALL_HEIGHT_STEP,
  1176. (SFG_TILE_CEILING_HEIGHT(tile) + SFG_TILE_FLOOR_HEIGHT(tile))
  1177. * SFG_WALL_HEIGHT_STEP,
  1178. SFG_game.frameTime - SFG_currentLevel.timeStart);
  1179. }
  1180. /**
  1181. Gets sprite (image and sprite size) for given item.
  1182. */
  1183. void SFG_getItemSprite(
  1184. uint8_t elementType, const uint8_t **sprite, uint8_t *spriteSize)
  1185. {
  1186. *spriteSize = 0;
  1187. *sprite = SFG_itemSprites + (elementType - 1) * SFG_TEXTURE_STORE_SIZE;
  1188. switch (elementType)
  1189. {
  1190. case SFG_LEVEL_ELEMENT_TREE:
  1191. case SFG_LEVEL_ELEMENT_RUIN:
  1192. case SFG_LEVEL_ELEMENT_LAMP:
  1193. case SFG_LEVEL_ELEMENT_TELEPORTER:
  1194. *spriteSize = 2;
  1195. break;
  1196. case SFG_LEVEL_ELEMENT_TERMINAL:
  1197. *spriteSize = 1;
  1198. break;
  1199. case SFG_LEVEL_ELEMENT_FINISH:
  1200. case SFG_LEVEL_ELEMENT_COLUMN:
  1201. *spriteSize = 3;
  1202. break;
  1203. case SFG_LEVEL_ELEMENT_CARD0:
  1204. case SFG_LEVEL_ELEMENT_CARD1:
  1205. case SFG_LEVEL_ELEMENT_CARD2:
  1206. *sprite = SFG_itemSprites +
  1207. (SFG_LEVEL_ELEMENT_CARD0 - 1) * SFG_TEXTURE_STORE_SIZE;
  1208. break;
  1209. case SFG_LEVEL_ELEMENT_BLOCKER:
  1210. *sprite = 0;
  1211. break;
  1212. default:
  1213. break;
  1214. }
  1215. }
  1216. /**
  1217. Says whether given item type collides, i.e. stops player from moving.
  1218. */
  1219. uint8_t SFG_itemCollides(uint8_t elementType)
  1220. {
  1221. return
  1222. elementType == SFG_LEVEL_ELEMENT_BARREL ||
  1223. elementType == SFG_LEVEL_ELEMENT_TREE ||
  1224. elementType == SFG_LEVEL_ELEMENT_TERMINAL ||
  1225. elementType == SFG_LEVEL_ELEMENT_COLUMN ||
  1226. elementType == SFG_LEVEL_ELEMENT_RUIN ||
  1227. elementType == SFG_LEVEL_ELEMENT_BLOCKER ||
  1228. elementType == SFG_LEVEL_ELEMENT_LAMP;
  1229. }
  1230. void SFG_setGameState(uint8_t state)
  1231. {
  1232. SFG_LOG("changing game state");
  1233. SFG_game.state = state;
  1234. SFG_game.stateTime = 0;
  1235. }
  1236. void SFG_setAndInitLevel(uint8_t levelNumber)
  1237. {
  1238. SFG_LOG("setting and initializing level");
  1239. const SFG_Level *level;
  1240. #if SFG_AVR
  1241. memcpy_P(&SFG_ramLevel,SFG_levels[levelNumber],sizeof(SFG_Level));
  1242. level = &SFG_ramLevel;
  1243. #else
  1244. level = SFG_levels[levelNumber];
  1245. #endif
  1246. SFG_game.currentRandom = 0;
  1247. if (SFG_game.saved != SFG_CANT_SAVE)
  1248. SFG_game.saved = 0;
  1249. SFG_currentLevel.levelNumber = levelNumber;
  1250. SFG_currentLevel.monstersDead = 0;
  1251. SFG_currentLevel.backgroundImage = level->backgroundImage;
  1252. SFG_currentLevel.levelPointer = level;
  1253. SFG_currentLevel.bossCount = 0;
  1254. SFG_currentLevel.floorColor = level->floorColor;
  1255. SFG_currentLevel.ceilingColor = level->ceilingColor;
  1256. SFG_currentLevel.completionTime10sOfS = 0;
  1257. for (uint8_t i = 0; i < 7; ++i)
  1258. SFG_currentLevel.textures[i] =
  1259. SFG_wallTextures + level->textureIndices[i] * SFG_TEXTURE_STORE_SIZE;
  1260. SFG_LOG("initializing doors");
  1261. SFG_currentLevel.checkedDoorIndex = 0;
  1262. SFG_currentLevel.doorRecordCount = 0;
  1263. SFG_currentLevel.projectileRecordCount = 0;
  1264. SFG_currentLevel.teleporterCount = 0;
  1265. SFG_currentLevel.mapRevealMask =
  1266. #if SFG_REVEAL_MAP
  1267. 0xffff;
  1268. #else
  1269. 0;
  1270. #endif
  1271. for (uint8_t j = 0; j < SFG_MAP_SIZE; ++j)
  1272. {
  1273. for (uint8_t i = 0; i < SFG_MAP_SIZE; ++i)
  1274. {
  1275. uint8_t properties;
  1276. SFG_getMapTile(level,i,j,&properties);
  1277. if ((properties & SFG_TILE_PROPERTY_MASK) == SFG_TILE_PROPERTY_DOOR)
  1278. {
  1279. SFG_DoorRecord *d =
  1280. &(SFG_currentLevel.doorRecords[SFG_currentLevel.doorRecordCount]);
  1281. d->coords[0] = i;
  1282. d->coords[1] = j;
  1283. d->state = 0x00;
  1284. SFG_currentLevel.doorRecordCount++;
  1285. }
  1286. if (SFG_currentLevel.doorRecordCount >= SFG_MAX_DOORS)
  1287. {
  1288. SFG_LOG("warning: too many doors!");
  1289. break;
  1290. }
  1291. }
  1292. if (SFG_currentLevel.doorRecordCount >= SFG_MAX_DOORS)
  1293. break;
  1294. }
  1295. SFG_LOG("initializing level elements");
  1296. SFG_currentLevel.itemRecordCount = 0;
  1297. SFG_currentLevel.checkedItemIndex = 0;
  1298. SFG_currentLevel.monsterRecordCount = 0;
  1299. SFG_currentLevel.checkedMonsterIndex = 0;
  1300. SFG_MonsterRecord *monster;
  1301. for (uint16_t i = 0; i < ((SFG_MAP_SIZE * SFG_MAP_SIZE) / 8); ++i)
  1302. SFG_currentLevel.itemCollisionMap[i] = 0;
  1303. for (uint8_t i = 0; i < SFG_MAX_LEVEL_ELEMENTS; ++i)
  1304. {
  1305. const SFG_LevelElement *e = &(SFG_currentLevel.levelPointer->elements[i]);
  1306. if (e->type != SFG_LEVEL_ELEMENT_NONE)
  1307. {
  1308. if (SFG_LEVEL_ELEMENT_TYPE_IS_MOSTER(e->type))
  1309. {
  1310. monster =
  1311. &(SFG_currentLevel.monsterRecords[SFG_currentLevel.monsterRecordCount]);
  1312. monster->stateType = (SFG_MONSTER_TYPE_TO_INDEX(e->type) << 4)
  1313. | SFG_MONSTER_STATE_INACTIVE;
  1314. monster->health =
  1315. SFG_GET_MONSTER_MAX_HEALTH(SFG_MONSTER_TYPE_TO_INDEX(e->type));
  1316. monster->coords[0] = e->coords[0] * 4 + 2;
  1317. monster->coords[1] = e->coords[1] * 4 + 2;
  1318. SFG_currentLevel.monsterRecordCount++;
  1319. if (e->type == SFG_LEVEL_ELEMENT_MONSTER_ENDER)
  1320. SFG_currentLevel.bossCount++;
  1321. }
  1322. else if ((e->type < SFG_LEVEL_ELEMENT_LOCK0) ||
  1323. (e->type > SFG_LEVEL_ELEMENT_LOCK2))
  1324. {
  1325. SFG_currentLevel.itemRecords[SFG_currentLevel.itemRecordCount] = i;
  1326. SFG_currentLevel.itemRecordCount++;
  1327. if (e->type == SFG_LEVEL_ELEMENT_TELEPORTER)
  1328. SFG_currentLevel.teleporterCount++;
  1329. if (SFG_itemCollides(e->type))
  1330. SFG_setItemCollisionMapBit(e->coords[0],e->coords[1],1);
  1331. }
  1332. else
  1333. {
  1334. uint8_t properties;
  1335. SFG_getMapTile(level,e->coords[0],e->coords[1],&properties);
  1336. if ((properties & SFG_TILE_PROPERTY_MASK) == SFG_TILE_PROPERTY_DOOR)
  1337. {
  1338. // find the door record and lock the door:
  1339. for (uint16_t j = 0; j < SFG_currentLevel.doorRecordCount; ++j)
  1340. {
  1341. SFG_DoorRecord *d = &(SFG_currentLevel.doorRecords[j]);
  1342. if (d->coords[0] == e->coords[0] && d->coords[1] == e->coords[1])
  1343. {
  1344. d->state |= (e->type - SFG_LEVEL_ELEMENT_LOCK0 + 1) << 6;
  1345. break;
  1346. }
  1347. }
  1348. }
  1349. else
  1350. {
  1351. SFG_LOG("warning: lock not put on door tile!");
  1352. }
  1353. }
  1354. }
  1355. }
  1356. SFG_currentLevel.timeStart = SFG_game.frameTime;
  1357. SFG_currentLevel.frameStart = SFG_game.frame;
  1358. SFG_game.spriteAnimationFrame = 0;
  1359. SFG_initPlayer();
  1360. SFG_setGameState(SFG_GAME_STATE_LEVEL_START);
  1361. SFG_setMusic(SFG_MUSIC_NEXT);
  1362. SFG_processEvent(SFG_EVENT_LEVEL_STARTS,levelNumber);
  1363. }
  1364. void SFG_createDefaultSaveData(uint8_t *memory)
  1365. {
  1366. for (uint16_t i = 0; i < SFG_SAVE_SIZE; ++i)
  1367. memory[i] = 0;
  1368. memory[1] = SFG_DEFAULT_SETTINGS;
  1369. }
  1370. void SFG_init(void)
  1371. {
  1372. SFG_LOG("initializing game")
  1373. SFG_game.frame = 0;
  1374. SFG_game.frameTime = 0;
  1375. SFG_game.currentRandom = 0;
  1376. SFG_game.cheatState = 0;
  1377. SFG_game.continues = 1;
  1378. RCL_initRayConstraints(&SFG_game.rayConstraints);
  1379. SFG_game.rayConstraints.maxHits = SFG_RAYCASTING_MAX_HITS;
  1380. SFG_game.rayConstraints.maxSteps = SFG_RAYCASTING_MAX_STEPS;
  1381. RCL_initRayConstraints(&SFG_game.visibilityRayConstraints);
  1382. SFG_game.visibilityRayConstraints.maxHits =
  1383. SFG_RAYCASTING_VISIBILITY_MAX_HITS;
  1384. SFG_game.visibilityRayConstraints.maxSteps =
  1385. SFG_RAYCASTING_VISIBILITY_MAX_STEPS;
  1386. SFG_game.antiSpam = 0;
  1387. SFG_LOG("computing average texture colors")
  1388. for (uint8_t i = 0; i < SFG_WALL_TEXTURE_COUNT; ++i)
  1389. {
  1390. /** For simplicity, we round colors so that there is only 64 of them, and
  1391. we count them up to 256. */
  1392. uint8_t colorHistogram[64];
  1393. for (uint8_t j = 0; j < 64; ++j)
  1394. colorHistogram[j] = 0;
  1395. for (uint8_t y = 0; y < SFG_TEXTURE_SIZE; ++y)
  1396. for (uint8_t x = 0; x < SFG_TEXTURE_SIZE; ++x)
  1397. {
  1398. uint8_t color =
  1399. SFG_getTexel(SFG_wallTextures + i * SFG_TEXTURE_STORE_SIZE,x,y) / 4;
  1400. colorHistogram[color] += 1;
  1401. if (colorHistogram[color] == 255)
  1402. break;
  1403. }
  1404. uint8_t maxIndex = 0;
  1405. for (uint8_t j = 0; j < 64; ++j)
  1406. {
  1407. if (colorHistogram[j] == 255)
  1408. {
  1409. maxIndex = j;
  1410. break;
  1411. }
  1412. if (colorHistogram[j] > colorHistogram[maxIndex])
  1413. maxIndex = j;
  1414. }
  1415. SFG_game.textureAverageColors[i] = maxIndex * 4;
  1416. }
  1417. for (uint16_t i = 0; i < SFG_GAME_RESOLUTION_Y; ++i)
  1418. SFG_game.backgroundScaleMap[i] =
  1419. (i * SFG_TEXTURE_SIZE) / SFG_GAME_RESOLUTION_Y;
  1420. for (uint8_t i = 0; i < SFG_KEY_COUNT; ++i)
  1421. SFG_game.keyStates[i] = 0;
  1422. SFG_currentLevel.levelPointer = 0;
  1423. SFG_game.backgroundScroll = 0;
  1424. SFG_game.selectedMenuItem = 0;
  1425. SFG_game.selectedLevel = 0;
  1426. SFG_game.settings = SFG_DEFAULT_SETTINGS;
  1427. SFG_game.saved = 0;
  1428. SFG_createDefaultSaveData(SFG_game.save);
  1429. SFG_gameLoad(); // attempt to load settings
  1430. if (SFG_game.saved != SFG_CANT_SAVE)
  1431. {
  1432. SFG_LOG("settings loaded");
  1433. SFG_game.settings = SFG_game.save[1];
  1434. }
  1435. else
  1436. {
  1437. SFG_LOG("saving/loading not possible");
  1438. SFG_game.save[0] = SFG_NUMBER_OF_LEVELS - 1; // revealed all levels
  1439. }
  1440. #if SFG_ALL_LEVELS
  1441. SFG_game.save[0] = SFG_NUMBER_OF_LEVELS - 1;
  1442. #endif
  1443. SFG_setMusic((SFG_game.settings & 0x02) ?
  1444. SFG_MUSIC_TURN_ON : SFG_MUSIC_TURN_OFF);
  1445. #if SFG_START_LEVEL == 0
  1446. SFG_setGameState(SFG_GAME_STATE_INIT);
  1447. #else
  1448. SFG_setAndInitLevel(SFG_START_LEVEL - 1);
  1449. #endif
  1450. }
  1451. /**
  1452. Adds new projectile to the current level, returns 1 if added, 0 if not (max
  1453. count reached).
  1454. */
  1455. uint8_t SFG_createProjectile(SFG_ProjectileRecord projectile)
  1456. {
  1457. if (SFG_currentLevel.projectileRecordCount >= SFG_MAX_PROJECTILES)
  1458. return 0;
  1459. SFG_currentLevel.projectileRecords[SFG_currentLevel.projectileRecordCount] =
  1460. projectile;
  1461. SFG_currentLevel.projectileRecordCount++;
  1462. return 1;
  1463. }
  1464. /**
  1465. Launches projectile of given type from given position in given direction
  1466. (has to be normalized), with given offset (so as to not collide with the
  1467. shooting entity). Returns the same value as SFG_createProjectile.
  1468. */
  1469. uint8_t SFG_launchProjectile(
  1470. uint8_t type,
  1471. RCL_Vector2D shootFrom,
  1472. RCL_Unit shootFromHeight,
  1473. RCL_Vector2D direction,
  1474. RCL_Unit verticalSpeed,
  1475. RCL_Unit offsetDistance
  1476. )
  1477. {
  1478. if (type == SFG_PROJECTILE_NONE)
  1479. return 0;
  1480. SFG_ProjectileRecord p;
  1481. p.type = type;
  1482. p.doubleFramesToLive =
  1483. RCL_nonZero(SFG_GET_PROJECTILE_FRAMES_TO_LIVE(type) / 2);
  1484. p.position[0] =
  1485. shootFrom.x + (direction.x * offsetDistance) / RCL_UNITS_PER_SQUARE;
  1486. p.position[1] =
  1487. shootFrom.y + (direction.y * offsetDistance) / RCL_UNITS_PER_SQUARE;
  1488. p.position[2] = shootFromHeight;
  1489. p.direction[0] =
  1490. (direction.x * SFG_GET_PROJECTILE_SPEED_UPF(type)) / RCL_UNITS_PER_SQUARE;
  1491. p.direction[1] =
  1492. (direction.y * SFG_GET_PROJECTILE_SPEED_UPF(type)) / RCL_UNITS_PER_SQUARE;
  1493. p.direction[2] = verticalSpeed;
  1494. return SFG_createProjectile(p);
  1495. }
  1496. /**
  1497. Pushes a given position away from a center by given distance, with collisions.
  1498. Returns 1 if push away happened, otherwise 0.
  1499. */
  1500. uint8_t SFG_pushAway(
  1501. RCL_Unit pos[3],
  1502. RCL_Unit centerX,
  1503. RCL_Unit centerY,
  1504. RCL_Unit preferredDirection,
  1505. RCL_Unit distance)
  1506. {
  1507. RCL_Vector2D fromCenter;
  1508. fromCenter.x = pos[0] - centerX;
  1509. fromCenter.y = pos[1] - centerY;
  1510. RCL_Unit l = RCL_len(fromCenter);
  1511. if (l < 128)
  1512. {
  1513. fromCenter = RCL_angleToDirection(preferredDirection);
  1514. l = RCL_UNITS_PER_SQUARE;
  1515. }
  1516. RCL_Vector2D offset;
  1517. offset.x = (fromCenter.x * distance) / l;
  1518. offset.y = (fromCenter.y * distance) / l;
  1519. RCL_Camera c;
  1520. RCL_initCamera(&c);
  1521. c.position.x = pos[0];
  1522. c.position.y = pos[1];
  1523. c.height = pos[2];
  1524. RCL_moveCameraWithCollision(&c,offset,0,SFG_floorCollisionHeightAt,
  1525. SFG_ceilingHeightAt,1,1);
  1526. pos[0] = c.position.x;
  1527. pos[1] = c.position.y;
  1528. pos[2] = c.height;
  1529. return 1;
  1530. }
  1531. uint8_t SFG_pushPlayerAway(
  1532. RCL_Unit centerX, RCL_Unit centerY, RCL_Unit distance)
  1533. {
  1534. RCL_Unit p[3];
  1535. p[0] = SFG_player.camera.position.x;
  1536. p[1] = SFG_player.camera.position.y;
  1537. p[2] = SFG_player.camera.height;
  1538. uint8_t result = SFG_pushAway(p,centerX,centerY,
  1539. SFG_player.camera.direction - RCL_UNITS_PER_SQUARE / 2,
  1540. distance);
  1541. SFG_player.camera.position.x = p[0];
  1542. SFG_player.camera.position.y = p[1];
  1543. SFG_player.camera.height = p[2];
  1544. return result;
  1545. }
  1546. /**
  1547. Helper function to resolve collision with level element. The function supposes
  1548. the collision already does happen and only resolves it. Returns adjusted move
  1549. offset.
  1550. */
  1551. RCL_Vector2D SFG_resolveCollisionWithElement(
  1552. RCL_Vector2D position, RCL_Vector2D moveOffset, RCL_Vector2D elementPos)
  1553. {
  1554. RCL_Unit dx = RCL_abs(elementPos.x - position.x);
  1555. RCL_Unit dy = RCL_abs(elementPos.y - position.y);
  1556. if (dx > dy)
  1557. {
  1558. // colliding from left/right
  1559. if ((moveOffset.x > 0) == (position.x < elementPos.x))
  1560. moveOffset.x = 0;
  1561. // ^ only stop if heading towards element, to avoid getting stuck
  1562. }
  1563. else
  1564. {
  1565. // colliding from up/down
  1566. if ((moveOffset.y > 0) == (position.y < elementPos.y))
  1567. moveOffset.y = 0;
  1568. }
  1569. return moveOffset;
  1570. }
  1571. /**
  1572. Adds or subtracts player's health during the playing state due to taking
  1573. damage (negative value) or getting healed. Negative value will be corrected by
  1574. SFG_PLAYER_DAMAGE_MULTIPLIER in this function.
  1575. */
  1576. void SFG_playerChangeHealth(int8_t healthAdd)
  1577. {
  1578. if (SFG_game.state != SFG_GAME_STATE_PLAYING)
  1579. return; // don't hurt during level starting phase
  1580. if (healthAdd < 0)
  1581. {
  1582. if (SFG_game.cheatState & 0x80) // invincible?
  1583. return;
  1584. healthAdd =
  1585. RCL_min(-1,
  1586. (((RCL_Unit) healthAdd) * SFG_PLAYER_DAMAGE_MULTIPLIER) /
  1587. RCL_UNITS_PER_SQUARE);
  1588. SFG_player.lastHurtFrame = SFG_game.frame;
  1589. SFG_processEvent(SFG_EVENT_VIBRATE,0);
  1590. SFG_processEvent(SFG_EVENT_PLAYER_HURT,-1 * healthAdd);
  1591. }
  1592. int16_t health = SFG_player.health;
  1593. health += healthAdd;
  1594. health = RCL_clamp(health,0,SFG_PLAYER_MAX_HEALTH);
  1595. SFG_player.health = health;
  1596. }
  1597. uint8_t SFG_distantSoundVolume(RCL_Unit x, RCL_Unit y, RCL_Unit z)
  1598. {
  1599. RCL_Unit distance = SFG_taxicabDistance(x,y,z,
  1600. SFG_player.camera.position.x,
  1601. SFG_player.camera.position.y,
  1602. SFG_player.camera.height);
  1603. if (distance >= SFG_SFX_MAX_DISTANCE)
  1604. return 0;
  1605. uint32_t result = 255 - (distance * 255) / SFG_SFX_MAX_DISTANCE;
  1606. return (result * result) / 256;
  1607. }
  1608. /**
  1609. Same as SFG_playerChangeHealth but for monsters.
  1610. */
  1611. void SFG_monsterChangeHealth(SFG_MonsterRecord *monster, int8_t healthAdd)
  1612. {
  1613. int16_t health = monster->health;
  1614. health += healthAdd;
  1615. health = RCL_clamp(health,0,255);
  1616. monster->health = health;
  1617. if (healthAdd < 0)
  1618. {
  1619. // play hurt sound
  1620. uint8_t volume = SFG_distantSoundVolume(
  1621. SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[0]),
  1622. SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[1]),
  1623. SFG_floorHeightAt(
  1624. SFG_MONSTER_COORD_TO_SQUARES(monster->coords[0]),
  1625. SFG_MONSTER_COORD_TO_SQUARES(monster->coords[1])));
  1626. SFG_playGameSound(5,volume);
  1627. if (monster->health == 0)
  1628. SFG_playGameSound(2,volume);
  1629. }
  1630. }
  1631. void SFG_removeItem(uint8_t index)
  1632. {
  1633. SFG_LOG("removing item");
  1634. for (uint16_t j = index; j < SFG_currentLevel.itemRecordCount - 1; ++j)
  1635. SFG_currentLevel.itemRecords[j] =
  1636. SFG_currentLevel.itemRecords[j + 1];
  1637. SFG_currentLevel.itemRecordCount--;
  1638. }
  1639. /**
  1640. Checks a 3D point visibility from player's position (WITHOUT considering
  1641. facing direction).
  1642. */
  1643. static inline uint8_t SFG_spriteIsVisible(RCL_Vector2D pos, RCL_Unit height)
  1644. {
  1645. return
  1646. RCL_castRay3D(
  1647. SFG_player.camera.position,
  1648. SFG_player.camera.height,
  1649. pos,
  1650. height,
  1651. SFG_floorHeightAt,
  1652. SFG_ceilingHeightAt,
  1653. SFG_game.visibilityRayConstraints
  1654. ) == RCL_UNITS_PER_SQUARE;
  1655. }
  1656. RCL_Unit SFG_directionTangent(RCL_Unit dirX, RCL_Unit dirY, RCL_Unit dirZ)
  1657. {
  1658. RCL_Vector2D v;
  1659. v.x = dirX;
  1660. v.y = dirY;
  1661. return (dirZ * RCL_UNITS_PER_SQUARE) / RCL_len(v);
  1662. }
  1663. /**
  1664. Returns a tangent in RCL_Unit of vertical autoaim, given current game state.
  1665. */
  1666. RCL_Unit SFG_autoaimVertically(void)
  1667. {
  1668. for (uint16_t i = 0; i < SFG_currentLevel.monsterRecordCount; ++i)
  1669. {
  1670. SFG_MonsterRecord m = SFG_currentLevel.monsterRecords[i];
  1671. uint8_t state = SFG_MR_STATE(m);
  1672. if (state == SFG_MONSTER_STATE_INACTIVE ||
  1673. state == SFG_MONSTER_STATE_DEAD)
  1674. continue;
  1675. RCL_Vector2D worldPosition, toMonster;
  1676. worldPosition.x = SFG_MONSTER_COORD_TO_RCL_UNITS(m.coords[0]);
  1677. worldPosition.y = SFG_MONSTER_COORD_TO_RCL_UNITS(m.coords[1]);
  1678. toMonster.x = worldPosition.x - SFG_player.camera.position.x;
  1679. toMonster.y = worldPosition.y - SFG_player.camera.position.y;
  1680. if (RCL_abs(
  1681. RCL_vectorsAngleCos(SFG_player.direction,toMonster)
  1682. - RCL_UNITS_PER_SQUARE) < SFG_VERTICAL_AUTOAIM_ANGLE_THRESHOLD)
  1683. {
  1684. uint8_t spriteSize = SFG_GET_MONSTER_SPRITE_SIZE(
  1685. SFG_MONSTER_TYPE_TO_INDEX(SFG_MR_TYPE(m)));
  1686. RCL_Unit worldHeight =
  1687. SFG_floorHeightAt(
  1688. SFG_MONSTER_COORD_TO_SQUARES(m.coords[0]),
  1689. SFG_MONSTER_COORD_TO_SQUARES(m.coords[1]))
  1690. +
  1691. SFG_SPRITE_SIZE_TO_HEIGHT_ABOVE_GROUND(spriteSize);
  1692. if (SFG_spriteIsVisible(worldPosition,worldHeight))
  1693. return SFG_directionTangent(toMonster.x,toMonster.y,
  1694. worldHeight - (SFG_player.camera.height));
  1695. }
  1696. }
  1697. return 0;
  1698. }
  1699. /**
  1700. Helper function, returns a pointer to level element representing item with
  1701. given index, but only if the item is active (otherwise 0 is returned).
  1702. */
  1703. static inline const SFG_LevelElement *SFG_getActiveItemElement(uint8_t index)
  1704. {
  1705. SFG_ItemRecord item = SFG_currentLevel.itemRecords[index];
  1706. if ((item & SFG_ITEM_RECORD_ACTIVE_MASK) == 0)
  1707. return 0;
  1708. return &(SFG_currentLevel.levelPointer->elements[item &
  1709. ~SFG_ITEM_RECORD_ACTIVE_MASK]);
  1710. }
  1711. static inline const SFG_LevelElement *SFG_getLevelElement(uint8_t index)
  1712. {
  1713. SFG_ItemRecord item = SFG_currentLevel.itemRecords[index];
  1714. return &(SFG_currentLevel.levelPointer->elements[item &
  1715. ~SFG_ITEM_RECORD_ACTIVE_MASK]);
  1716. }
  1717. void SFG_createExplosion(RCL_Unit, RCL_Unit, RCL_Unit); // forward decl
  1718. void SFG_explodeBarrel(uint8_t itemIndex, RCL_Unit x, RCL_Unit y, RCL_Unit z)
  1719. {
  1720. const SFG_LevelElement *e = SFG_getLevelElement(itemIndex);
  1721. SFG_setItemCollisionMapBit(e->coords[0],e->coords[1],0);
  1722. SFG_removeItem(itemIndex);
  1723. SFG_createExplosion(x,y,z);
  1724. }
  1725. void SFG_createExplosion(RCL_Unit x, RCL_Unit y, RCL_Unit z)
  1726. {
  1727. SFG_ProjectileRecord explosion;
  1728. SFG_playGameSound(2,SFG_distantSoundVolume(x,y,z));
  1729. SFG_processEvent(SFG_EVENT_EXPLOSION,0);
  1730. explosion.type = SFG_PROJECTILE_EXPLOSION;
  1731. explosion.position[0] = x;
  1732. explosion.position[1] = y;
  1733. explosion.position[2] = z;
  1734. explosion.direction[0] = 0;
  1735. explosion.direction[1] = 0;
  1736. explosion.direction[2] = 0;
  1737. explosion.doubleFramesToLive = RCL_nonZero(
  1738. SFG_GET_PROJECTILE_FRAMES_TO_LIVE(SFG_PROJECTILE_EXPLOSION) / 2);
  1739. SFG_createProjectile(explosion);
  1740. uint8_t damage = SFG_getDamageValue(SFG_WEAPON_FIRE_TYPE_FIREBALL);
  1741. if (SFG_taxicabDistance(x,y,z,SFG_player.camera.position.x,
  1742. SFG_player.camera.position.y,SFG_player.camera.height)
  1743. <= SFG_EXPLOSION_RADIUS)
  1744. {
  1745. SFG_playerChangeHealth(-1 * damage);
  1746. SFG_pushPlayerAway(x,y,SFG_EXPLOSION_PUSH_AWAY_DISTANCE);
  1747. }
  1748. for (uint16_t i = 0; i < SFG_currentLevel.monsterRecordCount; ++i)
  1749. {
  1750. SFG_MonsterRecord *monster = &(SFG_currentLevel.monsterRecords[i]);
  1751. uint16_t state = SFG_MR_STATE(*monster);
  1752. if ((state == SFG_MONSTER_STATE_INACTIVE) ||
  1753. (state == SFG_MONSTER_STATE_DEAD))
  1754. continue;
  1755. RCL_Unit monsterHeight =
  1756. SFG_floorHeightAt(
  1757. SFG_MONSTER_COORD_TO_SQUARES(monster->coords[0]),
  1758. SFG_MONSTER_COORD_TO_SQUARES(monster->coords[1]))
  1759. + RCL_UNITS_PER_SQUARE / 2;
  1760. if (SFG_taxicabDistance(
  1761. SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[0]),
  1762. SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[1]),monsterHeight,
  1763. x,y,z) <= SFG_EXPLOSION_RADIUS)
  1764. {
  1765. SFG_monsterChangeHealth(monster,
  1766. -1 * SFG_getDamageValue(SFG_WEAPON_FIRE_TYPE_FIREBALL));
  1767. }
  1768. }
  1769. // explode nearby barrels
  1770. if (damage >= SFG_BARREL_EXPLOSION_DAMAGE_THRESHOLD)
  1771. for (uint16_t i = 0; i < SFG_currentLevel.itemRecordCount; ++i)
  1772. {
  1773. SFG_ItemRecord item = SFG_currentLevel.itemRecords[i];
  1774. /* We DON'T check just active barrels but all, otherwise it looks weird
  1775. that out of sight barrels in a line didn't explode.*/
  1776. SFG_LevelElement element = SFG_ITEM_RECORD_LEVEL_ELEMENT(item);
  1777. if (element.type != SFG_LEVEL_ELEMENT_BARREL)
  1778. continue;
  1779. RCL_Unit elementX =
  1780. element.coords[0] * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2;
  1781. RCL_Unit elementY =
  1782. element.coords[1] * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2;
  1783. RCL_Unit elementHeight =
  1784. SFG_floorHeightAt(element.coords[0],element.coords[1]);
  1785. if (SFG_taxicabDistance(
  1786. x,y,z,elementX,elementY,elementHeight) <= SFG_EXPLOSION_RADIUS)
  1787. {
  1788. SFG_explodeBarrel(i,elementX,elementY,elementHeight);
  1789. i--;
  1790. }
  1791. }
  1792. }
  1793. void SFG_createDust(RCL_Unit x, RCL_Unit y, RCL_Unit z)
  1794. {
  1795. SFG_ProjectileRecord dust;
  1796. dust.type = SFG_PROJECTILE_DUST;
  1797. dust.position[0] = x;
  1798. dust.position[1] = y;
  1799. dust.position[2] = z;
  1800. dust.direction[0] = 0;
  1801. dust.direction[1] = 0;
  1802. dust.direction[2] = 0;
  1803. dust.doubleFramesToLive =
  1804. RCL_nonZero(SFG_GET_PROJECTILE_FRAMES_TO_LIVE(SFG_PROJECTILE_DUST) / 2);
  1805. SFG_createProjectile(dust);
  1806. }
  1807. void SFG_getMonsterWorldPosition(SFG_MonsterRecord *monster, RCL_Unit *x,
  1808. RCL_Unit *y, RCL_Unit *z)
  1809. {
  1810. *x = SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[0]);
  1811. *y = SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[1]);
  1812. *z = SFG_floorHeightAt(
  1813. SFG_MONSTER_COORD_TO_SQUARES(monster->coords[0]),
  1814. SFG_MONSTER_COORD_TO_SQUARES(monster->coords[1]))
  1815. + RCL_UNITS_PER_SQUARE / 2;
  1816. }
  1817. void SFG_monsterPerformAI(SFG_MonsterRecord *monster)
  1818. {
  1819. uint8_t state = SFG_MR_STATE(*monster);
  1820. uint8_t type = SFG_MR_TYPE(*monster);
  1821. uint8_t monsterNumber = SFG_MONSTER_TYPE_TO_INDEX(type);
  1822. uint8_t attackType = SFG_GET_MONSTER_ATTACK_TYPE(monsterNumber);
  1823. int8_t coordAdd[2];
  1824. coordAdd[0] = 0;
  1825. coordAdd[1] = 0;
  1826. uint8_t notRanged =
  1827. (attackType == SFG_MONSTER_ATTACK_MELEE) ||
  1828. (attackType == SFG_MONSTER_ATTACK_EXPLODE);
  1829. uint8_t monsterSquare[2];
  1830. /* because of some insanely retarded C++ compilers that error on narrowing
  1831. conversion between { } we init this way: */
  1832. monsterSquare[0] = SFG_MONSTER_COORD_TO_SQUARES(monster->coords[0]);
  1833. monsterSquare[1] = SFG_MONSTER_COORD_TO_SQUARES(monster->coords[1]);
  1834. RCL_Unit currentHeight =
  1835. SFG_floorCollisionHeightAt(monsterSquare[0],monsterSquare[1]);
  1836. if ( // ranged monsters: sometimes randomly attack
  1837. !notRanged &&
  1838. (SFG_random() <
  1839. SFG_GET_MONSTER_AGGRESSIVITY(SFG_MONSTER_TYPE_TO_INDEX(type)))
  1840. )
  1841. {
  1842. RCL_Vector2D pos;
  1843. pos.x = SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[0]);
  1844. pos.y = SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[1]);
  1845. if (SFG_random() % 4 != 0 &&
  1846. SFG_spriteIsVisible(pos,currentHeight + // only if player is visible
  1847. SFG_SPRITE_SIZE_TO_HEIGHT_ABOVE_GROUND(
  1848. SFG_GET_MONSTER_SPRITE_SIZE(
  1849. SFG_MONSTER_TYPE_TO_INDEX(type)))))
  1850. {
  1851. // ranged attack
  1852. state = SFG_MONSTER_STATE_ATTACKING;
  1853. RCL_Vector2D dir;
  1854. dir.x = SFG_player.camera.position.x - pos.x
  1855. - 128 * SFG_MONSTER_AIM_RANDOMNESS +
  1856. SFG_random() * SFG_MONSTER_AIM_RANDOMNESS;
  1857. dir.y = SFG_player.camera.position.y - pos.y
  1858. - 128 * SFG_MONSTER_AIM_RANDOMNESS +
  1859. SFG_random() * SFG_MONSTER_AIM_RANDOMNESS;
  1860. uint8_t projectile;
  1861. switch (SFG_GET_MONSTER_ATTACK_TYPE(monsterNumber))
  1862. {
  1863. case SFG_MONSTER_ATTACK_FIREBALL:
  1864. projectile = SFG_PROJECTILE_FIREBALL;
  1865. break;
  1866. case SFG_MONSTER_ATTACK_BULLET:
  1867. projectile = SFG_PROJECTILE_BULLET;
  1868. break;
  1869. case SFG_MONSTER_ATTACK_PLASMA:
  1870. projectile = SFG_PROJECTILE_PLASMA;
  1871. break;
  1872. case SFG_MONSTER_ATTACK_FIREBALL_BULLET:
  1873. projectile = (SFG_random() < 128) ?
  1874. SFG_PROJECTILE_FIREBALL :
  1875. SFG_PROJECTILE_BULLET;
  1876. break;
  1877. case SFG_MONSTER_ATTACK_FIREBALL_PLASMA:
  1878. projectile = (SFG_random() < 128) ?
  1879. SFG_PROJECTILE_FIREBALL :
  1880. SFG_PROJECTILE_PLASMA;
  1881. break;
  1882. default:
  1883. projectile = SFG_PROJECTILE_NONE;
  1884. break;
  1885. }
  1886. if (projectile == SFG_PROJECTILE_BULLET)
  1887. SFG_playGameSound(0,
  1888. SFG_distantSoundVolume(
  1889. SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[0]),
  1890. SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[1]),
  1891. currentHeight)
  1892. );
  1893. RCL_Unit middleHeight = currentHeight +
  1894. SFG_SPRITE_SIZE_TO_HEIGHT_ABOVE_GROUND(SFG_GET_MONSTER_SPRITE_SIZE(
  1895. SFG_MONSTER_TYPE_TO_INDEX(SFG_MR_TYPE(*monster))));
  1896. RCL_Unit verticalSpeed = (
  1897. ((projectile != SFG_PROJECTILE_NONE) ?
  1898. SFG_GET_PROJECTILE_SPEED_UPF(projectile) : 0) *
  1899. SFG_directionTangent(dir.x,dir.y,SFG_player.camera.height -
  1900. middleHeight)) / RCL_UNITS_PER_SQUARE;
  1901. dir = RCL_normalize(dir);
  1902. SFG_launchProjectile(
  1903. projectile,
  1904. pos,
  1905. middleHeight,
  1906. dir,
  1907. verticalSpeed,
  1908. SFG_PROJECTILE_SPAWN_OFFSET
  1909. );
  1910. } // if visible
  1911. else
  1912. state = SFG_MONSTER_STATE_IDLE;
  1913. }
  1914. else if (state == SFG_MONSTER_STATE_IDLE)
  1915. {
  1916. if (notRanged)
  1917. {
  1918. // non-ranged monsters: walk towards player
  1919. RCL_Unit pX, pY, pZ;
  1920. SFG_getMonsterWorldPosition(monster,&pX,&pY,&pZ);
  1921. uint8_t isClose = // close to player?
  1922. SFG_taxicabDistance(pX,pY,pZ,
  1923. SFG_player.camera.position.x,
  1924. SFG_player.camera.position.y,
  1925. SFG_player.camera.height) <= SFG_MELEE_RANGE;
  1926. if (!isClose)
  1927. {
  1928. // walk towards player
  1929. if (monsterSquare[0] > SFG_player.squarePosition[0])
  1930. {
  1931. if (monsterSquare[1] > SFG_player.squarePosition[1])
  1932. state = SFG_MONSTER_STATE_GOING_NW;
  1933. else if (monsterSquare[1] < SFG_player.squarePosition[1])
  1934. state = SFG_MONSTER_STATE_GOING_SW;
  1935. else
  1936. state = SFG_MONSTER_STATE_GOING_W;
  1937. }
  1938. else if (monsterSquare[0] < SFG_player.squarePosition[0])
  1939. {
  1940. if (monsterSquare[1] > SFG_player.squarePosition[1])
  1941. state = SFG_MONSTER_STATE_GOING_NE;
  1942. else if (monsterSquare[1] < SFG_player.squarePosition[1])
  1943. state = SFG_MONSTER_STATE_GOING_SE;
  1944. else
  1945. state = SFG_MONSTER_STATE_GOING_E;
  1946. }
  1947. else
  1948. {
  1949. if (monsterSquare[1] > SFG_player.squarePosition[1])
  1950. state = SFG_MONSTER_STATE_GOING_N;
  1951. else if (monsterSquare[1] < SFG_player.squarePosition[1])
  1952. state = SFG_MONSTER_STATE_GOING_S;
  1953. }
  1954. }
  1955. else // is close
  1956. {
  1957. // melee, close-up attack
  1958. if (attackType == SFG_MONSTER_ATTACK_MELEE)
  1959. {
  1960. // melee attack
  1961. state = SFG_MONSTER_STATE_ATTACKING;
  1962. SFG_playerChangeHealth(
  1963. -1 * SFG_getDamageValue(SFG_WEAPON_FIRE_TYPE_MELEE));
  1964. SFG_playGameSound(3,255);
  1965. }
  1966. else // SFG_MONSTER_ATTACK_EXPLODE
  1967. {
  1968. // explode
  1969. SFG_createExplosion(pX,pY,pZ);
  1970. monster->health = 0;
  1971. }
  1972. }
  1973. }
  1974. else // ranged monsters
  1975. {
  1976. // choose walk direction randomly
  1977. switch (SFG_random() % 8)
  1978. {
  1979. case 0: state = SFG_MONSTER_STATE_GOING_E; break;
  1980. case 1: state = SFG_MONSTER_STATE_GOING_W; break;
  1981. case 2: state = SFG_MONSTER_STATE_GOING_N; break;
  1982. case 3: state = SFG_MONSTER_STATE_GOING_S; break;
  1983. case 4: state = SFG_MONSTER_STATE_GOING_NE; break;
  1984. case 5: state = SFG_MONSTER_STATE_GOING_NW; break;
  1985. case 6: state = SFG_MONSTER_STATE_GOING_SE; break;
  1986. case 7: state = SFG_MONSTER_STATE_GOING_SW; break;
  1987. default: break;
  1988. }
  1989. }
  1990. }
  1991. else if (state == SFG_MONSTER_STATE_ATTACKING)
  1992. {
  1993. state = SFG_MONSTER_STATE_IDLE;
  1994. }
  1995. else
  1996. {
  1997. int8_t add = 1;
  1998. if (attackType == SFG_MONSTER_ATTACK_MELEE)
  1999. add = 2;
  2000. else if (attackType == SFG_MONSTER_ATTACK_EXPLODE)
  2001. add = 3;
  2002. if (state == SFG_MONSTER_STATE_GOING_E ||
  2003. state == SFG_MONSTER_STATE_GOING_NE ||
  2004. state == SFG_MONSTER_STATE_GOING_SE)
  2005. coordAdd[0] = add;
  2006. else if (state == SFG_MONSTER_STATE_GOING_W ||
  2007. state == SFG_MONSTER_STATE_GOING_SW ||
  2008. state == SFG_MONSTER_STATE_GOING_NW)
  2009. coordAdd[0] = -1 * add;
  2010. if (state == SFG_MONSTER_STATE_GOING_N ||
  2011. state == SFG_MONSTER_STATE_GOING_NE ||
  2012. state == SFG_MONSTER_STATE_GOING_NW)
  2013. coordAdd[1] = -1 * add;
  2014. else if (state == SFG_MONSTER_STATE_GOING_S ||
  2015. state == SFG_MONSTER_STATE_GOING_SE ||
  2016. state == SFG_MONSTER_STATE_GOING_SW)
  2017. coordAdd[1] = add;
  2018. if ((coordAdd[0] != 0 || coordAdd[1] != 0) && SFG_random() <
  2019. SFG_MONSTER_SOUND_PROBABILITY)
  2020. SFG_playGameSound(5,
  2021. SFG_distantSoundVolume(
  2022. SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[0]),
  2023. SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[1]),
  2024. currentHeight) / 2);
  2025. state = SFG_MONSTER_STATE_IDLE;
  2026. }
  2027. int16_t newPos[2];
  2028. newPos[0] = monster->coords[0] + coordAdd[0];
  2029. newPos[1] = monster->coords[1] + coordAdd[1];
  2030. int8_t collision = 0;
  2031. if (newPos[0] < 0 || newPos[0] >= 256 || newPos[1] < 0 || newPos[1] >= 256)
  2032. {
  2033. collision = 1;
  2034. }
  2035. else
  2036. {
  2037. uint8_t movingDiagonally = (coordAdd[0] != 0) && (coordAdd[1] != 0);
  2038. // when moving diagonally, we need to check extra tiles
  2039. for (uint8_t i = 0; i < (1 + movingDiagonally); ++i)
  2040. {
  2041. newPos[0] = monster->coords[0] + (i != 1) * coordAdd[0];
  2042. RCL_Unit newHeight =
  2043. SFG_floorCollisionHeightAt(
  2044. SFG_MONSTER_COORD_TO_SQUARES(newPos[0]),
  2045. SFG_MONSTER_COORD_TO_SQUARES(newPos[1]));
  2046. collision =
  2047. RCL_abs(currentHeight - newHeight) > RCL_CAMERA_COLL_STEP_HEIGHT;
  2048. if (!collision)
  2049. collision = (SFG_ceilingHeightAt(
  2050. SFG_MONSTER_COORD_TO_SQUARES(newPos[0]),
  2051. SFG_MONSTER_COORD_TO_SQUARES(newPos[1])) - newHeight) <
  2052. SFG_MONSTER_COLLISION_HEIGHT;
  2053. if (collision)
  2054. break;
  2055. }
  2056. newPos[0] = monster->coords[0] + coordAdd[0];
  2057. }
  2058. if (collision)
  2059. {
  2060. state = SFG_MONSTER_STATE_IDLE;
  2061. // ^ will force the monster to choose random direction in the next update
  2062. newPos[0] = monster->coords[0];
  2063. newPos[1] = monster->coords[1];
  2064. }
  2065. monster->stateType = state | (monsterNumber << 4);
  2066. monster->coords[0] = newPos[0];
  2067. monster->coords[1] = newPos[1];;
  2068. }
  2069. static inline uint8_t SFG_elementCollides(
  2070. RCL_Unit pointX,
  2071. RCL_Unit pointY,
  2072. RCL_Unit pointZ,
  2073. RCL_Unit elementX,
  2074. RCL_Unit elementY,
  2075. RCL_Unit elementHeight
  2076. )
  2077. {
  2078. return
  2079. SFG_taxicabDistance(pointX,pointY,pointZ,elementX,elementY,elementHeight)
  2080. <= SFG_ELEMENT_COLLISION_RADIUS;
  2081. }
  2082. /**
  2083. Checks collision of a projectile with level element at given position.
  2084. */
  2085. uint8_t SFG_projectileCollides(SFG_ProjectileRecord *projectile,
  2086. RCL_Unit x, RCL_Unit y, RCL_Unit z)
  2087. {
  2088. if (!SFG_elementCollides(x,y,z,
  2089. projectile->position[0],projectile->position[1],projectile->position[2]))
  2090. return 0;
  2091. if ((projectile->type == SFG_PROJECTILE_EXPLOSION) ||
  2092. (projectile->type == SFG_PROJECTILE_DUST))
  2093. return 0;
  2094. /* For directional projectiles we only register a collision if its direction
  2095. is "towards" the element so that the shooter doesn't get shot by his own
  2096. projectile. */
  2097. RCL_Vector2D projDir, toElement;
  2098. projDir.x = projectile->direction[0];
  2099. projDir.y = projectile->direction[1];
  2100. toElement.x = x - projectile->position[0];
  2101. toElement.y = y - projectile->position[1];
  2102. return RCL_vectorsAngleCos(projDir,toElement) >= 0;
  2103. }
  2104. /**
  2105. Updates a frame of the currently loaded level, i.e. enemies, projectiles,
  2106. animations etc., with the exception of player.
  2107. */
  2108. void SFG_updateLevel(void)
  2109. {
  2110. // update projectiles:
  2111. uint8_t subtractFrames =
  2112. ((SFG_game.frame - SFG_currentLevel.frameStart) & 0x01) ? 1 : 0;
  2113. /* ^ only subtract frames to live every other frame because a maximum of
  2114. 256 frames would be too few */
  2115. for (int8_t i = 0; i < SFG_currentLevel.projectileRecordCount; ++i)
  2116. { // ^ has to be signed
  2117. SFG_ProjectileRecord *p = &(SFG_currentLevel.projectileRecords[i]);
  2118. uint8_t attackType = 255;
  2119. if (p->type == SFG_PROJECTILE_BULLET)
  2120. attackType = SFG_WEAPON_FIRE_TYPE_BULLET;
  2121. else if (p->type == SFG_PROJECTILE_PLASMA)
  2122. attackType = SFG_WEAPON_FIRE_TYPE_PLASMA;
  2123. RCL_Unit pos[3] = {0,0,0}; /* we have to convert from uint16_t because of
  2124. under/overflows */
  2125. uint8_t eliminate = 0;
  2126. for (uint8_t j = 0; j < 3; ++j)
  2127. {
  2128. pos[j] = p->position[j];
  2129. pos[j] += p->direction[j];
  2130. if ( // projectile outside map?
  2131. (pos[j] < 0) ||
  2132. (pos[j] >= (SFG_MAP_SIZE * RCL_UNITS_PER_SQUARE)))
  2133. {
  2134. eliminate = 1;
  2135. break;
  2136. }
  2137. }
  2138. if (p->doubleFramesToLive == 0) // no more time to live?
  2139. {
  2140. eliminate = 1;
  2141. }
  2142. else if (
  2143. (p->type != SFG_PROJECTILE_EXPLOSION) &&
  2144. (p->type != SFG_PROJECTILE_DUST))
  2145. {
  2146. if (SFG_projectileCollides( // collides with player?
  2147. p,
  2148. SFG_player.camera.position.x,
  2149. SFG_player.camera.position.y,
  2150. SFG_player.camera.height))
  2151. {
  2152. eliminate = 1;
  2153. SFG_playerChangeHealth(-1 * SFG_getDamageValue(attackType));
  2154. }
  2155. /* Check collision with the map (we don't use SFG_floorCollisionHeightAt
  2156. because collisions with items have to be done differently for
  2157. projectiles). */
  2158. if (!eliminate &&
  2159. ((SFG_floorHeightAt(pos[0] / RCL_UNITS_PER_SQUARE,pos[1] /
  2160. RCL_UNITS_PER_SQUARE) >= pos[2])
  2161. ||
  2162. (SFG_ceilingHeightAt(pos[0] / RCL_UNITS_PER_SQUARE,pos[1] /
  2163. RCL_UNITS_PER_SQUARE) <= pos[2]))
  2164. )
  2165. eliminate = 1;
  2166. // check collision with active level elements
  2167. if (!eliminate) // monsters
  2168. for (uint16_t j = 0; j < SFG_currentLevel.monsterRecordCount; ++j)
  2169. {
  2170. SFG_MonsterRecord *m = &(SFG_currentLevel.monsterRecords[j]);
  2171. uint8_t state = SFG_MR_STATE(*m);
  2172. if ((state != SFG_MONSTER_STATE_INACTIVE) &&
  2173. (state != SFG_MONSTER_STATE_DEAD))
  2174. {
  2175. if (SFG_projectileCollides(p,
  2176. SFG_MONSTER_COORD_TO_RCL_UNITS(m->coords[0]),
  2177. SFG_MONSTER_COORD_TO_RCL_UNITS(m->coords[1]),
  2178. SFG_floorHeightAt(
  2179. SFG_MONSTER_COORD_TO_SQUARES(m->coords[0]),
  2180. SFG_MONSTER_COORD_TO_SQUARES(m->coords[1]))
  2181. ))
  2182. {
  2183. eliminate = 1;
  2184. SFG_monsterChangeHealth(m,-1 * SFG_getDamageValue(attackType));
  2185. break;
  2186. }
  2187. }
  2188. }
  2189. if (!eliminate) // items (can't check itemCollisionMap because of barrels)
  2190. for (uint16_t j = 0; j < SFG_currentLevel.itemRecordCount; ++j)
  2191. {
  2192. const SFG_LevelElement *e = SFG_getActiveItemElement(j);
  2193. if (e != 0 && SFG_itemCollides(e->type))
  2194. {
  2195. RCL_Unit x = SFG_ELEMENT_COORD_TO_RCL_UNITS(e->coords[0]);
  2196. RCL_Unit y = SFG_ELEMENT_COORD_TO_RCL_UNITS(e->coords[1]);
  2197. RCL_Unit z = SFG_floorHeightAt(e->coords[0],e->coords[1]);
  2198. if (SFG_projectileCollides(p,x,y,z))
  2199. {
  2200. if (
  2201. (e->type == SFG_LEVEL_ELEMENT_BARREL) &&
  2202. (SFG_getDamageValue(attackType) >=
  2203. SFG_BARREL_EXPLOSION_DAMAGE_THRESHOLD)
  2204. )
  2205. {
  2206. SFG_explodeBarrel(j,x,y,z);
  2207. }
  2208. eliminate = 1;
  2209. break;
  2210. }
  2211. }
  2212. }
  2213. }
  2214. if (eliminate)
  2215. {
  2216. if (p->type == SFG_PROJECTILE_FIREBALL)
  2217. SFG_createExplosion(p->position[0],p->position[1],p->position[2]);
  2218. else if (p->type == SFG_PROJECTILE_BULLET)
  2219. SFG_createDust(p->position[0],p->position[1],p->position[2]);
  2220. else if (p->type == SFG_PROJECTILE_PLASMA)
  2221. SFG_playGameSound(4,SFG_distantSoundVolume(pos[0],pos[1],pos[2]));
  2222. // remove the projectile
  2223. for (uint8_t j = i; j < SFG_currentLevel.projectileRecordCount - 1; ++j)
  2224. SFG_currentLevel.projectileRecords[j] =
  2225. SFG_currentLevel.projectileRecords[j + 1];
  2226. SFG_currentLevel.projectileRecordCount--;
  2227. i--;
  2228. }
  2229. else
  2230. {
  2231. p->position[0] = pos[0];
  2232. p->position[1] = pos[1];
  2233. p->position[2] = pos[2];
  2234. }
  2235. p->doubleFramesToLive -= subtractFrames;
  2236. }
  2237. // handle door:
  2238. if (SFG_currentLevel.doorRecordCount > 0) // has to be here
  2239. {
  2240. /* Check door on whether a player is standing nearby. For performance
  2241. reasons we only check a few doors and move to others in the next
  2242. frame. */
  2243. if (SFG_currentLevel.checkedDoorIndex == 0)
  2244. {
  2245. uint8_t count = SFG_player.cards >> 6;
  2246. SFG_player.cards = (count <= 1) ?
  2247. (SFG_player.cards & 0x07) :
  2248. ((SFG_player.cards & 0x7f) | ((count - 1) << 6));
  2249. }
  2250. for (uint16_t i = 0;
  2251. i < RCL_min(SFG_ELEMENT_DISTANCES_CHECKED_PER_FRAME,
  2252. SFG_currentLevel.doorRecordCount);
  2253. ++i)
  2254. {
  2255. SFG_DoorRecord *door =
  2256. &(SFG_currentLevel.doorRecords[SFG_currentLevel.checkedDoorIndex]);
  2257. uint8_t upDownState = door->state & SFG_DOOR_UP_DOWN_MASK;
  2258. uint8_t newUpDownState = 0;
  2259. uint8_t lock = SFG_DOOR_LOCK(door->state);
  2260. if ( // player near door?
  2261. (door->coords[0] >= (SFG_player.squarePosition[0] - 1)) &&
  2262. (door->coords[0] <= (SFG_player.squarePosition[0] + 1)) &&
  2263. (door->coords[1] >= (SFG_player.squarePosition[1] - 1)) &&
  2264. (door->coords[1] <= (SFG_player.squarePosition[1] + 1)))
  2265. {
  2266. if (lock == 0)
  2267. {
  2268. newUpDownState = SFG_DOOR_UP_DOWN_MASK;
  2269. }
  2270. else
  2271. {
  2272. lock = 1 << (lock - 1);
  2273. if (SFG_player.cards & lock) // player has the card?
  2274. newUpDownState = SFG_DOOR_UP_DOWN_MASK;
  2275. else
  2276. SFG_player.cards =
  2277. (SFG_player.cards & 0x07) | (lock << 3) | (2 << 6);
  2278. }
  2279. }
  2280. if (upDownState != newUpDownState)
  2281. SFG_playGameSound(1,255);
  2282. door->state = (door->state & ~SFG_DOOR_UP_DOWN_MASK) | newUpDownState;
  2283. SFG_currentLevel.checkedDoorIndex++;
  2284. if (SFG_currentLevel.checkedDoorIndex >= SFG_currentLevel.doorRecordCount)
  2285. SFG_currentLevel.checkedDoorIndex = 0;
  2286. }
  2287. // move door up/down:
  2288. for (uint32_t i = 0; i < SFG_currentLevel.doorRecordCount; ++i)
  2289. {
  2290. SFG_DoorRecord *door = &(SFG_currentLevel.doorRecords[i]);
  2291. int8_t height = door->state & SFG_DOOR_VERTICAL_POSITION_MASK;
  2292. height = (door->state & SFG_DOOR_UP_DOWN_MASK) ?
  2293. RCL_min(0x1f,height + SFG_DOOR_INCREMENT_PER_FRAME) :
  2294. RCL_max(0x00,height - SFG_DOOR_INCREMENT_PER_FRAME);
  2295. door->state = (door->state & ~SFG_DOOR_VERTICAL_POSITION_MASK) | height;
  2296. }
  2297. }
  2298. // handle items, in a similar manner to door:
  2299. if (SFG_currentLevel.itemRecordCount > 0) // has to be here
  2300. {
  2301. // check item distances:
  2302. for (uint16_t i = 0;
  2303. i < RCL_min(SFG_ELEMENT_DISTANCES_CHECKED_PER_FRAME,
  2304. SFG_currentLevel.itemRecordCount);
  2305. ++i)
  2306. {
  2307. SFG_ItemRecord item =
  2308. SFG_currentLevel.itemRecords[SFG_currentLevel.checkedItemIndex];
  2309. item &= ~SFG_ITEM_RECORD_ACTIVE_MASK;
  2310. SFG_LevelElement e =
  2311. SFG_currentLevel.levelPointer->elements[item];
  2312. if (
  2313. SFG_isInActiveDistanceFromPlayer(
  2314. e.coords[0] * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2,
  2315. e.coords[1] * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2,
  2316. SFG_floorHeightAt(e.coords[0],e.coords[1]) + RCL_UNITS_PER_SQUARE / 2)
  2317. )
  2318. item |= SFG_ITEM_RECORD_ACTIVE_MASK;
  2319. SFG_currentLevel.itemRecords[SFG_currentLevel.checkedItemIndex] = item;
  2320. SFG_currentLevel.checkedItemIndex++;
  2321. if (SFG_currentLevel.checkedItemIndex >= SFG_currentLevel.itemRecordCount)
  2322. SFG_currentLevel.checkedItemIndex = 0;
  2323. }
  2324. }
  2325. // similarly handle monsters:
  2326. if (SFG_currentLevel.monsterRecordCount > 0) // has to be here
  2327. {
  2328. // check monster distances:
  2329. for (uint16_t i = 0;
  2330. i < RCL_min(SFG_ELEMENT_DISTANCES_CHECKED_PER_FRAME,
  2331. SFG_currentLevel.monsterRecordCount);
  2332. ++i)
  2333. {
  2334. SFG_MonsterRecord *monster =
  2335. &(SFG_currentLevel.monsterRecords[SFG_currentLevel.checkedMonsterIndex]);
  2336. if ( // far away from the player?
  2337. !SFG_isInActiveDistanceFromPlayer(
  2338. SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[0]),
  2339. SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[1]),
  2340. SFG_floorHeightAt(
  2341. SFG_MONSTER_COORD_TO_SQUARES(monster->coords[0]),
  2342. SFG_MONSTER_COORD_TO_SQUARES(monster->coords[1]))
  2343. + RCL_UNITS_PER_SQUARE / 2
  2344. )
  2345. )
  2346. {
  2347. monster->stateType =
  2348. (monster->stateType & SFG_MONSTER_MASK_TYPE) |
  2349. SFG_MONSTER_STATE_INACTIVE;
  2350. }
  2351. else if (SFG_MR_STATE(*monster) == SFG_MONSTER_STATE_INACTIVE)
  2352. {
  2353. monster->stateType =
  2354. (monster->stateType & SFG_MONSTER_MASK_TYPE) |
  2355. (monster->health != 0 ?
  2356. SFG_MONSTER_STATE_IDLE : SFG_MONSTER_STATE_DEAD);
  2357. }
  2358. SFG_currentLevel.checkedMonsterIndex++;
  2359. if (SFG_currentLevel.checkedMonsterIndex >=
  2360. SFG_currentLevel.monsterRecordCount)
  2361. SFG_currentLevel.checkedMonsterIndex = 0;
  2362. }
  2363. }
  2364. // update AI and handle dead monsters:
  2365. if ((SFG_game.frame - SFG_currentLevel.frameStart) %
  2366. SFG_AI_UPDATE_FRAME_INTERVAL == 0)
  2367. {
  2368. for (uint16_t i = 0; i < SFG_currentLevel.monsterRecordCount; ++i)
  2369. {
  2370. SFG_MonsterRecord *monster = &(SFG_currentLevel.monsterRecords[i]);
  2371. uint8_t state = SFG_MR_STATE(*monster);
  2372. if ((state == SFG_MONSTER_STATE_INACTIVE) ||
  2373. (state == SFG_MONSTER_STATE_DEAD))
  2374. continue;
  2375. if (state == SFG_MONSTER_STATE_DYING)
  2376. {
  2377. monster->stateType =
  2378. (monster->stateType & 0xf0) | SFG_MONSTER_STATE_DEAD;
  2379. }
  2380. else if (monster->health == 0)
  2381. {
  2382. monster->stateType = (monster->stateType & SFG_MONSTER_MASK_TYPE) |
  2383. SFG_MONSTER_STATE_DYING;
  2384. if (SFG_MR_TYPE(*monster) == SFG_LEVEL_ELEMENT_MONSTER_ENDER)
  2385. {
  2386. SFG_currentLevel.bossCount--;
  2387. // last boss killed gives player a key card
  2388. if (SFG_currentLevel.bossCount == 0)
  2389. {
  2390. SFG_LOG("boss killed, giving player a card");
  2391. SFG_player.cards |= 0x04;
  2392. }
  2393. }
  2394. SFG_processEvent(SFG_EVENT_MONSTER_DIES,SFG_MR_TYPE(*monster));
  2395. if (SFG_MR_TYPE(*monster) == SFG_LEVEL_ELEMENT_MONSTER_EXPLODER)
  2396. SFG_createExplosion(
  2397. SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[0]),
  2398. SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[1]),
  2399. SFG_floorCollisionHeightAt(
  2400. SFG_MONSTER_COORD_TO_SQUARES(monster->coords[0]),
  2401. SFG_MONSTER_COORD_TO_SQUARES(monster->coords[0])) +
  2402. RCL_UNITS_PER_SQUARE / 2);
  2403. }
  2404. else
  2405. {
  2406. #if SFG_PREVIEW_MODE == 0
  2407. SFG_monsterPerformAI(monster);
  2408. #endif
  2409. }
  2410. }
  2411. }
  2412. }
  2413. /**
  2414. Maps square position on the map to a bit in map reveal mask.
  2415. */
  2416. static inline uint16_t SFG_getMapRevealBit(uint8_t squareX, uint8_t squareY)
  2417. {
  2418. return 1 << ((squareY / 16) * 4 + squareX / 16);
  2419. }
  2420. /**
  2421. Draws text on screen using the bitmap font stored in assets.
  2422. */
  2423. void SFG_drawText(
  2424. const char *text,
  2425. uint16_t x,
  2426. uint16_t y,
  2427. uint8_t size,
  2428. uint8_t color,
  2429. uint16_t maxLength,
  2430. uint16_t limitX)
  2431. {
  2432. if (size == 0)
  2433. size = 1;
  2434. if (limitX == 0)
  2435. limitX = 65535;
  2436. if (maxLength == 0)
  2437. maxLength = 65535;
  2438. uint16_t pos = 0;
  2439. uint16_t currentX = x;
  2440. uint16_t currentY = y;
  2441. while (pos < maxLength && text[pos] != 0) // for each character
  2442. {
  2443. uint16_t character =
  2444. //SFG_PROGRAM_MEMORY_U8(SFG_font + SFG_charToFontIndex(text[pos]));
  2445. SFG_font[SFG_charToFontIndex(text[pos])];
  2446. for (uint8_t i = 0; i < SFG_FONT_CHARACTER_SIZE; ++i) // for each line
  2447. {
  2448. currentY = y;
  2449. for (uint8_t j = 0; j < SFG_FONT_CHARACTER_SIZE; ++j) // for each row
  2450. {
  2451. if (character & 0x8000)
  2452. for (uint8_t k = 0; k < size; ++k)
  2453. for (uint8_t l = 0; l < size; ++l)
  2454. {
  2455. uint16_t drawX = currentX + k;
  2456. uint16_t drawY = currentY + l;
  2457. if (drawX < SFG_GAME_RESOLUTION_X &&
  2458. drawY < SFG_GAME_RESOLUTION_Y)
  2459. SFG_setGamePixel(drawX,drawY,color);
  2460. }
  2461. currentY += size;
  2462. character = character << 1;
  2463. }
  2464. currentX += size;
  2465. }
  2466. currentX += size; // space
  2467. if (currentX > limitX)
  2468. {
  2469. currentX = x;
  2470. y += (SFG_FONT_CHARACTER_SIZE + 1) * size;
  2471. }
  2472. pos++;
  2473. }
  2474. }
  2475. void SFG_drawLevelStartOverlay(void)
  2476. {
  2477. uint8_t stage = (SFG_game.stateTime * 4) / SFG_LEVEL_START_DURATION;
  2478. // fade in:
  2479. for (uint16_t y = 0; y < SFG_GAME_RESOLUTION_Y; ++y)
  2480. for (uint16_t x = 0; x < SFG_GAME_RESOLUTION_X; ++x)
  2481. {
  2482. uint8_t draw = 0;
  2483. switch (stage)
  2484. {
  2485. case 0: draw = 1; break;
  2486. case 1: draw = (x % 2) || (y % 2); break;
  2487. case 2: draw = (x % 2) == (y % 2); break;
  2488. case 3: draw = (x % 2) && (y % 2); break;
  2489. default: break;
  2490. }
  2491. if (draw)
  2492. SFG_setGamePixel(x,y,0);
  2493. }
  2494. if (SFG_game.saved == 1)
  2495. SFG_drawText(SFG_TEXT_SAVED,SFG_HUD_MARGIN,SFG_HUD_MARGIN,
  2496. SFG_FONT_SIZE_MEDIUM,7,255,0);
  2497. }
  2498. /**
  2499. Sets player's height to match the floor height below him.
  2500. */
  2501. void SFG_updatePlayerHeight(void)
  2502. {
  2503. SFG_player.camera.height =
  2504. SFG_floorCollisionHeightAt(
  2505. SFG_player.squarePosition[0],SFG_player.squarePosition[1]) +
  2506. RCL_CAMERA_COLL_HEIGHT_BELOW;
  2507. }
  2508. void SFG_winLevel(void)
  2509. {
  2510. SFG_levelEnds();
  2511. SFG_setGameState(SFG_GAME_STATE_WIN);
  2512. SFG_playGameSound(2,255);
  2513. SFG_processEvent(SFG_EVENT_VIBRATE,0);
  2514. SFG_processEvent(SFG_EVENT_LEVEL_WON,SFG_currentLevel.levelNumber + 1);
  2515. }
  2516. /**
  2517. Part of SFG_gameStep() for SFG_GAME_STATE_PLAYING.
  2518. */
  2519. void SFG_gameStepPlaying(void)
  2520. {
  2521. #if SFG_QUICK_WIN
  2522. if (SFG_game.stateTime > 500)
  2523. SFG_winLevel();
  2524. #endif
  2525. if (
  2526. (SFG_keyIsDown(SFG_KEY_C) && SFG_keyIsDown(SFG_KEY_DOWN)) ||
  2527. SFG_keyIsDown(SFG_KEY_MENU))
  2528. {
  2529. SFG_setGameState(SFG_GAME_STATE_MENU);
  2530. SFG_playGameSound(3,SFG_MENU_CLICK_VOLUME);
  2531. return;
  2532. }
  2533. SFG_updateLevel();
  2534. int8_t recomputeDirection = SFG_currentLevel.frameStart == SFG_game.frame;
  2535. RCL_Vector2D moveOffset;
  2536. moveOffset.x = 0;
  2537. moveOffset.y = 0;
  2538. int8_t strafe = 0;
  2539. uint8_t currentWeapon = SFG_player.weapon;
  2540. #if SFG_HEADBOB_ENABLED
  2541. int8_t bobbing = 0;
  2542. #endif
  2543. int8_t shearing = 0;
  2544. if (SFG_player.weaponCooldownFrames > 0)
  2545. SFG_player.weaponCooldownFrames--;
  2546. if (SFG_keyJustPressed(SFG_KEY_TOGGLE_FREELOOK))
  2547. SFG_game.settings = (SFG_game.settings & 0x04) ?
  2548. (SFG_game.settings & ~0x0c) : (SFG_game.settings | 0x0c);
  2549. int8_t canSwitchWeapon = SFG_player.weaponCooldownFrames == 0;
  2550. if (SFG_keyJustPressed(SFG_KEY_NEXT_WEAPON) && canSwitchWeapon)
  2551. SFG_playerRotateWeapon(1);
  2552. else if (SFG_keyJustPressed(SFG_KEY_PREVIOUS_WEAPON) && canSwitchWeapon)
  2553. SFG_playerRotateWeapon(0);
  2554. else if (SFG_keyJustPressed(SFG_KEY_CYCLE_WEAPON) &&
  2555. SFG_player.previousWeaponDirection)
  2556. SFG_playerRotateWeapon(SFG_player.previousWeaponDirection > 0);
  2557. uint8_t shearingOn = SFG_game.settings & 0x04;
  2558. if (SFG_keyIsDown(SFG_KEY_B))
  2559. {
  2560. if (shearingOn) // B + U/D: shearing (if on)
  2561. {
  2562. if (SFG_keyIsDown(SFG_KEY_UP))
  2563. {
  2564. SFG_player.camera.shear =
  2565. RCL_min(SFG_CAMERA_MAX_SHEAR_PIXELS,
  2566. SFG_player.camera.shear + SFG_CAMERA_SHEAR_STEP_PER_FRAME);
  2567. shearing = 1;
  2568. }
  2569. else if (SFG_keyIsDown(SFG_KEY_DOWN))
  2570. {
  2571. SFG_player.camera.shear =
  2572. RCL_max(-1 * SFG_CAMERA_MAX_SHEAR_PIXELS,
  2573. SFG_player.camera.shear - SFG_CAMERA_SHEAR_STEP_PER_FRAME);
  2574. shearing = 1;
  2575. }
  2576. }
  2577. if (!SFG_keyIsDown(SFG_KEY_C))
  2578. { // B + L/R: strafing
  2579. if (SFG_keyIsDown(SFG_KEY_LEFT))
  2580. strafe = -1;
  2581. else if (SFG_keyIsDown(SFG_KEY_RIGHT))
  2582. strafe = 1;
  2583. }
  2584. }
  2585. if (SFG_keyIsDown(SFG_KEY_C)) // C + A/B/L/R: weapon switching
  2586. {
  2587. if ((SFG_keyJustPressed(SFG_KEY_LEFT) || SFG_keyJustPressed(SFG_KEY_A)) &&
  2588. canSwitchWeapon)
  2589. SFG_playerRotateWeapon(0);
  2590. else if (
  2591. (SFG_keyJustPressed(SFG_KEY_RIGHT) || SFG_keyJustPressed(SFG_KEY_B)) &&
  2592. canSwitchWeapon)
  2593. SFG_playerRotateWeapon(1);
  2594. }
  2595. else if (!SFG_keyIsDown(SFG_KEY_B)) // L/R: turning
  2596. {
  2597. if (SFG_keyIsDown(SFG_KEY_LEFT))
  2598. {
  2599. SFG_player.camera.direction -= SFG_PLAYER_TURN_UNITS_PER_FRAME;
  2600. recomputeDirection = 1;
  2601. }
  2602. else if (SFG_keyIsDown(SFG_KEY_RIGHT))
  2603. {
  2604. SFG_player.camera.direction += SFG_PLAYER_TURN_UNITS_PER_FRAME;
  2605. recomputeDirection = 1;
  2606. }
  2607. }
  2608. if (!SFG_keyIsDown(SFG_KEY_B) || !shearingOn) // U/D: movement
  2609. {
  2610. if (SFG_keyIsDown(SFG_KEY_UP))
  2611. {
  2612. moveOffset.x += SFG_player.direction.x;
  2613. moveOffset.y += SFG_player.direction.y;
  2614. #if SFG_HEADBOB_ENABLED
  2615. bobbing = 1;
  2616. #endif
  2617. }
  2618. else if (SFG_keyIsDown(SFG_KEY_DOWN))
  2619. {
  2620. moveOffset.x -= SFG_player.direction.x;
  2621. moveOffset.y -= SFG_player.direction.y;
  2622. #if SFG_HEADBOB_ENABLED
  2623. bobbing = 1;
  2624. #endif
  2625. }
  2626. }
  2627. int16_t mouseX = 0, mouseY = 0;
  2628. SFG_getMouseOffset(&mouseX,&mouseY);
  2629. if (mouseX != 0) // mouse turning
  2630. {
  2631. SFG_player.camera.direction +=
  2632. (mouseX * SFG_MOUSE_SENSITIVITY_HORIZONTAL) / 128;
  2633. recomputeDirection = 1;
  2634. }
  2635. if ((mouseY != 0) && shearingOn) // mouse shearing
  2636. SFG_player.camera.shear =
  2637. RCL_max(RCL_min(
  2638. SFG_player.camera.shear -
  2639. (mouseY * SFG_MOUSE_SENSITIVITY_VERTICAL) / 128,
  2640. SFG_CAMERA_MAX_SHEAR_PIXELS),
  2641. -1 * SFG_CAMERA_MAX_SHEAR_PIXELS);
  2642. if (recomputeDirection)
  2643. SFG_recomputePLayerDirection();
  2644. if (SFG_keyIsDown(SFG_KEY_STRAFE_LEFT))
  2645. strafe = -1;
  2646. else if (SFG_keyIsDown(SFG_KEY_STRAFE_RIGHT))
  2647. strafe = 1;
  2648. if (strafe != 0)
  2649. {
  2650. uint8_t normalize = (moveOffset.x != 0) || (moveOffset.y != 0);
  2651. moveOffset.x += strafe * SFG_player.direction.y;
  2652. moveOffset.y -= strafe * SFG_player.direction.x;
  2653. if (normalize)
  2654. {
  2655. // This prevents reaching higher speed when moving diagonally.
  2656. moveOffset = RCL_normalize(moveOffset);
  2657. moveOffset.x = (moveOffset.x * SFG_PLAYER_MOVE_UNITS_PER_FRAME)
  2658. / RCL_UNITS_PER_SQUARE;
  2659. moveOffset.y = (moveOffset.y * SFG_PLAYER_MOVE_UNITS_PER_FRAME)
  2660. / RCL_UNITS_PER_SQUARE;
  2661. }
  2662. }
  2663. #if SFG_PREVIEW_MODE
  2664. if (SFG_keyIsDown(SFG_KEY_B))
  2665. SFG_player.verticalSpeed = SFG_PLAYER_MOVE_UNITS_PER_FRAME;
  2666. else if (SFG_keyIsDown(SFG_KEY_C))
  2667. SFG_player.verticalSpeed = -1 * SFG_PLAYER_MOVE_UNITS_PER_FRAME;
  2668. else
  2669. SFG_player.verticalSpeed = 0;
  2670. #else
  2671. RCL_Unit verticalOffset =
  2672. (
  2673. (
  2674. SFG_keyIsDown(SFG_KEY_JUMP) ||
  2675. (SFG_keyIsDown(SFG_KEY_UP) && SFG_keyIsDown(SFG_KEY_C))
  2676. ) &&
  2677. (SFG_player.verticalSpeed == 0) &&
  2678. (SFG_player.previousVerticalSpeed == 0)) ?
  2679. SFG_PLAYER_JUMP_OFFSET_PER_FRAME : // jump
  2680. (SFG_player.verticalSpeed - SFG_GRAVITY_SPEED_INCREASE_PER_FRAME);
  2681. #endif
  2682. if (!shearing && SFG_player.camera.shear != 0 && !(SFG_game.settings & 0x08))
  2683. {
  2684. // gradually shear back to zero
  2685. SFG_player.camera.shear =
  2686. (SFG_player.camera.shear > 0) ?
  2687. RCL_max(0,SFG_player.camera.shear - SFG_CAMERA_SHEAR_STEP_PER_FRAME) :
  2688. RCL_min(0,SFG_player.camera.shear + SFG_CAMERA_SHEAR_STEP_PER_FRAME);
  2689. }
  2690. #if SFG_HEADBOB_ENABLED && !SFG_PREVIEW_MODE
  2691. if (bobbing)
  2692. {
  2693. SFG_player.headBobFrame += SFG_HEADBOB_FRAME_INCREASE_PER_FRAME;
  2694. }
  2695. else if (SFG_player.headBobFrame != 0)
  2696. {
  2697. // smoothly stop bobbing
  2698. uint8_t quadrant = (SFG_player.headBobFrame % RCL_UNITS_PER_SQUARE) /
  2699. (RCL_UNITS_PER_SQUARE / 4);
  2700. /* When in quadrant in which sin is going away from zero, switch to the
  2701. same value of the next quadrant, so that bobbing starts to go towards
  2702. zero immediately. */
  2703. if (quadrant % 2 == 0)
  2704. SFG_player.headBobFrame =
  2705. ((quadrant + 1) * RCL_UNITS_PER_SQUARE / 4) +
  2706. (RCL_UNITS_PER_SQUARE / 4 - SFG_player.headBobFrame %
  2707. (RCL_UNITS_PER_SQUARE / 4));
  2708. RCL_Unit currentFrame = SFG_player.headBobFrame;
  2709. RCL_Unit nextFrame = SFG_player.headBobFrame + 16;
  2710. // only stop bobbing when we pass a frame at which sin crosses zero
  2711. SFG_player.headBobFrame =
  2712. (currentFrame / (RCL_UNITS_PER_SQUARE / 2) ==
  2713. nextFrame / (RCL_UNITS_PER_SQUARE / 2)) ?
  2714. nextFrame : 0;
  2715. }
  2716. #endif
  2717. RCL_Unit previousHeight = SFG_player.camera.height;
  2718. // handle player collision with level elements:
  2719. // monsters:
  2720. for (uint16_t i = 0; i < SFG_currentLevel.monsterRecordCount; ++i)
  2721. {
  2722. SFG_MonsterRecord *m = &(SFG_currentLevel.monsterRecords[i]);
  2723. uint8_t state = SFG_MR_STATE(*m);
  2724. if (state == SFG_MONSTER_STATE_INACTIVE || state == SFG_MONSTER_STATE_DEAD)
  2725. continue;
  2726. RCL_Vector2D mPos;
  2727. mPos.x = SFG_MONSTER_COORD_TO_RCL_UNITS(m->coords[0]);
  2728. mPos.y = SFG_MONSTER_COORD_TO_RCL_UNITS(m->coords[1]);
  2729. if (
  2730. SFG_elementCollides(
  2731. SFG_player.camera.position.x,
  2732. SFG_player.camera.position.y,
  2733. SFG_player.camera.height,
  2734. mPos.x,
  2735. mPos.y,
  2736. SFG_floorHeightAt(
  2737. SFG_MONSTER_COORD_TO_SQUARES(m->coords[0]),
  2738. SFG_MONSTER_COORD_TO_SQUARES(m->coords[1]))
  2739. )
  2740. )
  2741. {
  2742. moveOffset = SFG_resolveCollisionWithElement(
  2743. SFG_player.camera.position,moveOffset,mPos);
  2744. }
  2745. }
  2746. uint8_t collidesWithTeleporter = 0;
  2747. /* item collisions with player (only those that don't stop player's movement,
  2748. as those are handled differently, via itemCollisionMap): */
  2749. for (int16_t i = 0; i < SFG_currentLevel.itemRecordCount; ++i)
  2750. // ^ has to be int16_t (signed)
  2751. {
  2752. if (!(SFG_currentLevel.itemRecords[i] & SFG_ITEM_RECORD_ACTIVE_MASK))
  2753. continue;
  2754. const SFG_LevelElement *e = SFG_getActiveItemElement(i);
  2755. if (e != 0)
  2756. {
  2757. RCL_Vector2D ePos;
  2758. ePos.x = SFG_ELEMENT_COORD_TO_RCL_UNITS(e->coords[0]);
  2759. ePos.y = SFG_ELEMENT_COORD_TO_RCL_UNITS(e->coords[1]);
  2760. if (!SFG_itemCollides(e->type) &&
  2761. SFG_elementCollides(
  2762. SFG_player.camera.position.x,
  2763. SFG_player.camera.position.y,
  2764. SFG_player.camera.height,
  2765. ePos.x,
  2766. ePos.y,
  2767. SFG_floorHeightAt(e->coords[0],e->coords[1]))
  2768. )
  2769. {
  2770. uint8_t eliminate = 1;
  2771. uint8_t onlyKnife = 1;
  2772. for (uint8_t j = 0; j < SFG_AMMO_TOTAL; ++j)
  2773. if (SFG_player.ammo[j] != 0)
  2774. {
  2775. onlyKnife = 0;
  2776. break;
  2777. }
  2778. switch (e->type)
  2779. {
  2780. case SFG_LEVEL_ELEMENT_HEALTH:
  2781. if (SFG_player.health < SFG_PLAYER_MAX_HEALTH)
  2782. SFG_playerChangeHealth(SFG_HEALTH_KIT_VALUE);
  2783. else
  2784. eliminate = 0;
  2785. break;
  2786. #define addAmmo(type) \
  2787. if (SFG_player.ammo[SFG_AMMO_##type] < SFG_AMMO_MAX_##type) \
  2788. {\
  2789. SFG_player.ammo[SFG_AMMO_##type] = RCL_min(SFG_AMMO_MAX_##type,\
  2790. SFG_player.ammo[SFG_AMMO_##type] + SFG_AMMO_INCREASE_##type);\
  2791. if (onlyKnife) SFG_playerRotateWeapon(1); \
  2792. }\
  2793. else\
  2794. eliminate = 0;
  2795. case SFG_LEVEL_ELEMENT_BULLETS:
  2796. addAmmo(BULLETS)
  2797. break;
  2798. case SFG_LEVEL_ELEMENT_ROCKETS:
  2799. addAmmo(ROCKETS)
  2800. break;
  2801. case SFG_LEVEL_ELEMENT_PLASMA:
  2802. addAmmo(PLASMA)
  2803. break;
  2804. #undef addAmmo
  2805. case SFG_LEVEL_ELEMENT_CARD0:
  2806. case SFG_LEVEL_ELEMENT_CARD1:
  2807. case SFG_LEVEL_ELEMENT_CARD2:
  2808. SFG_player.cards |= 1 << (e->type - SFG_LEVEL_ELEMENT_CARD0);
  2809. break;
  2810. case SFG_LEVEL_ELEMENT_TELEPORTER:
  2811. collidesWithTeleporter = 1;
  2812. eliminate = 0;
  2813. break;
  2814. case SFG_LEVEL_ELEMENT_FINISH:
  2815. SFG_winLevel();
  2816. eliminate = 0;
  2817. break;
  2818. default:
  2819. eliminate = 0;
  2820. break;
  2821. }
  2822. if (eliminate) // take the item
  2823. {
  2824. #if !SFG_PREVIEW_MODE
  2825. SFG_removeItem(i);
  2826. SFG_player.lastItemTakenFrame = SFG_game.frame;
  2827. i--;
  2828. SFG_playGameSound(3,255);
  2829. SFG_processEvent(SFG_EVENT_PLAYER_TAKES_ITEM,e->type);
  2830. #endif
  2831. }
  2832. else if (
  2833. e->type == SFG_LEVEL_ELEMENT_TELEPORTER &&
  2834. SFG_currentLevel.teleporterCount > 1 &&
  2835. !SFG_player.justTeleported)
  2836. {
  2837. // teleport to random destination teleporter
  2838. uint8_t teleporterNumber =
  2839. SFG_random() % (SFG_currentLevel.teleporterCount - 1) + 1;
  2840. for (uint16_t j = 0; j < SFG_currentLevel.itemRecordCount; ++j)
  2841. {
  2842. SFG_LevelElement e2 =
  2843. SFG_currentLevel.levelPointer->elements
  2844. [SFG_currentLevel.itemRecords[j] &
  2845. ~SFG_ITEM_RECORD_ACTIVE_MASK];
  2846. if ((e2.type == SFG_LEVEL_ELEMENT_TELEPORTER) && (j != i))
  2847. teleporterNumber--;
  2848. if (teleporterNumber == 0)
  2849. {
  2850. SFG_player.camera.position.x =
  2851. SFG_ELEMENT_COORD_TO_RCL_UNITS(e2.coords[0]);
  2852. SFG_player.camera.position.y =
  2853. SFG_ELEMENT_COORD_TO_RCL_UNITS(e2.coords[1]);
  2854. SFG_player.camera.height =
  2855. SFG_floorHeightAt(e2.coords[0],e2.coords[1]) +
  2856. RCL_CAMERA_COLL_HEIGHT_BELOW;
  2857. SFG_currentLevel.itemRecords[j] |= SFG_ITEM_RECORD_ACTIVE_MASK;
  2858. /* ^ we have to make the new teleporter immediately active so
  2859. that it will immediately collide */
  2860. SFG_player.justTeleported = 1;
  2861. SFG_playGameSound(4,255);
  2862. SFG_processEvent(SFG_EVENT_PLAYER_TELEPORTS,0);
  2863. break;
  2864. } // if teleporterNumber == 0
  2865. } // for level items
  2866. } // if eliminate
  2867. } // if item collides
  2868. } // if element != 0
  2869. } // for, item collision check
  2870. if (!collidesWithTeleporter)
  2871. SFG_player.justTeleported = 0;
  2872. #if SFG_PREVIEW_MODE
  2873. SFG_player.camera.position.x +=
  2874. SFG_PREVIEW_MODE_SPEED_MULTIPLIER * moveOffset.x;
  2875. SFG_player.camera.position.y +=
  2876. SFG_PREVIEW_MODE_SPEED_MULTIPLIER * moveOffset.y;
  2877. SFG_player.camera.height +=
  2878. SFG_PREVIEW_MODE_SPEED_MULTIPLIER * SFG_player.verticalSpeed;
  2879. #else
  2880. RCL_moveCameraWithCollision(&(SFG_player.camera),moveOffset,
  2881. verticalOffset,SFG_floorCollisionHeightAt,SFG_ceilingHeightAt,1,1);
  2882. SFG_player.previousVerticalSpeed = SFG_player.verticalSpeed;
  2883. RCL_Unit limit = RCL_max(RCL_max(0,verticalOffset),SFG_player.verticalSpeed);
  2884. SFG_player.verticalSpeed =
  2885. RCL_min(limit,SFG_player.camera.height - previousHeight);
  2886. /* ^ By "limit" we assure height increase caused by climbing a step doesn't
  2887. add vertical velocity. */
  2888. #endif
  2889. #if SFG_PREVIEW_MODE == 0
  2890. if (
  2891. SFG_keyIsDown(SFG_KEY_A) &&
  2892. !SFG_keyIsDown(SFG_KEY_C) &&
  2893. (SFG_player.weaponCooldownFrames == 0) &&
  2894. (SFG_game.stateTime > 400) // don't immediately shoot if returning from menu
  2895. )
  2896. {
  2897. /* Player attack/shoot/fire, this has to be done AFTER the player is moved,
  2898. otherwise he could shoot himself while running forward. */
  2899. uint8_t ammo, projectileCount, canShoot;
  2900. SFG_getPlayerWeaponInfo(&ammo,&projectileCount,&canShoot);
  2901. if (canShoot)
  2902. {
  2903. uint8_t sound;
  2904. switch (SFG_player.weapon)
  2905. {
  2906. case SFG_WEAPON_KNIFE: sound = 255; break;
  2907. case SFG_WEAPON_ROCKET_LAUNCHER:
  2908. case SFG_WEAPON_SHOTGUN: sound = 2; break;
  2909. case SFG_WEAPON_PLASMAGUN:
  2910. case SFG_WEAPON_SOLUTION: sound = 4; break;
  2911. default: sound = 0; break;
  2912. }
  2913. if (sound != 255)
  2914. SFG_playGameSound(sound,255);
  2915. if (ammo != SFG_AMMO_NONE)
  2916. SFG_player.ammo[ammo] -= projectileCount;
  2917. uint8_t projectile;
  2918. switch (SFG_GET_WEAPON_FIRE_TYPE(SFG_player.weapon))
  2919. {
  2920. case SFG_WEAPON_FIRE_TYPE_PLASMA:
  2921. projectile = SFG_PROJECTILE_PLASMA;
  2922. break;
  2923. case SFG_WEAPON_FIRE_TYPE_FIREBALL:
  2924. projectile = SFG_PROJECTILE_FIREBALL;
  2925. break;
  2926. case SFG_WEAPON_FIRE_TYPE_BULLET:
  2927. projectile = SFG_PROJECTILE_BULLET;
  2928. break;
  2929. case SFG_WEAPON_FIRE_TYPE_MELEE:
  2930. projectile = SFG_PROJECTILE_NONE;
  2931. break;
  2932. default:
  2933. projectile = 255;
  2934. break;
  2935. }
  2936. if (projectile != SFG_PROJECTILE_NONE)
  2937. {
  2938. uint16_t angleAdd = SFG_PROJECTILE_SPREAD_ANGLE / (projectileCount + 1);
  2939. RCL_Unit direction =
  2940. (SFG_player.camera.direction - SFG_PROJECTILE_SPREAD_ANGLE / 2)
  2941. + angleAdd;
  2942. RCL_Unit projectileSpeed = SFG_GET_PROJECTILE_SPEED_UPF(projectile);
  2943. /* Vertical speed will be either determined by autoaim (if shearing is
  2944. off) or the camera shear value. */
  2945. RCL_Unit verticalSpeed = (SFG_game.settings & 0x04) ?
  2946. (SFG_player.camera.shear * projectileSpeed * 2) / // only approximate
  2947. SFG_CAMERA_MAX_SHEAR_PIXELS
  2948. :
  2949. (projectileSpeed * SFG_autoaimVertically()) / RCL_UNITS_PER_SQUARE;
  2950. for (uint8_t i = 0; i < projectileCount; ++i)
  2951. {
  2952. SFG_launchProjectile(
  2953. projectile,
  2954. SFG_player.camera.position,
  2955. SFG_player.camera.height,
  2956. RCL_angleToDirection(direction),
  2957. verticalSpeed,
  2958. SFG_PROJECTILE_SPAWN_OFFSET
  2959. );
  2960. direction += angleAdd;
  2961. }
  2962. }
  2963. else
  2964. {
  2965. // player's melee attack
  2966. for (uint16_t i = 0; i < SFG_currentLevel.monsterRecordCount; ++i)
  2967. {
  2968. SFG_MonsterRecord *m = &(SFG_currentLevel.monsterRecords[i]);
  2969. uint8_t state = SFG_MR_STATE(*m);
  2970. if ((state == SFG_MONSTER_STATE_INACTIVE) ||
  2971. (state == SFG_MONSTER_STATE_DEAD))
  2972. continue;
  2973. RCL_Unit pX, pY, pZ;
  2974. SFG_getMonsterWorldPosition(m,&pX,&pY,&pZ);
  2975. if (SFG_taxicabDistance(pX,pY,pZ,
  2976. SFG_player.camera.position.x,
  2977. SFG_player.camera.position.y,
  2978. SFG_player.camera.height) > SFG_MELEE_RANGE)
  2979. continue;
  2980. RCL_Vector2D toMonster;
  2981. toMonster.x = pX - SFG_player.camera.position.x;
  2982. toMonster.y = pY - SFG_player.camera.position.y;
  2983. if (RCL_vectorsAngleCos(SFG_player.direction,toMonster) >=
  2984. (RCL_UNITS_PER_SQUARE - SFG_PLAYER_MELEE_ANGLE))
  2985. {
  2986. SFG_monsterChangeHealth(m,
  2987. -1 * SFG_getDamageValue(SFG_WEAPON_FIRE_TYPE_MELEE));
  2988. SFG_createDust(pX,pY,pZ);
  2989. break;
  2990. }
  2991. }
  2992. }
  2993. SFG_player.weaponCooldownFrames =
  2994. RCL_max(
  2995. SFG_GET_WEAPON_FIRE_COOLDOWN_FRAMES(SFG_player.weapon),
  2996. SFG_MIN_WEAPON_COOLDOWN_FRAMES);
  2997. SFG_getPlayerWeaponInfo(&ammo,&projectileCount,&canShoot);
  2998. if (!canShoot)
  2999. {
  3000. // No more ammo, switch to the second strongest weapon.
  3001. SFG_playerRotateWeapon(1);
  3002. uint8_t previousWeapon = SFG_player.weapon;
  3003. SFG_playerRotateWeapon(0);
  3004. if (previousWeapon > SFG_player.weapon)
  3005. SFG_playerRotateWeapon(1);
  3006. }
  3007. } // endif: has enough ammo?
  3008. } // attack
  3009. #endif // SFG_PREVIEW_MODE == 0
  3010. SFG_player.squarePosition[0] =
  3011. SFG_player.camera.position.x / RCL_UNITS_PER_SQUARE;
  3012. SFG_player.squarePosition[1] =
  3013. SFG_player.camera.position.y / RCL_UNITS_PER_SQUARE;
  3014. SFG_currentLevel.mapRevealMask |=
  3015. SFG_getMapRevealBit(
  3016. SFG_player.squarePosition[0],
  3017. SFG_player.squarePosition[1]);
  3018. uint8_t properties;
  3019. SFG_getMapTile(SFG_currentLevel.levelPointer,SFG_player.squarePosition[0],
  3020. SFG_player.squarePosition[1],&properties);
  3021. if ( // squeezer check
  3022. (properties == SFG_TILE_PROPERTY_SQUEEZER) &&
  3023. ((SFG_ceilingHeightAt(
  3024. SFG_player.squarePosition[0],SFG_player.squarePosition[1]) -
  3025. SFG_floorHeightAt(
  3026. SFG_player.squarePosition[0],SFG_player.squarePosition[1]))
  3027. <
  3028. (RCL_CAMERA_COLL_HEIGHT_ABOVE + RCL_CAMERA_COLL_HEIGHT_BELOW)))
  3029. {
  3030. SFG_LOG("player is squeezed");
  3031. SFG_player.health = 0;
  3032. }
  3033. if (SFG_player.weapon != currentWeapon) // if weapon switched, start cooldown
  3034. {
  3035. if (SFG_player.weapon == (currentWeapon + 1) % SFG_WEAPONS_TOTAL)
  3036. SFG_player.previousWeaponDirection = -1;
  3037. else if (currentWeapon == (SFG_player.weapon + 1) % SFG_WEAPONS_TOTAL)
  3038. SFG_player.previousWeaponDirection = 1;
  3039. else
  3040. SFG_player.previousWeaponDirection = 0;
  3041. SFG_player.weaponCooldownFrames =
  3042. SFG_GET_WEAPON_FIRE_COOLDOWN_FRAMES(SFG_player.weapon) / 2;
  3043. }
  3044. #if SFG_IMMORTAL == 0
  3045. if (SFG_player.health == 0)
  3046. {
  3047. SFG_LOG("player dies");
  3048. SFG_levelEnds();
  3049. SFG_processEvent(SFG_EVENT_VIBRATE,0);
  3050. SFG_processEvent(SFG_EVENT_PLAYER_DIES,0);
  3051. SFG_setGameState(SFG_GAME_STATE_LOSE);
  3052. }
  3053. #endif
  3054. }
  3055. /**
  3056. This function defines which items are displayed in the menu.
  3057. */
  3058. uint8_t SFG_getMenuItem(uint8_t index)
  3059. {
  3060. uint8_t current = 0;
  3061. while (1) // find first legitimate item
  3062. {
  3063. if ( // skip non-legitimate items
  3064. ((current <= SFG_MENU_ITEM_MAP) && (SFG_currentLevel.levelPointer == 0))
  3065. || ((current == SFG_MENU_ITEM_LOAD) && ((SFG_game.save[0] >> 4) == 0)))
  3066. {
  3067. current++;
  3068. continue;
  3069. }
  3070. if (index == 0)
  3071. return (current <= (SFG_MENU_ITEM_EXIT - (SFG_CAN_EXIT ? 0 : 1))
  3072. ) ? current : SFG_MENU_ITEM_NONE;
  3073. current++;
  3074. index--;
  3075. }
  3076. return SFG_MENU_ITEM_NONE;
  3077. }
  3078. void SFG_handleCheats(void)
  3079. {
  3080. // this is a state machine handling cheat typing
  3081. uint8_t expectedKey;
  3082. switch (SFG_game.cheatState & 0x0f)
  3083. {
  3084. case 0: case 3: case 5: case 7: case 10:
  3085. expectedKey = SFG_KEY_A; break;
  3086. case 1: case 8:
  3087. expectedKey = SFG_KEY_B; break;
  3088. case 2: case 9:
  3089. expectedKey = SFG_KEY_RIGHT; break;
  3090. case 4:
  3091. expectedKey = SFG_KEY_C; break;
  3092. case 6: default:
  3093. expectedKey = SFG_KEY_DOWN; break;
  3094. }
  3095. for (uint8_t i = 0; i < SFG_KEY_COUNT; ++i) // no other keys must be pressed
  3096. if ((i != expectedKey) && SFG_keyJustPressed(i))
  3097. {
  3098. SFG_game.cheatState &= 0xf0; // back to start state
  3099. return;
  3100. }
  3101. if (!SFG_keyJustPressed(expectedKey))
  3102. return;
  3103. SFG_game.cheatState++; // go to next state
  3104. if ((SFG_game.cheatState & 0x0f) > 10) // final state resolved?
  3105. {
  3106. if (SFG_game.cheatState & 0x80)
  3107. {
  3108. SFG_LOG("cheat disabled");
  3109. SFG_game.cheatState = 0;
  3110. }
  3111. else
  3112. {
  3113. SFG_LOG("cheat activated");
  3114. SFG_playGameSound(4,255);
  3115. SFG_playerChangeHealth(SFG_PLAYER_MAX_HEALTH);
  3116. SFG_player.ammo[SFG_AMMO_BULLETS] = SFG_AMMO_MAX_BULLETS;
  3117. SFG_player.ammo[SFG_AMMO_ROCKETS] = SFG_AMMO_MAX_ROCKETS;
  3118. SFG_player.ammo[SFG_AMMO_PLASMA] = SFG_AMMO_MAX_PLASMA;
  3119. SFG_player.weapon = SFG_WEAPON_SOLUTION;
  3120. SFG_player.cards |= 0x07;
  3121. SFG_game.cheatState = 0x80;
  3122. }
  3123. }
  3124. }
  3125. void SFG_gameStepMenu(void)
  3126. {
  3127. uint8_t menuItems = 0;
  3128. while (SFG_getMenuItem(menuItems) != SFG_MENU_ITEM_NONE)
  3129. menuItems++;
  3130. uint8_t item = SFG_getMenuItem(SFG_game.selectedMenuItem);
  3131. if (SFG_keyRegisters(SFG_KEY_DOWN) &&
  3132. (SFG_game.selectedMenuItem < menuItems - 1))
  3133. {
  3134. SFG_game.selectedMenuItem++;
  3135. SFG_playGameSound(3,SFG_MENU_CLICK_VOLUME);
  3136. }
  3137. else if (SFG_keyRegisters(SFG_KEY_UP) && (SFG_game.selectedMenuItem > 0))
  3138. {
  3139. SFG_game.selectedMenuItem--;
  3140. SFG_playGameSound(3,SFG_MENU_CLICK_VOLUME);
  3141. }
  3142. else if (SFG_keyJustPressed(SFG_KEY_A))
  3143. {
  3144. switch (item)
  3145. {
  3146. case SFG_MENU_ITEM_PLAY:
  3147. for (uint8_t i = 6; i < SFG_SAVE_SIZE; ++i) // reset totals in save
  3148. SFG_game.save[i] = 0;
  3149. if (SFG_game.selectedLevel == 0)
  3150. {
  3151. SFG_currentLevel.levelNumber = 0; // to draw intro, not outro
  3152. SFG_setGameState(SFG_GAME_STATE_INTRO);
  3153. }
  3154. else
  3155. SFG_setAndInitLevel(SFG_game.selectedLevel);
  3156. break;
  3157. case SFG_MENU_ITEM_LOAD:
  3158. {
  3159. SFG_gameLoad();
  3160. uint8_t saveBackup[SFG_SAVE_SIZE];
  3161. for (uint8_t i = 0; i < SFG_SAVE_SIZE; ++i)
  3162. saveBackup[i] = SFG_game.save[i];
  3163. SFG_setAndInitLevel(SFG_game.save[0] >> 4);
  3164. for (uint8_t i = 0; i < SFG_SAVE_SIZE; ++i)
  3165. SFG_game.save[i] = saveBackup[i];
  3166. SFG_player.health = SFG_game.save[2];
  3167. SFG_player.ammo[0] = SFG_game.save[3];
  3168. SFG_player.ammo[1] = SFG_game.save[4];
  3169. SFG_player.ammo[2] = SFG_game.save[5];
  3170. SFG_playerRotateWeapon(1); // this chooses weapon with ammo available
  3171. break;
  3172. }
  3173. case SFG_MENU_ITEM_CONTINUE:
  3174. SFG_setGameState(SFG_GAME_STATE_PLAYING);
  3175. break;
  3176. case SFG_MENU_ITEM_MAP:
  3177. SFG_setGameState(SFG_GAME_STATE_MAP);
  3178. break;
  3179. case SFG_MENU_ITEM_SOUND:
  3180. SFG_LOG("sound changed");
  3181. SFG_game.settings =
  3182. (SFG_game.settings & ~0x03) | ((SFG_game.settings + 1) & 0x03);
  3183. SFG_playGameSound(3,SFG_MENU_CLICK_VOLUME);
  3184. if ((SFG_game.settings & 0x02) !=
  3185. ((SFG_game.settings - 1) & 0x02))
  3186. SFG_setMusic((SFG_game.settings & 0x02) ?
  3187. SFG_MUSIC_TURN_ON : SFG_MUSIC_TURN_OFF);
  3188. SFG_game.save[1] = SFG_game.settings;
  3189. SFG_gameSave();
  3190. break;
  3191. case SFG_MENU_ITEM_SHEAR:
  3192. {
  3193. uint8_t current = (SFG_game.settings >> 2) & 0x03;
  3194. current++;
  3195. if (current == 2) // option that doesn't make sense, skip
  3196. current++;
  3197. SFG_game.settings =
  3198. (SFG_game.settings & ~0x0c) | ((current & 0x03) << 2);
  3199. SFG_game.save[1] = SFG_game.settings;
  3200. SFG_gameSave();
  3201. break;
  3202. }
  3203. case SFG_MENU_ITEM_EXIT:
  3204. SFG_game.continues = 0;
  3205. break;
  3206. default:
  3207. break;
  3208. }
  3209. }
  3210. else if (item == SFG_MENU_ITEM_PLAY)
  3211. {
  3212. if (SFG_keyRegisters(SFG_KEY_RIGHT) &&
  3213. (SFG_game.selectedLevel < (SFG_game.save[0] & 0x0f)))
  3214. {
  3215. SFG_game.selectedLevel++;
  3216. SFG_playGameSound(3,SFG_MENU_CLICK_VOLUME);
  3217. }
  3218. else if (SFG_keyRegisters(SFG_KEY_LEFT) && SFG_game.selectedLevel > 0)
  3219. {
  3220. SFG_game.selectedLevel--;
  3221. SFG_playGameSound(3,SFG_MENU_CLICK_VOLUME);
  3222. }
  3223. }
  3224. }
  3225. /**
  3226. Performs one game step (logic, physics, menu, ...), happening SFG_MS_PER_FRAME
  3227. after the previous step.
  3228. */
  3229. void SFG_gameStep(void)
  3230. {
  3231. SFG_GAME_STEP_COMMAND
  3232. SFG_game.soundsPlayedThisFrame = 0;
  3233. SFG_game.blink = (SFG_game.frame / SFG_BLINK_PERIOD_FRAMES) % 2;
  3234. for (uint8_t i = 0; i < SFG_KEY_COUNT; ++i)
  3235. if (!SFG_keyPressed(i))
  3236. SFG_game.keyStates[i] = 0;
  3237. else if (SFG_game.keyStates[i] < 255)
  3238. SFG_game.keyStates[i]++;
  3239. if ((SFG_currentLevel.frameStart - SFG_game.frame) %
  3240. SFG_SPRITE_ANIMATION_FRAME_DURATION == 0)
  3241. SFG_game.spriteAnimationFrame++;
  3242. switch (SFG_game.state)
  3243. {
  3244. case SFG_GAME_STATE_PLAYING:
  3245. SFG_handleCheats();
  3246. SFG_gameStepPlaying();
  3247. break;
  3248. case SFG_GAME_STATE_MENU:
  3249. SFG_gameStepMenu();
  3250. break;
  3251. case SFG_GAME_STATE_LOSE:
  3252. {
  3253. // player die animation (lose)
  3254. SFG_updateLevel(); // let monsters and other things continue moving
  3255. SFG_updatePlayerHeight(); // in case player is on elevator
  3256. int32_t t = SFG_game.stateTime;
  3257. RCL_Unit h = SFG_floorHeightAt(SFG_player.squarePosition[0],
  3258. SFG_player.squarePosition[1]);
  3259. SFG_player.camera.height =
  3260. RCL_max(h,h + ((SFG_LOSE_ANIMATION_DURATION - t) *
  3261. RCL_CAMERA_COLL_HEIGHT_BELOW) / SFG_LOSE_ANIMATION_DURATION);
  3262. SFG_player.camera.shear =
  3263. RCL_min(SFG_CAMERA_MAX_SHEAR_PIXELS / 4,
  3264. (t * (SFG_CAMERA_MAX_SHEAR_PIXELS / 4)) / SFG_LOSE_ANIMATION_DURATION);
  3265. if (t > SFG_LOSE_ANIMATION_DURATION &&
  3266. (SFG_keyIsDown(SFG_KEY_A) || SFG_keyIsDown(SFG_KEY_B)))
  3267. {
  3268. for (uint8_t i = 6; i < SFG_SAVE_SIZE; ++i)
  3269. SFG_game.save[i] = 0;
  3270. SFG_setAndInitLevel(SFG_currentLevel.levelNumber);
  3271. }
  3272. break;
  3273. }
  3274. case SFG_GAME_STATE_WIN:
  3275. {
  3276. // win animation
  3277. SFG_updateLevel();
  3278. int32_t t = SFG_game.stateTime;
  3279. if (t > SFG_WIN_ANIMATION_DURATION)
  3280. {
  3281. if (SFG_currentLevel.levelNumber == (SFG_NUMBER_OF_LEVELS - 1))
  3282. {
  3283. if (SFG_keyIsDown(SFG_KEY_A))
  3284. {
  3285. SFG_setGameState(SFG_GAME_STATE_OUTRO);
  3286. SFG_setMusic(SFG_MUSIC_TURN_OFF);
  3287. }
  3288. }
  3289. else if (SFG_keyIsDown(SFG_KEY_RIGHT) ||
  3290. SFG_keyIsDown(SFG_KEY_LEFT) ||
  3291. SFG_keyIsDown(SFG_KEY_STRAFE_LEFT) ||
  3292. SFG_keyIsDown(SFG_KEY_STRAFE_RIGHT))
  3293. {
  3294. SFG_setAndInitLevel(SFG_currentLevel.levelNumber + 1);
  3295. SFG_player.health = SFG_game.save[2];
  3296. SFG_player.ammo[0] = SFG_game.save[3];
  3297. SFG_player.ammo[1] = SFG_game.save[4];
  3298. SFG_player.ammo[2] = SFG_game.save[5];
  3299. SFG_playerRotateWeapon(1);
  3300. if (SFG_keyIsDown(SFG_KEY_RIGHT) || SFG_keyIsDown(SFG_KEY_STRAFE_RIGHT))
  3301. {
  3302. // save the current position
  3303. SFG_game.save[0] =
  3304. (SFG_game.save[0] & 0x0f) | (SFG_currentLevel.levelNumber << 4);
  3305. SFG_gameSave();
  3306. SFG_game.saved = 1;
  3307. }
  3308. }
  3309. }
  3310. break;
  3311. }
  3312. case SFG_GAME_STATE_MAP:
  3313. if (SFG_keyIsDown(SFG_KEY_B))
  3314. SFG_setGameState(SFG_GAME_STATE_MENU);
  3315. break;
  3316. case SFG_GAME_STATE_INTRO:
  3317. if (SFG_keyJustPressed(SFG_KEY_A) || SFG_keyJustPressed(SFG_KEY_B))
  3318. SFG_setAndInitLevel(0);
  3319. break;
  3320. case SFG_GAME_STATE_OUTRO:
  3321. if ((SFG_game.stateTime > SFG_STORYTEXT_DURATION) &&
  3322. (SFG_keyIsDown(SFG_KEY_A) ||
  3323. SFG_keyIsDown(SFG_KEY_B)))
  3324. {
  3325. SFG_currentLevel.levelPointer = 0;
  3326. SFG_currentLevel.levelNumber = 0;
  3327. SFG_setGameState(SFG_GAME_STATE_MENU);
  3328. SFG_playGameSound(3,SFG_MENU_CLICK_VOLUME);
  3329. SFG_setMusic(SFG_MUSIC_TURN_ON);
  3330. }
  3331. break;
  3332. case SFG_GAME_STATE_LEVEL_START:
  3333. {
  3334. SFG_updateLevel();
  3335. SFG_updatePlayerHeight(); // in case player is on elevator
  3336. int16_t x = 0, y = 0;
  3337. SFG_getMouseOffset(&x,&y); // this keeps centering the mouse
  3338. if (SFG_game.stateTime >= SFG_LEVEL_START_DURATION)
  3339. SFG_setGameState(SFG_GAME_STATE_PLAYING);
  3340. break;
  3341. }
  3342. default:
  3343. break;
  3344. }
  3345. SFG_game.stateTime += SFG_MS_PER_FRAME;
  3346. }
  3347. void SFG_fillRectangle(
  3348. uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t color)
  3349. {
  3350. if ((x + width > SFG_GAME_RESOLUTION_X) ||
  3351. (y + height > SFG_GAME_RESOLUTION_Y))
  3352. return;
  3353. for (uint16_t j = y; j < y + height; ++j)
  3354. for (uint16_t i = x; i < x + width; ++i)
  3355. SFG_setGamePixel(i,j,color);
  3356. }
  3357. static inline void SFG_clearScreen(uint8_t color)
  3358. {
  3359. SFG_fillRectangle(0,0,SFG_GAME_RESOLUTION_X,
  3360. SFG_GAME_RESOLUTION_Y,color);
  3361. }
  3362. /**
  3363. Draws fullscreen map of the current level.
  3364. */
  3365. void SFG_drawMap(void)
  3366. {
  3367. SFG_clearScreen(0);
  3368. uint16_t maxJ =
  3369. (SFG_MAP_PIXEL_SIZE * SFG_MAP_SIZE) < SFG_GAME_RESOLUTION_Y ?
  3370. (SFG_MAP_SIZE) : (SFG_GAME_RESOLUTION_Y / SFG_MAP_PIXEL_SIZE);
  3371. uint16_t maxI =
  3372. (SFG_MAP_PIXEL_SIZE * SFG_MAP_SIZE) < SFG_GAME_RESOLUTION_X ?
  3373. (SFG_MAP_SIZE) : (SFG_GAME_RESOLUTION_X / SFG_MAP_PIXEL_SIZE);
  3374. uint16_t topLeftX =
  3375. (SFG_GAME_RESOLUTION_X - (maxI * SFG_MAP_PIXEL_SIZE)) / 2;
  3376. uint16_t topLeftY =
  3377. (SFG_GAME_RESOLUTION_Y - (maxJ * SFG_MAP_PIXEL_SIZE)) / 2;
  3378. uint16_t x;
  3379. uint16_t y = topLeftY;
  3380. uint8_t playerColor =
  3381. SFG_game.blink ? SFG_MAP_PLAYER_COLOR1 : SFG_MAP_PLAYER_COLOR2;
  3382. for (int16_t j = 0; j < maxJ; ++j)
  3383. {
  3384. x = topLeftX;
  3385. for (int16_t i = maxI - 1; i >= 0; --i)
  3386. {
  3387. uint8_t color = 0; // init with non-revealed color
  3388. if (SFG_currentLevel.mapRevealMask & SFG_getMapRevealBit(i,j))
  3389. {
  3390. uint8_t properties;
  3391. SFG_TileDefinition tile =
  3392. SFG_getMapTile(SFG_currentLevel.levelPointer,i,j,&properties);
  3393. color = playerColor; // start with player color
  3394. if (i != SFG_player.squarePosition[0] ||
  3395. j != SFG_player.squarePosition[1])
  3396. {
  3397. if (properties == SFG_TILE_PROPERTY_ELEVATOR)
  3398. color = SFG_MAP_ELEVATOR_COLOR;
  3399. else if (properties == SFG_TILE_PROPERTY_SQUEEZER)
  3400. color = SFG_MAP_SQUEEZER_COLOR;
  3401. else if (properties == SFG_TILE_PROPERTY_DOOR)
  3402. color = SFG_MAP_DOOR_COLOR;
  3403. else
  3404. {
  3405. color = 0;
  3406. uint8_t c = SFG_TILE_CEILING_HEIGHT(tile) / 4;
  3407. if (c != 0)
  3408. color = (SFG_TILE_FLOOR_HEIGHT(tile) % 8 + 3) * 8 + c - 1;
  3409. }
  3410. }
  3411. }
  3412. for (int_fast16_t k = 0; k < SFG_MAP_PIXEL_SIZE; ++k)
  3413. for (int_fast16_t l = 0; l < SFG_MAP_PIXEL_SIZE; ++l)
  3414. SFG_setGamePixel(x + l, y + k,color);
  3415. x += SFG_MAP_PIXEL_SIZE;
  3416. }
  3417. y += SFG_MAP_PIXEL_SIZE;
  3418. }
  3419. }
  3420. /**
  3421. Draws fullscreen story text (intro/outro).
  3422. */
  3423. void SFG_drawStoryText(void)
  3424. {
  3425. const char *text = SFG_outroText;
  3426. uint16_t textColor = 23;
  3427. uint8_t clearColor = 9;
  3428. uint8_t sprite = 18;
  3429. if (SFG_currentLevel.levelNumber != (SFG_NUMBER_OF_LEVELS - 1)) // intro?
  3430. {
  3431. text = SFG_introText;
  3432. textColor = 7;
  3433. clearColor = 0;
  3434. sprite = SFG_game.blink * 2;
  3435. }
  3436. SFG_clearScreen(clearColor);
  3437. if (SFG_GAME_RESOLUTION_Y > 50)
  3438. SFG_blitImage(SFG_monsterSprites + sprite * SFG_TEXTURE_STORE_SIZE,
  3439. (SFG_GAME_RESOLUTION_X - SFG_TEXTURE_SIZE * SFG_FONT_SIZE_SMALL) / 2,
  3440. SFG_GAME_RESOLUTION_Y - (SFG_TEXTURE_SIZE + 3) * SFG_FONT_SIZE_SMALL,
  3441. SFG_FONT_SIZE_SMALL);
  3442. uint16_t textLen = 0;
  3443. while (text[textLen] != 0)
  3444. textLen++;
  3445. uint16_t drawLen = RCL_min(
  3446. textLen,(SFG_game.stateTime * textLen) / SFG_STORYTEXT_DURATION + 1);
  3447. #define CHAR_SIZE (SFG_FONT_SIZE_SMALL * (SFG_FONT_CHARACTER_SIZE + 1))
  3448. #define LINE_LENGTH (SFG_GAME_RESOLUTION_X / CHAR_SIZE)
  3449. #define MAX_LENGTH (((SFG_GAME_RESOLUTION_Y / CHAR_SIZE) / 2) * LINE_LENGTH )
  3450. uint16_t drawShift = (drawLen < MAX_LENGTH) ? 0 :
  3451. (((drawLen - MAX_LENGTH) / LINE_LENGTH) * LINE_LENGTH);
  3452. #undef CHAR_SIZE
  3453. #undef LINE_LENGTH
  3454. #undef MAX_LENGTH
  3455. text += drawShift;
  3456. drawLen -= drawShift;
  3457. SFG_drawText(text,SFG_HUD_MARGIN,SFG_HUD_MARGIN,SFG_FONT_SIZE_SMALL,textColor,
  3458. drawLen,SFG_GAME_RESOLUTION_X - SFG_HUD_MARGIN);
  3459. }
  3460. /**
  3461. Draws a number as text on screen, returns the number of characters drawn.
  3462. */
  3463. uint8_t SFG_drawNumber(
  3464. int16_t number,
  3465. uint16_t x,
  3466. uint16_t y,
  3467. uint8_t size,
  3468. uint8_t color)
  3469. {
  3470. char text[7];
  3471. text[6] = 0; // terminate the string
  3472. int8_t positive = 1;
  3473. if (number < 0)
  3474. {
  3475. positive = 0;
  3476. number *= -1;
  3477. }
  3478. int8_t position = 5;
  3479. while (1)
  3480. {
  3481. text[position] = '0' + number % 10;
  3482. number /= 10;
  3483. position--;
  3484. if (number == 0 || position == 0)
  3485. break;
  3486. }
  3487. if (!positive)
  3488. {
  3489. text[position] = '-';
  3490. position--;
  3491. }
  3492. SFG_drawText(text + position + 1,x,y,size,color,0,0);
  3493. return 5 - position;
  3494. }
  3495. /**
  3496. Draws a screen border that indicates something is happening, e.g. being hurt
  3497. or taking an item.
  3498. */
  3499. void SFG_drawIndicationBorder(uint16_t width, uint8_t color)
  3500. {
  3501. for (int_fast16_t j = 0; j < width; ++j)
  3502. {
  3503. uint16_t j2 = SFG_GAME_RESOLUTION_Y - 1 - j;
  3504. for (int_fast16_t i = 0; i < SFG_GAME_RESOLUTION_X; ++i)
  3505. {
  3506. if ((i & 0x01) == (j & 0x01))
  3507. {
  3508. SFG_setGamePixel(i,j,color);
  3509. SFG_setGamePixel(i,j2,color);
  3510. }
  3511. }
  3512. }
  3513. for (int_fast16_t i = 0; i < width; ++i)
  3514. {
  3515. uint16_t i2 = SFG_GAME_RESOLUTION_X - 1 - i;
  3516. for (int_fast16_t j = width; j < SFG_GAME_RESOLUTION_Y - width; ++j)
  3517. {
  3518. if ((i & 0x01) == (j & 0x01))
  3519. {
  3520. SFG_setGamePixel(i,j,color);
  3521. SFG_setGamePixel(i2,j,color);
  3522. }
  3523. }
  3524. }
  3525. }
  3526. /**
  3527. Draws the player weapon, includes handling the shoot animation.
  3528. */
  3529. void SFG_drawWeapon(int16_t bobOffset)
  3530. {
  3531. uint32_t animationLength =
  3532. RCL_max(SFG_MIN_WEAPON_COOLDOWN_FRAMES,
  3533. SFG_GET_WEAPON_FIRE_COOLDOWN_FRAMES(SFG_player.weapon));
  3534. uint32_t shotAnimationFrame =
  3535. animationLength - SFG_player.weaponCooldownFrames;
  3536. bobOffset -= SFG_HUD_BAR_HEIGHT;
  3537. uint8_t fireType = SFG_GET_WEAPON_FIRE_TYPE(SFG_player.weapon);
  3538. if (shotAnimationFrame < animationLength)
  3539. {
  3540. if (fireType == SFG_WEAPON_FIRE_TYPE_MELEE)
  3541. {
  3542. bobOffset = shotAnimationFrame < animationLength / 2 ? 0 :
  3543. 2 * SFG_WEAPONBOB_OFFSET_PIXELS;
  3544. }
  3545. else
  3546. {
  3547. bobOffset +=
  3548. ((animationLength - shotAnimationFrame) * SFG_WEAPON_IMAGE_SCALE * 20)
  3549. / animationLength;
  3550. if (
  3551. ((fireType == SFG_WEAPON_FIRE_TYPE_FIREBALL) ||
  3552. (fireType == SFG_WEAPON_FIRE_TYPE_BULLET)) &&
  3553. shotAnimationFrame < animationLength / 2)
  3554. SFG_blitImage(SFG_effectSprites,
  3555. SFG_WEAPON_IMAGE_POSITION_X,
  3556. SFG_WEAPON_IMAGE_POSITION_Y -
  3557. (SFG_TEXTURE_SIZE / 3) * SFG_WEAPON_IMAGE_SCALE + bobOffset,
  3558. SFG_WEAPON_IMAGE_SCALE);
  3559. }
  3560. }
  3561. SFG_blitImage(SFG_weaponImages + SFG_player.weapon * SFG_TEXTURE_STORE_SIZE,
  3562. SFG_WEAPON_IMAGE_POSITION_X,
  3563. SFG_WEAPON_IMAGE_POSITION_Y + bobOffset - 1,
  3564. SFG_WEAPON_IMAGE_SCALE);
  3565. }
  3566. uint16_t SFG_textLen(const char *text)
  3567. {
  3568. uint16_t result = 0;
  3569. while (text[result] != 0)
  3570. result++;
  3571. return result;
  3572. }
  3573. static inline uint16_t SFG_characterSize(uint8_t textSize)
  3574. {
  3575. return (SFG_FONT_CHARACTER_SIZE + 1) * textSize;
  3576. }
  3577. static inline uint16_t
  3578. SFG_textHorizontalSize(const char *text, uint8_t textSize)
  3579. {
  3580. return (SFG_textLen(text) * SFG_characterSize(textSize));
  3581. }
  3582. void SFG_drawMenu(void)
  3583. {
  3584. #define BACKGROUND_SCALE (SFG_GAME_RESOLUTION_X / (4 * SFG_TEXTURE_SIZE))
  3585. #if BACKGROUND_SCALE == 0
  3586. #undef BACKGROUND_SCALE
  3587. #define BACKGROUND_SCALE 1
  3588. #endif
  3589. #define SCROLL_PIXELS_PER_FRAME ((64 * SFG_GAME_RESOLUTION_X) / (8 * SFG_FPS))
  3590. #if SCROLL_PIXELS_PER_FRAME == 0
  3591. #undef SCROLL_PIXELS_PER_FRAME
  3592. #define SCROLL_PIXELS_PER_FRAME 1
  3593. #endif
  3594. #define SELECTION_START_X ((SFG_GAME_RESOLUTION_X - 12 * SFG_FONT_SIZE_MEDIUM\
  3595. * (SFG_FONT_CHARACTER_SIZE + 1)) / 2)
  3596. uint16_t scroll = (SFG_game.frame * SCROLL_PIXELS_PER_FRAME) / 64;
  3597. for (uint16_t y = 0; y < SFG_GAME_RESOLUTION_Y; ++y)
  3598. for (uint16_t x = 0; x < SFG_GAME_RESOLUTION_X; ++x)
  3599. SFG_setGamePixel(x,y,
  3600. (y >= (SFG_TEXTURE_SIZE * BACKGROUND_SCALE)) ? 0 :
  3601. SFG_getTexel(SFG_backgroundImages,((x + scroll) / BACKGROUND_SCALE)
  3602. % SFG_TEXTURE_SIZE,y / BACKGROUND_SCALE));
  3603. uint16_t y = SFG_characterSize(SFG_FONT_SIZE_MEDIUM);
  3604. SFG_blitImage(SFG_logoImage,SFG_GAME_RESOLUTION_X / 2 -
  3605. (SFG_TEXTURE_SIZE / 2) * SFG_FONT_SIZE_SMALL,y,SFG_FONT_SIZE_SMALL);
  3606. #if SFG_GAME_RESOLUTION_Y > 50
  3607. y += 32 * SFG_FONT_SIZE_MEDIUM + SFG_characterSize(SFG_FONT_SIZE_MEDIUM);
  3608. #else
  3609. y = 2;
  3610. #endif
  3611. uint8_t i = 0;
  3612. while (1) // draw menu items
  3613. {
  3614. uint8_t item = SFG_getMenuItem(i);
  3615. if (item == SFG_MENU_ITEM_NONE)
  3616. break;
  3617. #if (SFG_GAME_RESOLUTION_Y < 70) || SFG_FORCE_SINGLE_ITEM_MENU
  3618. // with low resolution only display the selected item
  3619. if (i != SFG_game.selectedMenuItem)
  3620. {
  3621. i++;
  3622. continue;
  3623. }
  3624. #endif
  3625. const char *text = SFG_menuItemTexts[item];
  3626. uint8_t textLen = SFG_textLen(text);
  3627. uint16_t drawX = (SFG_GAME_RESOLUTION_X -
  3628. SFG_textHorizontalSize(text,SFG_FONT_SIZE_MEDIUM)) / 2;
  3629. uint8_t textColor = 7;
  3630. if (i != SFG_game.selectedMenuItem)
  3631. textColor = 23;
  3632. else
  3633. SFG_fillRectangle( // menu item highlight
  3634. SELECTION_START_X,
  3635. y - SFG_FONT_SIZE_MEDIUM,
  3636. SFG_GAME_RESOLUTION_X - SELECTION_START_X * 2,
  3637. SFG_characterSize(SFG_FONT_SIZE_MEDIUM),2);
  3638. SFG_drawText(text,drawX,y,SFG_FONT_SIZE_MEDIUM,textColor,0,0);
  3639. if ((item == SFG_MENU_ITEM_PLAY || item == SFG_MENU_ITEM_SOUND
  3640. || item == SFG_MENU_ITEM_SHEAR) &&
  3641. ((i != SFG_game.selectedMenuItem) || SFG_game.blink))
  3642. {
  3643. uint32_t x =
  3644. drawX + SFG_characterSize(SFG_FONT_SIZE_MEDIUM) * (textLen + 1);
  3645. uint8_t c = 93;
  3646. if (item == SFG_MENU_ITEM_PLAY)
  3647. SFG_drawNumber(SFG_game.selectedLevel + 1,x,y,SFG_FONT_SIZE_MEDIUM,c);
  3648. else if (item == SFG_MENU_ITEM_SHEAR)
  3649. {
  3650. uint8_t n = (SFG_game.settings >> 2) & 0x03;
  3651. SFG_drawNumber(n == 3 ? 2 : n,x,y,SFG_FONT_SIZE_MEDIUM,c);
  3652. }
  3653. else
  3654. {
  3655. char settingText[3] = " ";
  3656. settingText[0] = (SFG_game.settings & 0x01) ? 'S' : ' ';
  3657. settingText[1] = (SFG_game.settings & 0x02) ? 'M' : ' ';
  3658. SFG_drawText(settingText,x,y,SFG_FONT_SIZE_MEDIUM,c,0,0);
  3659. }
  3660. }
  3661. y += SFG_characterSize(SFG_FONT_SIZE_MEDIUM) + SFG_FONT_SIZE_MEDIUM;
  3662. i++;
  3663. }
  3664. SFG_drawText(SFG_VERSION_STRING " CC0",SFG_HUD_MARGIN,SFG_GAME_RESOLUTION_Y -
  3665. SFG_HUD_MARGIN - SFG_FONT_SIZE_SMALL * SFG_FONT_CHARACTER_SIZE,
  3666. SFG_FONT_SIZE_SMALL,4,0,0);
  3667. #if SFG_OS_IS_MALWARE
  3668. if (SFG_game.blink)
  3669. SFG_drawText(SFG_MALWARE_WARNING,SFG_HUD_MARGIN,SFG_HUD_MARGIN,
  3670. SFG_FONT_SIZE_MEDIUM,95,0,0);
  3671. #endif
  3672. #undef MAX_ITEMS
  3673. #undef BACKGROUND_SCALE
  3674. #undef SCROLL_PIXELS_PER_FRAME
  3675. }
  3676. void SFG_drawWinOverlay(void)
  3677. {
  3678. uint32_t t = RCL_min(SFG_WIN_ANIMATION_DURATION,SFG_game.stateTime);
  3679. uint32_t t2 = RCL_min(t,SFG_WIN_ANIMATION_DURATION / 4);
  3680. #define STRIP_HEIGHT (SFG_GAME_RESOLUTION_Y / 2)
  3681. #define INNER_STRIP_HEIGHT ((STRIP_HEIGHT * 3) / 4)
  3682. #define STRIP_START ((SFG_GAME_RESOLUTION_Y - STRIP_HEIGHT) / 2)
  3683. RCL_Unit l = (t2 * STRIP_HEIGHT * 4) / SFG_WIN_ANIMATION_DURATION;
  3684. for (uint16_t y = STRIP_START; y < STRIP_START + l; ++y)
  3685. for (uint16_t x = 0; x < SFG_GAME_RESOLUTION_X; ++x)
  3686. SFG_setGamePixel(x,y,
  3687. RCL_abs(y - (SFG_GAME_RESOLUTION_Y / 2)) <= (INNER_STRIP_HEIGHT / 2) ?
  3688. 0 : 172);
  3689. char textLine[] = SFG_TEXT_LEVEL_COMPLETE;
  3690. uint16_t y = SFG_GAME_RESOLUTION_Y / 2 -
  3691. ((STRIP_HEIGHT + INNER_STRIP_HEIGHT) / 2) / 2;
  3692. uint16_t x = (SFG_GAME_RESOLUTION_X -
  3693. SFG_textHorizontalSize(textLine,SFG_FONT_SIZE_BIG)) / 2;
  3694. SFG_drawText(textLine,x,y,SFG_FONT_SIZE_BIG,7 + SFG_game.blink * 95,255,0);
  3695. uint32_t timeTotal = SFG_SAVE_TOTAL_TIME;
  3696. // don't show totals in level 1:
  3697. uint8_t blink = (SFG_game.blink) && (SFG_currentLevel.levelNumber != 0)
  3698. && (timeTotal != 0);
  3699. if (t >= (SFG_WIN_ANIMATION_DURATION / 2))
  3700. {
  3701. y += (SFG_FONT_SIZE_BIG + SFG_FONT_SIZE_MEDIUM) * SFG_FONT_CHARACTER_SIZE;
  3702. x = SFG_HUD_MARGIN;
  3703. #define CHAR_SIZE (SFG_FONT_SIZE_SMALL * SFG_FONT_CHARACTER_SIZE)
  3704. uint32_t time = blink ? timeTotal : SFG_currentLevel.completionTime10sOfS;
  3705. x += SFG_drawNumber(time / 10,x,y,SFG_FONT_SIZE_SMALL,7) *
  3706. CHAR_SIZE + SFG_FONT_SIZE_SMALL;
  3707. char timeRest[5] = ".X s";
  3708. timeRest[1] = '0' + (time % 10);
  3709. SFG_drawText(timeRest,x,y,SFG_FONT_SIZE_SMALL,7,4,0);
  3710. x = SFG_HUD_MARGIN;
  3711. y += (SFG_FONT_SIZE_BIG + SFG_FONT_SIZE_MEDIUM) * SFG_FONT_CHARACTER_SIZE;
  3712. if (blink)
  3713. {
  3714. x += (SFG_drawNumber(SFG_game.save[10] + SFG_game.save[11] * 256,x,y,
  3715. SFG_FONT_SIZE_SMALL,7) + 1) * CHAR_SIZE;
  3716. }
  3717. else
  3718. {
  3719. x += SFG_drawNumber(SFG_currentLevel.monstersDead,x,y,
  3720. SFG_FONT_SIZE_SMALL,7) * CHAR_SIZE;
  3721. SFG_drawText("/",x,y,SFG_FONT_SIZE_SMALL,7,1,0);
  3722. x += CHAR_SIZE;
  3723. x += (SFG_drawNumber(SFG_currentLevel.monsterRecordCount,x,y,
  3724. SFG_FONT_SIZE_SMALL,7) + 1) * CHAR_SIZE;
  3725. }
  3726. SFG_drawText(SFG_TEXT_KILLS,x,y,SFG_FONT_SIZE_SMALL,7,255,0);
  3727. if ((t >= (SFG_WIN_ANIMATION_DURATION - 1)) &&
  3728. (SFG_currentLevel.levelNumber != (SFG_NUMBER_OF_LEVELS - 1)) &&
  3729. SFG_game.blink)
  3730. {
  3731. y += (SFG_FONT_SIZE_BIG + SFG_FONT_SIZE_MEDIUM) * SFG_FONT_CHARACTER_SIZE;
  3732. SFG_drawText(SFG_TEXT_SAVE_PROMPT,
  3733. (SFG_GAME_RESOLUTION_X - SFG_textHorizontalSize(SFG_TEXT_SAVE_PROMPT,
  3734. SFG_FONT_SIZE_MEDIUM)) / 2,y,SFG_FONT_SIZE_MEDIUM,7,255,0);
  3735. }
  3736. #undef CHAR_SIZE
  3737. }
  3738. #undef STRIP_HEIGHT
  3739. #undef STRIP_START
  3740. #undef INNER_STRIP_HEIGHT
  3741. }
  3742. void SFG_draw(void)
  3743. {
  3744. #if SFG_BACKGROUND_BLUR != 0
  3745. SFG_backgroundBlurIndex = 0;
  3746. #endif
  3747. if (SFG_game.state == SFG_GAME_STATE_MENU)
  3748. {
  3749. SFG_drawMenu();
  3750. return;
  3751. }
  3752. if (SFG_game.state == SFG_GAME_STATE_INTRO ||
  3753. SFG_game.state == SFG_GAME_STATE_OUTRO)
  3754. {
  3755. SFG_drawStoryText();
  3756. return;
  3757. }
  3758. if (SFG_keyIsDown(SFG_KEY_MAP) || (SFG_game.state == SFG_GAME_STATE_MAP))
  3759. {
  3760. SFG_drawMap();
  3761. }
  3762. else
  3763. {
  3764. for (int_fast16_t i = 0; i < SFG_Z_BUFFER_SIZE; ++i)
  3765. SFG_game.zBuffer[i] = 255;
  3766. int16_t weaponBobOffset = 0;
  3767. #if SFG_HEADBOB_ENABLED
  3768. RCL_Unit headBobOffset = 0;
  3769. #if SFG_HEADBOB_SHEAR != 0
  3770. int16_t headBobShearOffset = 0;
  3771. #endif
  3772. if (SFG_game.state != SFG_GAME_STATE_LOSE)
  3773. {
  3774. RCL_Unit bobSin = RCL_sin(SFG_player.headBobFrame);
  3775. headBobOffset = (bobSin * SFG_HEADBOB_OFFSET) / RCL_UNITS_PER_SQUARE;
  3776. #if SFG_HEADBOB_SHEAR != 0
  3777. headBobShearOffset = (bobSin * SFG_HEADBOB_SHEAR) / RCL_UNITS_PER_SQUARE;
  3778. SFG_player.camera.shear += headBobShearOffset;
  3779. #endif
  3780. weaponBobOffset =
  3781. (bobSin * SFG_WEAPONBOB_OFFSET_PIXELS) / (RCL_UNITS_PER_SQUARE) +
  3782. SFG_WEAPONBOB_OFFSET_PIXELS;
  3783. }
  3784. else
  3785. {
  3786. // player die animation
  3787. weaponBobOffset =
  3788. (SFG_WEAPON_IMAGE_SCALE * SFG_TEXTURE_SIZE * SFG_game.stateTime) /
  3789. SFG_LOSE_ANIMATION_DURATION;
  3790. }
  3791. // add head bob just for the rendering (we'll will subtract it back later)
  3792. SFG_player.camera.height += headBobOffset;
  3793. #endif // headbob enabled?
  3794. RCL_renderComplex(
  3795. SFG_player.camera,
  3796. SFG_floorHeightAt,
  3797. SFG_ceilingHeightAt,
  3798. SFG_texturesAt,
  3799. SFG_game.rayConstraints);
  3800. // draw sprites:
  3801. // monster sprites:
  3802. for (int_fast16_t i = 0; i < SFG_currentLevel.monsterRecordCount; ++i)
  3803. {
  3804. SFG_MonsterRecord m = SFG_currentLevel.monsterRecords[i];
  3805. uint8_t state = SFG_MR_STATE(m);
  3806. if (state != SFG_MONSTER_STATE_INACTIVE)
  3807. {
  3808. RCL_Vector2D worldPosition;
  3809. worldPosition.x = SFG_MONSTER_COORD_TO_RCL_UNITS(m.coords[0]);
  3810. worldPosition.y = SFG_MONSTER_COORD_TO_RCL_UNITS(m.coords[1]);
  3811. uint8_t spriteSize = SFG_GET_MONSTER_SPRITE_SIZE(
  3812. SFG_MONSTER_TYPE_TO_INDEX(SFG_MR_TYPE(m)));
  3813. RCL_Unit worldHeight =
  3814. SFG_floorHeightAt(
  3815. SFG_MONSTER_COORD_TO_SQUARES(m.coords[0]),
  3816. SFG_MONSTER_COORD_TO_SQUARES(m.coords[1]))
  3817. + SFG_SPRITE_SIZE_TO_HEIGHT_ABOVE_GROUND(spriteSize);
  3818. RCL_PixelInfo p =
  3819. RCL_mapToScreen(worldPosition,worldHeight,SFG_player.camera);
  3820. if (p.depth > 0 &&
  3821. SFG_spriteIsVisible(worldPosition,worldHeight))
  3822. {
  3823. const uint8_t *s =
  3824. SFG_getMonsterSprite(
  3825. SFG_MR_TYPE(m),
  3826. state,
  3827. SFG_game.spriteAnimationFrame & 0x01);
  3828. SFG_drawScaledSprite(s,
  3829. p.position.x * SFG_RAYCASTING_SUBSAMPLE,p.position.y,
  3830. RCL_perspectiveScaleVertical(
  3831. SFG_SPRITE_SIZE_PIXELS(spriteSize),
  3832. p.depth),
  3833. p.depth / (RCL_UNITS_PER_SQUARE * 2),p.depth);
  3834. }
  3835. }
  3836. }
  3837. // item sprites:
  3838. for (int_fast16_t i = 0; i < SFG_currentLevel.itemRecordCount; ++i)
  3839. if (SFG_currentLevel.itemRecords[i] & SFG_ITEM_RECORD_ACTIVE_MASK)
  3840. {
  3841. RCL_Vector2D worldPosition;
  3842. SFG_LevelElement e =
  3843. SFG_currentLevel.levelPointer->elements[
  3844. SFG_currentLevel.itemRecords[i] & ~SFG_ITEM_RECORD_ACTIVE_MASK];
  3845. worldPosition.x =
  3846. SFG_ELEMENT_COORD_TO_RCL_UNITS(e.coords[0]);
  3847. worldPosition.y =
  3848. SFG_ELEMENT_COORD_TO_RCL_UNITS(e.coords[1]);
  3849. const uint8_t *sprite;
  3850. uint8_t spriteSize;
  3851. SFG_getItemSprite(e.type,&sprite,&spriteSize);
  3852. if (sprite != 0)
  3853. {
  3854. RCL_Unit worldHeight = SFG_floorHeightAt(e.coords[0],e.coords[1])
  3855. + SFG_SPRITE_SIZE_TO_HEIGHT_ABOVE_GROUND(spriteSize);
  3856. RCL_PixelInfo p =
  3857. RCL_mapToScreen(worldPosition,worldHeight,SFG_player.camera);
  3858. if (p.depth > 0 &&
  3859. SFG_spriteIsVisible(worldPosition,worldHeight))
  3860. SFG_drawScaledSprite(sprite,p.position.x * SFG_RAYCASTING_SUBSAMPLE,
  3861. p.position.y,
  3862. RCL_perspectiveScaleVertical(SFG_SPRITE_SIZE_PIXELS(spriteSize),
  3863. p.depth),p.depth / (RCL_UNITS_PER_SQUARE * 2),p.depth);
  3864. }
  3865. }
  3866. // projectile sprites:
  3867. for (uint8_t i = 0; i < SFG_currentLevel.projectileRecordCount; ++i)
  3868. {
  3869. SFG_ProjectileRecord *proj = &(SFG_currentLevel.projectileRecords[i]);
  3870. if (proj->type == SFG_PROJECTILE_BULLET)
  3871. continue; // bullets aren't drawn
  3872. RCL_Vector2D worldPosition;
  3873. worldPosition.x = proj->position[0];
  3874. worldPosition.y = proj->position[1];
  3875. RCL_PixelInfo p =
  3876. RCL_mapToScreen(worldPosition,proj->position[2],SFG_player.camera);
  3877. const uint8_t *s =
  3878. SFG_effectSprites + proj->type * SFG_TEXTURE_STORE_SIZE;
  3879. int16_t spriteSize = SFG_SPRITE_SIZE_PIXELS(0);
  3880. if (proj->type == SFG_PROJECTILE_EXPLOSION ||
  3881. proj->type == SFG_PROJECTILE_DUST)
  3882. {
  3883. int16_t doubleFramesToLive =
  3884. RCL_nonZero(SFG_GET_PROJECTILE_FRAMES_TO_LIVE(proj->type) / 2);
  3885. // grow the explosion/dust sprite as an animation
  3886. spriteSize = (
  3887. SFG_SPRITE_SIZE_PIXELS(2) *
  3888. RCL_sin(
  3889. ((doubleFramesToLive -
  3890. proj->doubleFramesToLive) * RCL_UNITS_PER_SQUARE / 4)
  3891. / doubleFramesToLive)
  3892. ) / RCL_UNITS_PER_SQUARE;
  3893. }
  3894. if (p.depth > 0 &&
  3895. SFG_spriteIsVisible(worldPosition,proj->position[2]))
  3896. SFG_drawScaledSprite(s,
  3897. p.position.x * SFG_RAYCASTING_SUBSAMPLE,p.position.y,
  3898. RCL_perspectiveScaleVertical(spriteSize,p.depth),
  3899. SFG_fogValueDiminish(p.depth),
  3900. p.depth);
  3901. }
  3902. #if SFG_HEADBOB_ENABLED
  3903. // after rendering sprites subtract back the head bob offset
  3904. SFG_player.camera.height -= headBobOffset;
  3905. #if SFG_HEADBOB_SHEAR != 0
  3906. SFG_player.camera.shear -= headBobShearOffset;
  3907. #endif
  3908. #endif // head bob enabled?
  3909. #if SFG_PREVIEW_MODE == 0
  3910. SFG_drawWeapon(weaponBobOffset);
  3911. #endif
  3912. // draw HUD:
  3913. // bar
  3914. uint8_t color = 61;
  3915. uint8_t color2 = 48;
  3916. if (SFG_game.cheatState & 0x80)
  3917. {
  3918. color = 170;
  3919. color2 = 0;
  3920. }
  3921. for (uint16_t j = SFG_GAME_RESOLUTION_Y - SFG_HUD_BAR_HEIGHT;
  3922. j < SFG_GAME_RESOLUTION_Y; ++j)
  3923. {
  3924. for (uint16_t i = 0; i < SFG_GAME_RESOLUTION_X; ++i)
  3925. SFG_setGamePixel(i,j,color);
  3926. color = color2;
  3927. }
  3928. #define TEXT_Y (SFG_GAME_RESOLUTION_Y - SFG_HUD_MARGIN - \
  3929. SFG_FONT_CHARACTER_SIZE * SFG_FONT_SIZE_MEDIUM)
  3930. SFG_drawNumber( // health
  3931. SFG_player.health,
  3932. SFG_HUD_MARGIN,
  3933. TEXT_Y,
  3934. SFG_FONT_SIZE_MEDIUM,
  3935. SFG_player.health > SFG_PLAYER_HEALTH_WARNING_LEVEL ? 6 : 175);
  3936. SFG_drawNumber( // ammo
  3937. SFG_player.weapon != SFG_WEAPON_KNIFE ?
  3938. SFG_player.ammo[SFG_weaponAmmo(SFG_player.weapon)] : 0,
  3939. SFG_GAME_RESOLUTION_X - SFG_HUD_MARGIN -
  3940. (SFG_FONT_CHARACTER_SIZE + 1) * SFG_FONT_SIZE_MEDIUM * 3,
  3941. TEXT_Y,
  3942. SFG_FONT_SIZE_MEDIUM,
  3943. 6);
  3944. for (uint8_t i = 0; i < 3; ++i) // access cards
  3945. if (
  3946. ((SFG_player.cards >> i) | ((SFG_player.cards >> (i + 3))
  3947. & SFG_game.blink)) & 0x01)
  3948. SFG_fillRectangle(
  3949. SFG_HUD_MARGIN + (SFG_FONT_CHARACTER_SIZE + 1) *
  3950. SFG_FONT_SIZE_MEDIUM * (5 + i),
  3951. TEXT_Y,
  3952. SFG_FONT_SIZE_MEDIUM * SFG_FONT_CHARACTER_SIZE,
  3953. SFG_FONT_SIZE_MEDIUM * SFG_FONT_CHARACTER_SIZE,
  3954. i == 0 ? 93 : (i == 1 ? 124 : 60));
  3955. #undef TEXT_Y
  3956. // border indicator
  3957. if ((SFG_game.frame - SFG_player.lastHurtFrame
  3958. <= SFG_HUD_BORDER_INDICATOR_DURATION_FRAMES) ||
  3959. (SFG_game.state == SFG_GAME_STATE_LOSE))
  3960. SFG_drawIndicationBorder(SFG_HUD_BORDER_INDICATOR_WIDTH_PIXELS,
  3961. SFG_HUD_HURT_INDICATION_COLOR);
  3962. else if (SFG_game.frame - SFG_player.lastItemTakenFrame
  3963. <= SFG_HUD_BORDER_INDICATOR_DURATION_FRAMES)
  3964. SFG_drawIndicationBorder(SFG_HUD_BORDER_INDICATOR_WIDTH_PIXELS,
  3965. SFG_HUD_ITEM_TAKEN_INDICATION_COLOR);
  3966. if (SFG_game.state == SFG_GAME_STATE_WIN)
  3967. SFG_drawWinOverlay();
  3968. else if (SFG_game.state == SFG_GAME_STATE_LEVEL_START)
  3969. SFG_drawLevelStartOverlay();
  3970. }
  3971. }
  3972. uint8_t SFG_mainLoopBody(void)
  3973. {
  3974. /* Standard deterministic game loop, independed of actual achieved FPS.
  3975. Each game logic (physics) frame is performed with the SFG_MS_PER_FRAME
  3976. delta time. */
  3977. if (SFG_game.state != SFG_GAME_STATE_INIT)
  3978. {
  3979. uint32_t timeNow = SFG_getTimeMs();
  3980. #if SFG_TIME_MULTIPLIER != 1024
  3981. timeNow = (timeNow * SFG_TIME_MULTIPLIER) / 1024;
  3982. #endif
  3983. int32_t timeSinceLastFrame = timeNow - SFG_game.frameTime;
  3984. if (timeSinceLastFrame >= SFG_MS_PER_FRAME)
  3985. {
  3986. uint8_t steps = 0;
  3987. uint8_t wasFirstFrame = SFG_game.frame == 0;
  3988. while (timeSinceLastFrame >= SFG_MS_PER_FRAME)
  3989. {
  3990. uint8_t previousWeapon = SFG_player.weapon;
  3991. SFG_game.frameTime += SFG_MS_PER_FRAME;
  3992. SFG_gameStep();
  3993. if (SFG_player.weapon != previousWeapon)
  3994. SFG_processEvent(SFG_EVENT_PLAYER_CHANGES_WEAPON,SFG_player.weapon);
  3995. timeSinceLastFrame -= SFG_MS_PER_FRAME;
  3996. SFG_game.frame++;
  3997. steps++;
  3998. }
  3999. if ((steps > 1) && (SFG_game.antiSpam == 0) && (!wasFirstFrame))
  4000. {
  4001. SFG_LOG("failed to reach target FPS! consider setting a lower value")
  4002. SFG_game.antiSpam = 30;
  4003. }
  4004. if (SFG_game.antiSpam > 0)
  4005. SFG_game.antiSpam--;
  4006. // render only once
  4007. SFG_draw();
  4008. if (SFG_game.frame % 16 == 0)
  4009. SFG_CPU_LOAD(((SFG_getTimeMs() - timeNow) * 100) / SFG_MS_PER_FRAME);
  4010. }
  4011. else
  4012. {
  4013. // wait, relieve CPU
  4014. SFG_sleepMs(RCL_max(1,
  4015. (3 * (SFG_game.frameTime + SFG_MS_PER_FRAME - timeNow)) / 4));
  4016. }
  4017. }
  4018. else if (!SFG_keyPressed(SFG_KEY_A) && !SFG_keyPressed(SFG_KEY_B))
  4019. {
  4020. /* At the beginning we have to wait for the release of the keys in order not
  4021. to immediatelly confirm a menu item. */
  4022. SFG_setGameState(SFG_GAME_STATE_MENU);
  4023. }
  4024. return SFG_game.continues;
  4025. }
  4026. #undef SFG_SAVE_TOTAL_TIME
  4027. #endif // guard