ccleste/celeste.c

2033 lines
72 KiB
C

/*
* This is where the actual celeste code sits.
* It is mostly a line by line port of the original lua code.
* Due to C limitations, modifications have to be made, mostly relating to static typing.
* The PICO-8 functions such as music() are used here preceded by Celeste_P8,
* so _init becomes Celeste_P8_init && music becomes P8music, etc
*/
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include "celeste.h"
#ifdef CELESTE_P8_FIXEDP
//very ugly hack:
//in order to switch to fixed point type and arithmetic, without having
//to replace every arithmetic operator with a macro call, use a C++
//struct with operator overloading and #define float to it...
//it works, i guess!
# ifndef __cplusplus
# error "fixed point mode can only be compiled with a C++ compiler"
# endif
#include <cstdint>
#include <climits>
struct _fix32 {
int32_t n;
static const int32_t FACTOR = (1<<16);
//constructor
template <typename T> _fix32(T v) { *this = v; /* use operator= */ }
_fix32() { /*undefined*/ }
//construct number from raw integer value
static _fix32 from_bits(int32_t i) {
_fix32 tmp;
tmp.n = i;
return tmp;
}
//fractional part
inline uint16_t frac() { return static_cast<uint16_t>(this->n); }
// T -> _fix32
template <typename T> static inline _fix32 from(T n) { return _fix32::from_bits(n * T(FACTOR)); }
// _fix32 -> T
template <typename T> inline T to() const { return T(this->n) / T(FACTOR); }
template <typename T> inline _fix32& operator=(T n) {
this->n = _fix32::from<T>(n).n;
return *this;
}
//same as .to<T>()
template <typename T> inline explicit operator T() const { return this->to<T>(); }
//arithmetic operator overloading
inline friend _fix32 operator +(_fix32 a, _fix32 b) { return from_bits(a.n + b.n); }
inline friend _fix32 operator -(_fix32 a, _fix32 b) { return from_bits(a.n - b.n); }
inline friend _fix32 operator -(_fix32 a) { return from_bits(-a.n); }
inline friend _fix32 operator *(_fix32 a, _fix32 b) {
return from_bits(int64_t(a.n)*int64_t(b.n) / int64_t(FACTOR));
}
inline friend _fix32 operator /(_fix32 a, _fix32 b) { //pico8 decomp'd
if (b == 0) return a > 0 ? FACTOR : -FACTOR; // +inf / -inf
if (b.frac() == 0 && ((int)b > 0)) {
return _fix32::from_bits(int64_t(a.n) / int64_t(b));
}
return from_bits((int64_t(a.n)*FACTOR) / int64_t(b.n));
}
inline friend _fix32& operator +=(_fix32& a, _fix32 b) { return a = a + b; }
inline friend _fix32& operator -=(_fix32& a, _fix32 b) { return a = a - b; }
inline friend _fix32& operator *=(_fix32& a, _fix32 b) { return a = a * b; }
inline friend _fix32& operator /=(_fix32& a, _fix32 b) { return a = a / b; }
inline friend bool operator ==(_fix32 a, _fix32 b) { return a.n == b.n; }
inline friend bool operator !=(_fix32 a, _fix32 b) { return a.n != b.n; }
inline friend bool operator <(_fix32 a, _fix32 b) { return a.n < b.n; }
inline friend bool operator >(_fix32 a, _fix32 b) { return a.n > b.n; }
inline friend bool operator <=(_fix32 a, _fix32 b) { return a.n <= b.n; }
inline friend bool operator >=(_fix32 a, _fix32 b) { return a.n >= b.n; }
};
static_assert(sizeof(_fix32) == 4, "bad");
//math functions
static _fix32 _fix32_mod(_fix32 a, _fix32 b) {
return _fix32::from_bits(((a.n % b.n) + b.n) % b.n);
}
static _fix32 _fix32_sin(_fix32 x) { //pico8 decomp'd
static const int32_t sin_tbl[4098] = {65536,65536,65536,65536,65536,65536,65536,65536,65536,65536,65536,65535,65535,65535,65535,65535,65535,65535,65534,65534,65534,65534,65534,65533,65533,65533,65533,65532,65532,65532,65532,65531,65531,65531,65530,65530,65530,65529,65529,65529,65528,65528,65527,65527,65527,65526,65526,65525,65525,65524,65524,65523,65523,65522,65522,65521,65521,65520,65520,65519,65519,65518,65517,65517,65516,65516,65515,65514,65514,65513,65512,65512,65511,65510,65510,65509,65508,65507,65507,65506,65505,65504,65504,65503,65502,65501,65500,65500,65499,65498,65497,65496,65495,65494,65493,65493,65492,65491,65490,65489,65488,65487,65486,65485,65484,65483,65482,65481,65480,65479,65478,65477,65476,65474,65473,65472,65471,65470,65469,65468,65467,65465,65464,65463,65462,65461,65460,65458,65457,65456,65455,65453,65452,65451,65449,65448,65447,65446,65444,65443,65442,65440,65439,65437,65436,65435,65433,65432,65430,65429,65428,65426,65425,65423,65422,65420,65419,65417,65416,65414,65413,65411,65410,65408,65406,65405,65403,65402,65400,65398,65397,65395,65393,65392,65390,65388,65387,65385,65383,65382,65380,65378,65376,65375,65373,65371,65369,65368,65366,65364,65362,65360,65358,65357,65355,65353,65351,65349,65347,65345,65343,65341,65339,65338,65336,65334,65332,65330,65328,65326,65324,65322,65320,65317,65315,65313,65311,65309,65307,65305,65303,65301,65299,65296,65294,65292,65290,65288,65286,65283,65281,65279,65277,65275,65272,65270,65268,65265,65263,65261,65259,65256,65254,65252,65249,65247,65245,65242,65240,65237,65235,65233,65230,65228,65225,65223,65220,65218,65215,65213,65210,65208,65205,65203,65200,65198,65195,65193,65190,65188,65185,65182,65180,65177,65175,65172,65169,65167,65164,65161,65159,65156,65153,65150,65148,65145,65142,65139,65137,65134,65131,65128,65126,65123,65120,65117,65114,65111,65109,65106,65103,65100,65097,65094,65091,65088,65085,65082,65079,65076,65073,65070,65067,65064,65061,65058,65055,65052,65049,65046,65043,65040,65037,65034,65031,65028,65025,65021,65018,65015,65012,65009,65006,65002,64999,64996,64993,64989,64986,64983,64980,64976,64973,64970,64967,64963,64960,64957,64953,64950,64947,64943,64940,64936,64933,64930,64926,64923,64919,64916,64912,64909,64905,64902,64899,64895,64892,64888,64884,64881,64877,64874,64870,64867,64863,64859,64856,64852,64849,64845,64841,64838,64834,64830,64827,64823,64819,64816,64812,64808,64804,64801,64797,64793,64789,64786,64782,64778,64774,64770,64766,64763,64759,64755,64751,64747,64743,64739,64735,64732,64728,64724,64720,64716,64712,64708,64704,64700,64696,64692,64688,64684,64680,64676,64672,64667,64663,64659,64655,64651,64647,64643,64639,64635,64630,64626,64622,64618,64614,64609,64605,64601,64597,64593,64588,64584,64580,64575,64571,64567,64563,64558,64554,64550,64545,64541,64536,64532,64528,64523,64519,64514,64510,64506,64501,64497,64492,64488,64483,64479,64474,64470,64465,64461,64456,64452,64447,64443,64438,64433,64429,64424,64420,64415,64410,64406,64401,64396,64392,64387,64382,64378,64373,64368,64363,64359,64354,64349,64344,64340,64335,64330,64325,64320,64316,64311,64306,64301,64296,64291,64287,64282,64277,64272,64267,64262,64257,64252,64247,64242,64237,64232,64227,64222,64217,64212,64207,64202,64197,64192,64187,64182,64177,64172,64167,64161,64156,64151,64146,64141,64136,64131,64125,64120,64115,64110,64105,64099,64094,64089,64084,64078,64073,64068,64062,64057,64052,64047,64041,64036,64031,64025,64020,64014,64009,64004,63998,63993,63987,63982,63976,63971,63966,63960,63955,63949,63944,63938,63933,63927,63922,63916,63910,63905,63899,63894,63888,63882,63877,63871,63866,63860,63854,63849,63843,63837,63832,63826,63820,63814,63809,63803,63797,63792,63786,63780,63774,63768,63763,63757,63751,63745,63739,63733,63728,63722,63716,63710,63704,63698,63692,63686,63680,63674,63668,63663,63657,63651,63645,63639,63633,63627,63621,63614,63608,63602,63596,63590,63584,63578,63572,63566,63560,63554,63547,63541,63535,63529,63523,63517,63510,63504,63498,63492,63486,63479,63473,63467,63461,63454,63448,63442,63435,63429,63423,63416,63410,63404,63397,63391,63385,63378,63372,63365,63359,63353,63346,63340,63333,63327,63320,63314,63307,63301,63294,63288,63281,63275,63268,63262,63255,63248,63242,63235,63229,63222,63215,63209,63202,63195,63189,63182,63175,63169,63162,63155,63149,63142,63135,63128,63122,63115,63108,63101,63095,63088,63081,63074,63067,63060,63054,63047,63040,63033,63026,63019,63012,63005,62998,62992,62985,62978,62971,62964,62957,62950,62943,62936,62929,62922,62915,62908,62901,62894,62886,62879,62872,62865,62858,62851,62844,62837,62830,62822,62815,62808,62801,62794,62787,62779,62772,62765,62758,62750,62743,62736,62729,62721,62714,62707,62699,62692,62685,62677,62670,62663,62655,62648,62641,62633,62626,62618,62611,62604,62596,62589,62581,62574,62566,62559,62551,62544,62536,62529,62521,62514,62506,62499,62491,62483,62476,62468,62461,62453,62445,62438,62430,62423,62415,62407,62400,62392,62384,62376,62369,62361,62353,62346,62338,62330,62322,62314,62307,62299,62291,62283,62275,62268,62260,62252,62244,62236,62228,62220,62212,62205,62197,62189,62181,62173,62165,62157,62149,62141,62133,62125,62117,62109,62101,62093,62085,62077,62069,62061,62053,62045,62036,62028,62020,62012,62004,61996,61988,61979,61971,61963,61955,61947,61939,61930,61922,61914,61906,61897,61889,61881,61873,61864,61856,61848,61839,61831,61823,61814,61806,61798,61789,61781,61772,61764,61756,61747,61739,61730,61722,61713,61705,61697,61688,61680,61671,61663,61654,61646,61637,61628,61620,61611,61603,61594,61586,61577,61568,61560,61551,61543,61534,61525,61517,61508,61499,61491,61482,61473,61464,61456,61447,61438,61429,61421,61412,61403,61394,61386,61377,61368,61359,61350,61341,61333,61324,61315,61306,61297,61288,61279,61270,61261,61253,61244,61235,61226,61217,61208,61199,61190,61181,61172,61163,61154,61145,61136,61127,61117,61108,61099,61090,61081,61072,61063,61054,61045,61035,61026,61017,61008,60999,60990,60980,60971,60962,60953,60943,60934,60925,60916,60906,60897,60888,60879,60869,60860,60851,60841,60832,60823,60813,60804,60794,60785,60776,60766,60757,60747,60738,60728,60719,60710,60700,60691,60681,60672,60662,60653,60643,60634,60624,60614,60605,60595,60586,60576,60567,60557,60547,60538,60528,60518,60509,60499,60490,60480,60470,60460,60451,60441,60431,60422,60412,60402,60392,60383,60373,60363,60353,60343,60334,60324,60314,60304,60294,60284,60275,60265,60255,60245,60235,60225,60215,60205,60195,60185,60175,60166,60156,60146,60136,60126,60116,60106,60096,60086,60075,60065,60055,60045,60035,60025,60015,60005,59995,59985,59975,59964,59954,59944,59934,59924,59914,59903,59893,59883,59873,59863,59852,59842,59832,59822,59811,59801,59791,59781,59770,59760,59750,59739,59729,59719,59708,59698,59687,59677,59667,59656,59646,59635,59625,59615,59604,59594,59583,59573,59562,59552,59541,59531,59520,59510,59499,59489,59478,59468,59457,59446,59436,59425,59415,59404,59393,59383,59372,59362,59351,59340,59330,59319,59308,59297,59287,59276,59265,59255,59244,59233,59222,59212,59201,59190,59179,59168,59158,59147,59136,59125,59114,59103,59093,59082,59071,59060,59049,59038,59027,59016,59005,58994,58983,58972,58962,58951,58940,58929,58918,58907,58896,58885,58873,58862,58851,58840,58829,58818,58807,58796,58785,58774,58763,58751,58740,58729,58718,58707,58696,58685,58673,58662,58651,58640,58628,58617,58606,58595,58583,58572,58561,58550,58538,58527,58516,58504,58493,58482,58470,58459,58448,58436,58425,58414,58402,58391,58379,58368,58356,58345,58334,58322,58311,58299,58288,58276,58265,58253,58242,58230,58219,58207,58195,58184,58172,58161,58149,58138,58126,58114,58103,58091,58079,58068,58056,58045,58033,58021,58009,57998,57986,57974,57963,57951,57939,57927,57916,57904,57892,57880,57869,57857,57845,57833,57821,57809,57798,57786,57774,57762,57750,57738,57726,57714,57703,57691,57679,57667,57655,57643,57631,57619,57607,57595,57583,57571,57559,57547,57535,57523,57511,57499,57487,57475,57463,57450,57438,57426,57414,57402,57390,57378,57366,57353,57341,57329,57317,57305,57293,57280,57268,57256,57244,57231,57219,57207,57195,57182,57170,57158,57145,57133,57121,57109,57096,57084,57072,57059,57047,57034,57022,57010,56997,56985,56972,56960,56948,56935,56923,56910,56898,56885,56873,56860,56848,56835,56823,56810,56798,56785,56773,56760,56747,56735,56722,56710,56697,56684,56672,56659,56647,56634,56621,56609,56596,56583,56571,56558,56545,56533,56520,56507,56494,56482,56469,56456,56443,56431,56418,56405,56392,56379,56367,56354,56341,56328,56315,56302,56289,56277,56264,56251,56238,56225,56212,56199,56186,56173,56160,56147,56134,56121,56108,56095,56082,56069,56056,56043,56030,56017,56004,55991,55978,55965,55952,55939,55926,55913,55900,55887,55873,55860,55847,55834,55821,55808,55794,55781,55768,55755,55742,55728,55715,55702,55689,55675,55662,55649,55636,55622,55609,55596,55582,55569,55556,55542,55529,55516,55502,55489,55476,55462,55449,55435,55422,55409,55395,55382,55368,55355,55341,55328,55314,55301,55288,55274,55260,55247,55233,55220,55206,55193,55179,55166,55152,55139,55125,55111,55098,55084,55071,55057,55043,55030,55016,55002,54989,54975,54961,54948,54934,54920,54906,54893,54879,54865,54852,54838,54824,54810,54796,54783,54769,54755,54741,54727,54714,54700,54686,54672,54658,54644,54630,54617,54603,54589,54575,54561,54547,54533,54519,54505,54491,54477,54463,54449,54435,54421,54407,54393,54379,54365,54351,54337,54323,54309,54295,54281,54267,54253,54239,54224,54210,54196,54182,54168,54154,54140,54125,54111,54097,54083,54069,54054,54040,54026,54012,53998,53983,53969,53955,53941,53926,53912,53898,53883,53869,53855,53840,53826,53812,53797,53783,53769,53754,53740,53726,53711,53697,53682,53668,53653,53639,53625,53610,53596,53581,53567,53552,53538,53523,53509,53494,53480,53465,53451,53436,53422,53407,53392,53378,53363,53349,53334,53319,53305,53290,53276,53261,53246,53232,53217,53202,53188,53173,53158,53144,53129,53114,53099,53085,53070,53055,53040,53026,53011,52996,52981,52967,52952,52937,52922,52907,52892,52878,52863,52848,52833,52818,52803,52788,52773,52759,52744,52729,52714,52699,52684,52669,52654,52639,52624,52609,52594,52579,52564,52549,52534,52519,52504,52489,52474,52459,52444,52429,52414,52398,52383,52368,52353,52338,52323,52308,52293,52277,52262,52247,52232,52217,52202,52186,52171,52156,52141,52126,52110,52095,52080,52065,52049,52034,52019,52003,51988,51973,51957,51942,51927,51911,51896,51881,51865,51850,51835,51819,51804,51789,51773,51758,51742,51727,51711,51696,51681,51665,51650,51634,51619,51603,51588,51572,51557,51541,51526,51510,51495,51479,51463,51448,51432,51417,51401,51386,51370,51354,51339,51323,51307,51292,51276,51260,51245,51229,51213,51198,51182,51166,51151,51135,51119,51104,51088,51072,51056,51041,51025,51009,50993,50977,50962,50946,50930,50914,50898,50882,50867,50851,50835,50819,50803,50787,50771,50756,50740,50724,50708,50692,50676,50660,50644,50628,50612,50596,50580,50564,50548,50532,50516,50500,50484,50468,50452,50436,50420,50404,50388,50372,50356,50340,50324,50307,50291,50275,50259,50243,50227,50211,50195,50178,50162,50146,50130,50114,50097,50081,50065,50049,50033,50016,50000,49984,49968,49951,49935,49919,49902,49886,49870,49854,49837,49821,49805,49788,49772,49756,49739,49723,49706,49690,49674,49657,49641,49624,49608,49592,49575,49559,49542,49526,49509,49493,49476,49460,49443,49427,49410,49394,49377,49361,49344,49328,49311,49295,49278,49262,49245,49228,49212,49195,49179,49162,49145,49129,49112,49095,49079,49062,49045,49029,49012,48995,48979,48962,48945,48929,48912,48895,48878,48862,48845,48828,48811,48795,48778,48761,48744,48727,48711,48694,48677,48660,48643,48626,48610,48593,48576,48559,48542,48525,48508,48491,48474,48458,48441,48424,48407,48390,48373,48356,48339,48322,48305,48288,48271,48254,48237,48220,48203,48186,48169,48152,48135,48118,48101,48084,48067,48049,48032,48015,47998,47981,47964,47947,47930,47912,47895,47878,47861,47844,47827,47809,47792,47775,47758,47741,47723,47706,47689,47672,47654,47637,47620,47603,47585,47568,47551,47534,47516,47499,47482,47464,47447,47430,47412,47395,47378,47360,47343,47325,47308,47291,47273,47256,47238,47221,47204,47186,47169,47151,47134,47116,47099,47081,47064,47046,47029,47011,46994,46976,46959,46941,46924,46906,46889,46871,46853,46836,46818,46801,46783,46765,46748,46730,46713,46695,46677,46660,46642,46624,46607,46589,46571,46554,46536,46518,46501,46483,46465,46447,46430,46412,46394,46376,46359,46341,46323,46305,46288,46270,46252,46234,46216,46199,46181,46163,46145,46127,46109,46091,46074,46056,46038,46020,46002,45984,45966,45948,45930,45912,45895,45877,45859,45841,45823,45805,45787,45769,45751,45733,45715,45697,45679,45661,45643,45625,45607,45589,45571,45552,45534,45516,45498,45480,45462,45444,45426,45408,45390,45371,45353,45335,45317,45299,45281,45262,45244,45226,45208,45190,45172,45153,45135,45117,45099,45080,45062,45044,45026,45007,44989,44971,44953,44934,44916,44898,44879,44861,44843,44824,44806,44788,44769,44751,44733,44714,44696,44677,44659,44641,44622,44604,44585,44567,44549,44530,44512,44493,44475,44456,44438,44419,44401,44382,44364,44345,44327,44308,44290,44271,44253,44234,44216,44197,44179,44160,44141,44123,44104,44086,44067,44049,44030,44011,43993,43974,43955,43937,43918,43899,43881,43862,43843,43825,43806,43787,43769,43750,43731,43713,43694,43675,43656,43638,43619,43600,43581,43562,43544,43525,43506,43487,43469,43450,43431,43412,43393,43374,43356,43337,43318,43299,43280,43261,43242,43223,43205,43186,43167,43148,43129,43110,43091,43072,43053,43034,43015,42996,42977,42958,42939,42920,42901,42882,42863,42844,42825,42806,42787,42768,42749,42730,42711,42692,42673,42654,42635,42616,42597,42578,42558,42539,42520,42501,42482,42463,42444,42424,42405,42386,42367,42348,42329,42309,42290,42271,42252,42233,42213,42194,42175,42156,42136,42117,42098,42079,42059,42040,42021,42002,41982,41963,41944,41924,41905,41886,41866,41847,41828,41808,41789,41770,41750,41731,41711,41692,41673,41653,41634,41614,41595,41576,41556,41537,41517,41498,41478,41459,41439,41420,41401,41381,41362,41342,41323,41303,41283,41264,41244,41225,41205,41186,41166,41147,41127,41108,41088,41068,41049,41029,41010,40990,40970,40951,40931,40912,40892,40872,40853,40833,40813,40794,40774,40754,40735,40715,40695,40675,40656,40636,40616,40597,40577,40557,40537,40518,40498,40478,40458,40439,40419,40399,40379,40359,40340,40320,40300,40280,40260,40241,40221,40201,40181,40161,40141,40121,40102,40082,40062,40042,40022,40002,39982,39962,39942,39922,39902,39882,39863,39843,39823,39803,39783,39763,39743,39723,39703,39683,39663,39643,39623,39603,39583,39563,39543,39523,39503,39482,39462,39442,39422,39402,39382,39362,39342,39322,39302,39282,39261,39241,39221,39201,39181,39161,39141,39120,39100,39080,39060,39040,39020,38999,38979,38959,38939,38919,38898,38878,38858,38838,38817,38797,38777,38757,38736,38716,38696,38675,38655,38635,38615,38594,38574,38554,38533,38513,38493,38472,38452,38432,38411,38391,38370,38350,38330,38309,38289,38269,38248,38228,38207,38187,38166,38146,38126,38105,38085,38064,38044,38023,38003,37982,37962,37941,37921,37900,37880,37859,37839,37818,37798,37777,37757,37736,37716,37695,37674,37654,37633,37613,37592,37572,37551,37530,37510,37489,37469,37448,37427,37407,37386,37365,37345,37324,37303,37283,37262,37241,37221,37200,37179,37159,37138,37117,37097,37076,37055,37034,37014,36993,36972,36951,36931,36910,36889,36868,36848,36827,36806,36785,36764,36744,36723,36702,36681,36660,36639,36619,36598,36577,36556,36535,36514,36493,36473,36452,36431,36410,36389,36368,36347,36326,36305,36284,36263,36243,36222,36201,36180,36159,36138,36117,36096,36075,36054,36033,36012,35991,35970,35949,35928,35907,35886,35865,35844,35823,35802,35781,35759,35738,35717,35696,35675,35654,35633,35612,35591,35570,35549,35527,35506,35485,35464,35443,35422,35401,35380,35358,35337,35316,35295,35274,35252,35231,35210,35189,35168,35146,35125,35104,35083,35062,35040,35019,34998,34977,34955,34934,34913,34892,34870,34849,34828,34806,34785,34764,34743,34721,34700,34679,34657,34636,34615,34593,34572,34551,34529,34508,34486,34465,34444,34422,34401,34380,34358,34337,34315,34294,34272,34251,34230,34208,34187,34165,34144,34122,34101,34079,34058,34037,34015,33994,33972,33951,33929,33908,33886,33865,33843,33821,33800,33778,33757,33735,33714,33692,33671,33649,33628,33606,33584,33563,33541,33520,33498,33476,33455,33433,33412,33390,33368,33347,33325,33303,33282,33260,33238,33217,33195,33173,33152,33130,33108,33087,33065,33043,33022,33000,32978,32956,32935,32913,32891,32870,32848,32826,32804,32783,32761,32739,32717,32695,32674,32652,32630,32608,32586,32565,32543,32521,32499,32477,32456,32434,32412,32390,32368,32346,32324,32303,32281,32259,32237,32215,32193,32171,32149,32127,32106,32084,32062,32040,32018,31996,31974,31952,31930,31908,31886,31864,31842,31820,31798,31776,31754,31732,31710,31688,31666,31644,31622,31600,31578,31556,31534,31512,31490,31468,31446,31424,31402,31380,31358,31336,31314,31292,31270,31248,31225,31203,31181,31159,31137,31115,31093,31071,31049,31026,31004,30982,30960,30938,30916,30893,30871,30849,30827,30805,30783,30760,30738,30716,30694,30672,30649,30627,30605,30583,30560,30538,30516,30494,30472,30449,30427,30405,30382,30360,30338,30316,30293,30271,30249,30226,30204,30182,30160,30137,30115,30093,30070,30048,30026,30003,29981,29959,29936,29914,29891,29869,29847,29824,29802,29780,29757,29735,29712,29690,29668,29645,29623,29600,29578,29555,29533,29511,29488,29466,29443,29421,29398,29376,29353,29331,29308,29286,29264,29241,29219,29196,29174,29151,29129,29106,29083,29061,29038,29016,28993,28971,28948,28926,28903,28881,28858,28835,28813,28790,28768,28745,28723,28700,28677,28655,28632,28610,28587,28564,28542,28519,28496,28474,28451,28429,28406,28383,28361,28338,28315,28293,28270,28247,28225,28202,28179,28156,28134,28111,28088,28066,28043,28020,27998,27975,27952,27929,27907,27884,27861,27838,27816,27793,27770,27747,27725,27702,27679,27656,27633,27611,27588,27565,27542,27519,27497,27474,27451,27428,27405,27382,27360,27337,27314,27291,27268,27245,27223,27200,27177,27154,27131,27108,27085,27062,27040,27017,26994,26971,26948,26925,26902,26879,26856,26833,26810,26787,26765,26742,26719,26696,26673,26650,26627,26604,26581,26558,26535,26512,26489,26466,26443,26420,26397,26374,26351,26328,26305,26282,26259,26236,26213,26190,26167,26144,26121,26098,26075,26051,26028,26005,25982,25959,25936,25913,25890,25867,25844,25821,25798,25774,25751,25728,25705,25682,25659,25636,25613,25589,25566,25543,25520,25497,25474,25451,25427,25404,25381,25358,25335,25312,25288,25265,25242,25219,25196,25172,25149,25126,25103,25080,25056,25033,25010,24987,24963,24940,24917,24894,24870,24847,24824,24801,24777,24754,24731,24708,24684,24661,24638,24614,24591,24568,24545,24521,24498,24475,24451,24428,24405,24381,24358,24335,24311,24288,24265,24241,24218,24195,24171,24148,24124,24101,24078,24054,24031,24008,23984,23961,23937,23914,23891,23867,23844,23820,23797,23774,23750,23727,23703,23680,23656,23633,23610,23586,23563,23539,23516,23492,23469,23445,23422,23398,23375,23351,23328,23304,23281,23257,23234,23210,23187,23163,23140,23116,23093,23069,23046,23022,22999,22975,22952,22928,22905,22881,22858,22834,22810,22787,22763,22740,22716,22693,22669,22645,22622,22598,22575,22551,22527,22504,22480,22457,22433,22409,22386,22362,22339,22315,22291,22268,22244,22220,22197,22173,22149,22126,22102,22078,22055,22031,22007,21984,21960,21936,21913,21889,21865,21842,21818,21794,21771,21747,21723,21699,21676,21652,21628,21604,21581,21557,21533,21510,21486,21462,21438,21415,21391,21367,21343,21320,21296,21272,21248,21224,21201,21177,21153,21129,21106,21082,21058,21034,21010,20987,20963,20939,20915,20891,20867,20844,20820,20796,20772,20748,20724,20701,20677,20653,20629,20605,20581,20557,20534,20510,20486,20462,20438,20414,20390,20366,20343,20319,20295,20271,20247,20223,20199,20175,20151,20127,20103,20080,20056,20032,20008,19984,19960,19936,19912,19888,19864,19840,19816,19792,19768,19744,19720,19696,19672,19648,19624,19600,19577,19553,19529,19505,19481,19457,19433,19409,19385,19361,19337,19313,19288,19264,19240,19216,19192,19168,19144,19120,19096,19072,19048,19024,19000,18976,18952,18928,18904,18880,18856,18832,18808,18783,18759,18735,18711,18687,18663,18639,18615,18591,18567,18543,18518,18494,18470,18446,18422,18398,18374,18350,18325,18301,18277,18253,18229,18205,18181,18156,18132,18108,18084,18060,18036,18012,17987,17963,17939,17915,17891,17867,17842,17818,17794,17770,17746,17721,17697,17673,17649,17625,17600,17576,17552,17528,17504,17479,17455,17431,17407,17382,17358,17334,17310,17285,17261,17237,17213,17188,17164,17140,17116,17091,17067,17043,17019,16994,16970,16946,16922,16897,16873,16849,16824,16800,16776,16751,16727,16703,16679,16654,16630,16606,16581,16557,16533,16508,16484,16460,16435,16411,16387,16362,16338,16314,16289,16265,16241,16216,16192,16168,16143,16119,16095,16070,16046,16021,15997,15973,15948,15924,15900,15875,15851,15826,15802,15778,15753,15729,15704,15680,15656,15631,15607,15582,15558,15534,15509,15485,15460,15436,15411,15387,15363,15338,15314,15289,15265,15240,15216,15192,15167,15143,15118,15094,15069,15045,15020,14996,14971,14947,14922,14898,14874,14849,14825,14800,14776,14751,14727,14702,14678,14653,14629,14604,14580,14555,14531,14506,14482,14457,14433,14408,14384,14359,14334,14310,14285,14261,14236,14212,14187,14163,14138,14114,14089,14065,14040,14016,13991,13966,13942,13917,13893,13868,13844,13819,13794,13770,13745,13721,13696,13672,13647,13622,13598,13573,13549,13524,13499,13475,13450,13426,13401,13376,13352,13327,13303,13278,13253,13229,13204,13180,13155,13130,13106,13081,13056,13032,13007,12983,12958,12933,12909,12884,12859,12835,12810,12785,12761,12736,12711,12687,12662,12638,12613,12588,12564,12539,12514,12490,12465,12440,12415,12391,12366,12341,12317,12292,12267,12243,12218,12193,12169,12144,12119,12095,12070,12045,12020,11996,11971,11946,11922,11897,11872,11847,11823,11798,11773,11749,11724,11699,11674,11650,11625,11600,11575,11551,11526,11501,11476,11452,11427,11402,11377,11353,11328,11303,11278,11254,11229,11204,11179,11155,11130,11105,11080,11056,11031,11006,10981,10956,10932,10907,10882,10857,10833,10808,10783,10758,10733,10709,10684,10659,10634,10609,10585,10560,10535,10510,10485,10461,10436,10411,10386,10361,10336,10312,10287,10262,10237,10212,10188,10163,10138,10113,10088,10063,10039,10014,9989,9964,9939,9914,9890,9865,9840,9815,9790,9765,9740,9716,9691,9666,9641,9616,9591,9566,9542,9517,9492,9467,9442,9417,9392,9367,9343,9318,9293,9268,9243,9218,9193,9168,9144,9119,9094,9069,9044,9019,8994,8969,8944,8919,8895,8870,8845,8820,8795,8770,8745,8720,8695,8670,8646,8621,8596,8571,8546,8521,8496,8471,8446,8421,8396,8371,8346,8322,8297,8272,8247,8222,8197,8172,8147,8122,8097,8072,8047,8022,7997,7972,7947,7923,7898,7873,7848,7823,7798,7773,7748,7723,7698,7673,7648,7623,7598,7573,7548,7523,7498,7473,7448,7423,7398,7373,7348,7323,7298,7273,7249,7224,7199,7174,7149,7124,7099,7074,7049,7024,6999,6974,6949,6924,6899,6874,6849,6824,6799,6774,6749,6724,6699,6674,6649,6624,6599,6574,6549,6524,6499,6474,6449,6424,6399,6374,6349,6324,6299,6274,6249,6224,6199,6173,6148,6123,6098,6073,6048,6023,5998,5973,5948,5923,5898,5873,5848,5823,5798,5773,5748,5723,5698,5673,5648,5623,5598,5573,5548,5523,5498,5473,5448,5422,5397,5372,5347,5322,5297,5272,5247,5222,5197,5172,5147,5122,5097,5072,5047,5022,4997,4972,4946,4921,4896,4871,4846,4821,4796,4771,4746,4721,4696,4671,4646,4621,4596,4570,4545,4520,4495,4470,4445,4420,4395,4370,4345,4320,4295,4270,4244,4219,4194,4169,4144,4119,4094,4069,4044,4019,3994,3969,3943,3918,3893,3868,3843,3818,3793,3768,3743,3718,3693,3667,3642,3617,3592,3567,3542,3517,3492,3467,3442,3417,3391,3366,3341,3316,3291,3266,3241,3216,3191,3165,3140,3115,3090,3065,3040,3015,2990,2965,2940,2914,2889,2864,2839,2814,2789,2764,2739,2714,2688,2663,2638,2613,2588,2563,2538,2513,2488,2462,2437,2412,2387,2362,2337,2312,2287,2261,2236,2211,2186,2161,2136,2111,2086,2061,2035,2010,1985,1960,1935,1910,1885,1860,1834,1809,1784,1759,1734,1709,1684,1659,1633,1608,1583,1558,1533,1508,1483,1458,1432,1407,1382,1357,1332,1307,1282,1257,1231,1206,1181,1156,1131,1106,1081,1056,1030,1005,980,955,930,905,880,854,829,804,779,754,729,704,679,653,628,603,578,553,528,503,478,452,427,402,377,352,327,302,276,251,226,201,176,151,126,101,75,50,25,0,0};
unsigned index = ((x.n + 0x4002) >> 2) & 0x3FFF;
if (0x1FFF < index) {
index = 0x4000 - index;
}
if ((int)index < 0x1000) {
return _fix32::from_bits(sin_tbl[index]);
}
return _fix32::from_bits(-sin_tbl[0x2000 - index]);
}
static int _fix32_floor(_fix32 x) {
return static_cast<int>(static_cast<unsigned>(x.n) & 0xFFFF0000) / (1<<16);
}
static _fix32 _fix32_min(_fix32 a, _fix32 b) {
return a > b ? b : a;
}
static _fix32 _fix32_max(_fix32 a, _fix32 b) {
return a > b ? a : b;
}
static _fix32 _fix32_abs(_fix32 x) {
return x >= 0 ? x : -x;
}
#define float _fix32
#else //CELESTE_P8_FIXEDP
#define float float
#endif //CELESTE_P8_FIXEDP
#ifdef __cplusplus
#define this xthis //this is a keyword in C++
#endif
//i cant be bothered to put all function declarations in an appropiate place so ill just toss them all here:
static void PRELUDE(void);
static void PRELUDE_initclouds(void);
static void PRELUDE_initparticles(void);
static void title_screen(void);
static void load_room(int x, int y);
static void next_room(void);
static void psfx(int num);
static void restart_room(void);
#define bool Celeste_P8_bool_t
#define false 0
#define true 1
static float clamp(float val, float a, float b);
static float appr(float val, float target, float amount);
static float sign(float v);
static bool maybe(void);
static bool solid_at(int x,int y,int w,int h);
static bool ice_at(int x,int y,int w,int h);
static bool tile_flag_at(int x,int y,int w,int h,int flag);
static int tile_at(int x,int y);
static bool spikes_at(float x,float y,int w,int h,float xspd,float yspd);
//exported /imported functions
static Celeste_P8_cb_func_t Celeste_P8_call = NULL;
//exported
void Celeste_P8_set_call_func(Celeste_P8_cb_func_t func) {
Celeste_P8_call = func;
}
static void pico8_srand(unsigned seed);
void Celeste_P8_set_rndseed(unsigned seed) {
pico8_srand(seed);
}
///////PICO-8 functions
static inline void P8music(int track, int fade, int mask) {
Celeste_P8_call(CELESTE_P8_MUSIC, track, fade, mask);
}
static inline void P8spr(int sprite, int x, int y, int cols, int rows, bool flipx, bool flipy) {
Celeste_P8_call(CELESTE_P8_SPR, sprite, x, y, cols, rows, flipx, flipy);
}
static inline bool P8btn(int b) {
return Celeste_P8_call(CELESTE_P8_BTN, b);
}
static inline void P8sfx(int id) {
Celeste_P8_call(CELESTE_P8_SFX, id);
}
static inline void P8pal(int a, int b) {
Celeste_P8_call(CELESTE_P8_PAL, a, b);
}
static inline void P8pal_reset() {
Celeste_P8_call(CELESTE_P8_PAL_RESET);
}
static inline void P8circfill(int x, int y, int r, int c) {
Celeste_P8_call(CELESTE_P8_CIRCFILL, x,y,r,c);
}
static inline void P8rectfill(int x, int y, int x2, int y2, int c) {
Celeste_P8_call(CELESTE_P8_RECTFILL, x,y,x2,y2,c);
}
static inline void P8print(const char* str, int x, int y, int c) {
Celeste_P8_call(CELESTE_P8_PRINT, str,x,y,c);
}
static inline void P8line(int x, int y, int x2, int y2, int c) {
Celeste_P8_call(CELESTE_P8_LINE, x,y,x2,y2,c);
}
static inline int P8mget(int x, int y) {
return Celeste_P8_call(CELESTE_P8_MGET, x,y);
}
static inline bool P8fget(int t, int f) {
return Celeste_P8_call(CELESTE_P8_FGET, t,f);
}
static inline void P8camera(int x, int y) {
Celeste_P8_call(CELESTE_P8_CAMERA, x, y);
}
static inline void P8map(int mx, int my, int tx, int ty, int mw, int mh, int mask) {
Celeste_P8_call(CELESTE_P8_MAP, mx, my, tx, ty, mw, mh, mask);
}
//these values dont matter as set_rndseed should be called before init, as long as they arent both zero
static unsigned rnd_seed_lo = 0, rnd_seed_hi = 1;
static int pico8_random(int max) { //decomp'd pico-8
if (!max) return 0;
rnd_seed_hi = ((rnd_seed_hi << 16) | (rnd_seed_hi >> 16)) + rnd_seed_lo;
rnd_seed_lo += rnd_seed_hi;
return rnd_seed_hi % (unsigned)max;
};
static void pico8_srand(unsigned seed) { //also decomp'd
if (seed == 0) {
rnd_seed_hi = 0x60009755;
seed = 0xdeadbeef;
} else {
rnd_seed_hi = seed ^ 0xbead29ba;
}
for (int i = 0x20; i > 0; i--) {
rnd_seed_hi = ((rnd_seed_hi << 16) | (rnd_seed_hi >> 16)) + seed;
seed += rnd_seed_hi;
}
rnd_seed_lo = seed;
}
#ifndef CELESTE_P8_FIXEDP
// https://github.com/lemon-sherbet/ccleste/issues/1
static float P8modulo(float a, float b) {
return fmodf(fmodf(a, b) + b, b);
}
#define P8max fmaxf
#define P8min fminf
#define P8abs fabsf
#define P8flr floorf
static float P8rnd(float max) {
int n = pico8_random(max * (1<<16));
return (float)n / (1<<16);
}
static float P8sin(float x) {
return -sinf(x*6.2831853071796f); //https://pico-8.fandom.com/wiki/Math
}
#else //CELESTE_P8_FIXEDP
#define P8modulo _fix32_mod
#define P8max _fix32_max
#define P8min _fix32_min
#define P8abs _fix32_abs
#define P8flr _fix32_floor
static _fix32 P8rnd(_fix32 max) {
return _fix32::from_bits(pico8_random(max.n));
}
#define P8sin _fix32_sin
#endif
#define P8cos(x) (-P8sin((x)+0.25f)) //cos(x) = sin(x+pi/2)
#ifdef CELESTE_P8_FIXEDP
//these need explicit casts to int
#define P8pal(_a,_b) P8pal(int(_a), int(_b))
#define P8spr(_s,_x,_y,_c,_r,_fx,_fy) P8spr(int(_s), int(_x), int(_y), (_c),(_r), (_fx),(_fy))
#define P8circfill(_x,_y,_r,_c) P8circfill(int(_x), int(_y), int(_r), (_c))
#define P8rectfill(_x0,_y0,_x1,_y1,_c) P8rectfill(int(_x0),int(_y0),int(_x1),int(_y1), int(_c))
#define P8print(_s,_x,_y,_c) P8print(_s,int(_x),int(_y),(_c))
#define P8line(_x0,_y0,_x1,_y1,_c) P8line(int(_x0),int(_y0),int(_x1),int(_y1),(_c))
#define P8camera(_x,_y) P8camera(int(_x),int(_y))
static inline bool ice_at(float x,float y,float w,float h) { return ice_at(int(x),int(y),int(w),int(h)); }
static inline bool solid_at(float x,float y,float w,float h) { return solid_at(int(x),int(y),int(w),int(h)); }
#endif
#define MAX_OBJECTS 30
#define FRUIT_COUNT 30
////////////////////////////////////////////////
// ~celeste~
// matt thorson + noel berry
// globals
//////////////
typedef struct {float x,y;} VEC;
typedef struct {int x,y;} VECI;
static VECI room = {.x=0,.y=0};
//static int num_objects = 0;
static int freeze = 0;
static int shake = 0;
static bool will_restart = false;
static int delay_restart = 0;
static bool got_fruit[FRUIT_COUNT] = {false};
static bool has_dashed = false;
static int sfx_timer = 0;
static bool has_key = false;
static bool pause_player = false;
static bool flash_bg = false;
static int music_timer = 0;
//these are originally implicit globals defined in title_screen()
static bool new_bg = false;
static int frames, seconds;
static short minutes; //this variable can overflow in normal gameplay (after +500 hours)
static int deaths, max_djump;
static bool start_game;
static int start_game_flash;
enum {
k_left = 0,
k_right = 1,
k_up = 2,
k_down = 3,
k_jump = 4,
k_dash = 5
};
//with this X macro table thing we can define the properties that each object type has, in the original lua code these properties
//are inferred from the `types` table
#define OBJ_PROP_LIST() \
/* TYPE TILE HAS INIT HAS UPDATE HAS DRAW IF_NOT_FRUIT */\
X(PLAYER, -1, Y, Y, Y, false)\
X(PLAYER_SPAWN, 1, Y, Y, Y, false)\
X(SPRING, 18, Y, Y, N, false)\
X(BALLOON, 22, Y, Y, Y, false)\
X(SMOKE, -1, Y, Y, N, false)\
X(PLATFORM, -1, Y, Y, Y, false)\
X(FALL_FLOOR, 23, Y, Y, Y, false)\
X(FRUIT, 26, Y, Y, N, true)\
X(FLY_FRUIT, 28, Y, Y, Y, true)\
X(FAKE_WALL, 64, N, Y, Y, true)\
X(KEY, 8, N, Y, N, true)\
X(CHEST, 20, Y, Y, N, true)\
X(LIFEUP, -1, Y, Y, Y, false)\
X(MESSAGE, 86, N, N, Y, false)\
X(BIG_CHEST, 96, Y, N, Y, false)\
X(ORB, -1, Y, N, Y, false)\
X(FLAG, 118, Y, N, Y, false)\
X(ROOM_TITLE, -1, Y, N, Y, false)
typedef enum {
#define X(t,...) OBJ_##t,
OBJ_PROP_LIST()
#undef X
OBJTYPE_COUNT
} OBJTYPE;
// entry point //
/////////////////
static void PRELUDE() {
//top-level init code has been moved into functions that are called here
PRELUDE_initclouds();
PRELUDE_initparticles();
}
void Celeste_P8_init() { //identifiers beginning with underscores are reserved in C
if (!Celeste_P8_call) {
fprintf(stderr, "Warning: Celeste_P8_call is NULL.. have you called Celeste_P8_set_call_func()?\n");
}
PRELUDE();
title_screen();
}
static void title_screen() {
for (int i = 0; i <= 29; i++)
got_fruit[i] = false;
frames=0;
deaths=0;
max_djump=1;
start_game=false;
start_game_flash=0;
P8music(40,0,7);
load_room(7,3);
}
static void begin_game() {
frames=0;
seconds=0;
minutes=0;
music_timer=0;
start_game=false;
P8music(0,0,7);
load_room(0,0);
}
static int level_index() {
return room.x%8+room.y*8;
}
static bool is_title() {
return level_index()==31;
}
// effects //
/////////////
typedef struct {
float x,y,spd,w;
} CLOUD;
static CLOUD clouds[17];
//top level init code has been moved into a function
static void PRELUDE_initclouds() {
for (int i=0; i<=16; i++) {
clouds[i] = (CLOUD){
.x=P8rnd(128),
.y=P8rnd(128),
.spd=1+P8rnd(4),
.w=32+P8rnd(32),
};
}
}
typedef struct {
bool active;
float x,y,s,spd,off,c,h,t;
VEC spd2; //used by dead particles, moved from spd
} PARTICLE;
static PARTICLE particles[25];
static PARTICLE dead_particles[8];
//top level init code has been moved into a function
static void PRELUDE_initparticles() {
for (int i=0; i<=24; i++) {
particles[i] = (PARTICLE){
.x=P8rnd(128),
.y=P8rnd(128),
.s=0+P8flr(P8rnd(5)/4),
.spd=0.25f+P8rnd(5),
.off=P8rnd(1),
.c=6+P8flr(0.5+P8rnd(1))
};
}
}
typedef struct {int x,y,w,h;} HITBOX;
typedef struct {
float x,y,size;
bool isLast;
} HAIR;
//OBJECT strucutre
typedef struct {
bool active;
short id; //unique identifier for each object, incremented per object
//inherited
OBJTYPE type;
bool collideable, solids;
float spr;
bool flip_x, flip_y;
float x, y;
HITBOX hitbox;
VEC spd;
VEC rem;
//player
bool p_jump, p_dash;
int grace, jbuffer, djump, dash_time;
short dash_effect_time; //can underflow in normal gameplay (after 18 minutes)
VEC dash_target;
VEC dash_accel;
float spr_off;
bool was_on_ground;
HAIR hair[5]; //also player_spawn
//player_spawn
int state, delay;
VEC target;
//spring
int hide_in, hide_for;
//balloon
int timer;
float offset, start;
//fruit
float off;
//fly_fruit
bool fly;
float step;
int sfx_delay;
//lifeup
int duration;
float flash;
//platform
float last, dir;
//message
const char* text;
float index;
VECI off2; //changed from off..
//big chest
PARTICLE particles[50];
int particle_count;
//flag
int score;
bool show;
} OBJ;
//OBJ function declarations fuckery
#define when_Y(x) static void x(OBJ* this);
#define when_N(x) enum { x = 0 }; //OBJTYPE_prop definition requires a constant value, and `static cost void* x = NULL` doesn't count
#define X(name,t,has_init,has_update,has_draw,if_not_fruit) \
when_##has_init (name##_init)\
when_##has_update (name##_update)\
when_##has_draw (name##_draw)
OBJ_PROP_LIST()
#undef X
typedef void (*obj_callback_t)(OBJ*);
struct objprop {
int tile;
obj_callback_t init;
obj_callback_t update;
obj_callback_t draw;
const char* nam;
bool if_not_fruit;
};
static const struct objprop OBJTYPE_prop[] = {
#define X(name,t,has_init,has_update,has_draw,_if_not_fruit) \
[OBJ_##name] = { \
.tile = t,\
.init = (obj_callback_t)name##_init, \
.update = (obj_callback_t)name##_update, \
.draw = (obj_callback_t)name##_draw, \
.nam = #name, \
.if_not_fruit = _if_not_fruit \
},
OBJ_PROP_LIST()
#undef X
{0}
};
#define OBJ_PROP(o) OBJTYPE_prop[(o)->type]
static OBJ objects[MAX_OBJECTS] = {{.active = false}};
static void create_hair(OBJ* obj);
static void set_hair_color(int c);
static void draw_hair(OBJ* obj, int facing);
static void unset_hair_color(void);
static void kill_player(OBJ* obj);
static void break_fall_floor(OBJ* obj);
static void draw_time(float x, float y);
static OBJ* init_object(OBJTYPE type, float x, float y);
static void destroy_object(OBJ* obj);
static void draw_object(OBJ* obj);
//OBJECT FUNCTIONS MOVED HERE
static bool OBJ_is_solid(OBJ* obj, float ox, float oy);
static bool OBJ_is_ice(OBJ* obj, float ox, float oy);
static OBJ* OBJ_collide(OBJ* obj, OBJTYPE type, float ox, float oy);
static bool OBJ_check(OBJ* obj, OBJTYPE type, float ox, float oy);
static void OBJ_move(OBJ* obj, float ox, float oy);
static void OBJ_move_x(OBJ* obj, float amount, float start);
static void OBJ_move_y(OBJ* obj, float amount);
static bool OBJ_is_solid(OBJ* obj, float ox, float oy) {
if (oy>0 && !OBJ_check(obj, OBJ_PLATFORM,ox,0) && OBJ_check(obj, OBJ_PLATFORM,ox,oy)) {
return true;
}
return solid_at(obj->x+obj->hitbox.x+ox,obj->y+obj->hitbox.y+oy,obj->hitbox.w,obj->hitbox.h)
|| OBJ_check(obj, OBJ_FALL_FLOOR,ox,oy)
|| OBJ_check(obj, OBJ_FAKE_WALL,ox,oy);
}
static bool OBJ_is_ice(OBJ* obj, float ox, float oy) {
return ice_at(obj->x+obj->hitbox.x+ox,obj->y+obj->hitbox.y+oy,obj->hitbox.w,obj->hitbox.h);
}
static OBJ* OBJ_collide(OBJ* obj, OBJTYPE type, float ox, float oy) {
OBJ* other;
for (int i=0; i < MAX_OBJECTS; i++) {
other=&objects[i];
if (other->active && other->type == type && other != obj && other->collideable &&
other->x+other->hitbox.x+other->hitbox.w > obj->x+obj->hitbox.x+ox &&
other->y+other->hitbox.y+other->hitbox.h > obj->y+obj->hitbox.y+oy &&
other->x+other->hitbox.x < obj->x+obj->hitbox.x+obj->hitbox.w+ox &&
other->y+other->hitbox.y < obj->y+obj->hitbox.y+obj->hitbox.h+oy) {
return other;
}
}
return NULL;
}
static bool OBJ_check(OBJ* obj, OBJTYPE type, float ox, float oy) {
return OBJ_collide(obj, type,ox,oy) != NULL;
}
static void OBJ_move(OBJ* obj, float ox, float oy) {
float amount;
// [x] get move amount
obj->rem.x += ox;
amount = P8flr(obj->rem.x + 0.5);
obj->rem.x -= amount;
OBJ_move_x(obj, amount,0);
// [y] get move amount
obj->rem.y += oy;
amount = P8flr(obj->rem.y + 0.5);
obj->rem.y -= amount;
OBJ_move_y(obj, amount);
}
static void OBJ_move_x(OBJ* obj, float amount, float start) {
if (obj->solids) {
float step = sign(amount);
for (float i=start; i <= P8abs(amount); i+=1) {
if (!OBJ_is_solid(obj, step,0)) {
obj->x += step;
} else {
obj->spd.x = 0;
obj->rem.x = 0;
break;
}
}
} else {
obj->x += amount;
}
}
static void OBJ_move_y(OBJ* obj, float amount) {
if (obj->solids) {
float step = sign(amount);
for (int i=0; i <= P8abs(amount); i++) {
if (!OBJ_is_solid(obj,0,step)) {
obj->y += step;
} else {
obj->spd.y = 0;
obj->rem.y = 0;
break;
}
}
} else {
obj->y += amount;
}
}
// player entity //
///////////////////
static void PLAYER_init(OBJ* this) {
this->p_jump=false;
this->p_dash=false;
this->grace=0;
this->jbuffer=0;
this->djump=max_djump;
this->dash_time=0;
this->dash_effect_time=0;
this->dash_target=(VEC){.x=0,.y=0};
this->dash_accel=(VEC){.x=0,.y=0};
this->hitbox = (HITBOX){.x=1,.y=3,.w=6,.h=5};
this->spr_off=0;
this->was_on_ground=false;
create_hair(this);
}
static OBJ player_dummy_copy; //see below
static void PLAYER_update(OBJ* this) {
if (pause_player) return;
int input = P8btn(k_right) ? 1 : (P8btn(k_left) ? -1 : 0);
/*LEMON: in order to kill the player in these lines, while maintaining object slots in the same order as they would be in pico-8,
* we need to remove the object there but that shifts back the objects array which will make it so the rest of the player_update()
* function modifies data from a newly loaded object; which is bad, so we simulate the pico-8 behaviour of reading from and writing to
* a table that is not referenced in the objects table by switching to a dummy copy of the player object */
bool do_kill_player = false;
// spikes collide
if (spikes_at(this->x+this->hitbox.x,this->y+this->hitbox.y,this->hitbox.w,this->hitbox.h,this->spd.x,this->spd.y)) {
do_kill_player = true;
}
// bottom death
if (this->y>128) {
do_kill_player = true;
}
if (do_kill_player) {
//switch to dummy copy, need to copy before destroying the object
player_dummy_copy = *this;
kill_player(this);
this = &player_dummy_copy;
}
bool on_ground=OBJ_is_solid(this, 0,1);
bool on_ice=OBJ_is_ice(this, 0,1);
// smoke particles
if (on_ground && !this->was_on_ground) {
init_object(OBJ_SMOKE,this->x,this->y+4);
}
bool jump = P8btn(k_jump) && !this->p_jump;
this->p_jump = P8btn(k_jump);
if ((jump)) {
this->jbuffer=4;
} else if (this->jbuffer>0) {
this->jbuffer-=1;
}
bool dash = P8btn(k_dash) && !this->p_dash;
this->p_dash = P8btn(k_dash);
if (on_ground) {
this->grace=6;
if (this->djump<max_djump) {
psfx(54);
this->djump=max_djump;
}
} else if (this->grace > 0) {
this->grace-=1;
}
this->dash_effect_time -=1;
if (this->dash_time > 0) {
init_object(OBJ_SMOKE, this->x,this->y);
this->dash_time-=1;
this->spd.x=appr(this->spd.x,this->dash_target.x,this->dash_accel.x);
this->spd.y=appr(this->spd.y,this->dash_target.y,this->dash_accel.y);
} else {
// move
int maxrun=1;
float accel=0.6;
float deccel=0.15;
if (!on_ground) {
accel=0.4;
} else if (on_ice) {
accel=0.05;
if (input==(this->flip_x ? -1 : 1)) {
accel=0.05;
}
}
if (P8abs(this->spd.x) > maxrun) {
this->spd.x=appr(this->spd.x,sign(this->spd.x)*maxrun,deccel);
} else {
this->spd.x=appr(this->spd.x,input*maxrun,accel);
}
//facing
if (this->spd.x!=0) {
this->flip_x=(this->spd.x<0);
}
// gravity
float maxfall=2;
float gravity=0.21;
if (P8abs(this->spd.y) <= 0.15) {
gravity*=0.5;
}
// wall slide
if (input!=0 && OBJ_is_solid(this, input,0) && !OBJ_is_ice(this, input,0)) {
maxfall=0.4;
if (P8rnd(10)<2) {
init_object(OBJ_SMOKE,this->x+input*6,this->y);
}
}
if (!on_ground) {
this->spd.y=appr(this->spd.y,maxfall,gravity);
}
// jump
if (this->jbuffer>0) {
if (this->grace>0) {
// normal jump
psfx(1);
this->jbuffer=0;
this->grace=0;
this->spd.y=-2;
init_object(OBJ_SMOKE,this->x,this->y+4);
} else {
// wall jump
int wall_dir=(OBJ_is_solid(this, -3,0) ? -1 : (OBJ_is_solid(this, 3,0) ? 1 : 0));
if (wall_dir!=0) {
psfx(2);
this->jbuffer=0;
this->spd.y=-2;
this->spd.x=-wall_dir*(maxrun+1);
if (!OBJ_is_ice(this, wall_dir*3,0)) {
init_object(OBJ_SMOKE,this->x+wall_dir*6,this->y);
}
}
}
}
// dash
float d_full=5;
float d_half=d_full*0.70710678118;
if (this->djump>0 && dash) {
init_object(OBJ_SMOKE,this->x,this->y);
this->djump-=1;
this->dash_time=4;
has_dashed=true;
this->dash_effect_time=10;
int v_input=(P8btn(k_up) ? -1 : (P8btn(k_down) ? 1 : 0));
if (input!=0) {
if (v_input!=0) {
this->spd.x=input*d_half;
this->spd.y=v_input*d_half;
} else {
this->spd.x=input*d_full;
this->spd.y=0;
}
} else if (v_input!=0) {
this->spd.x=0;
this->spd.y=v_input*d_full;
} else {
this->spd.x=(this->flip_x ? -1 : 1);
this->spd.y=0;
}
psfx(3);
freeze=2;
shake=6;
this->dash_target.x=2*sign(this->spd.x);
this->dash_target.y=2*sign(this->spd.y);
this->dash_accel.x=1.5;
this->dash_accel.y=1.5;
if (this->spd.y<0) {
this->dash_target.y*=.75;
}
if (this->spd.y!=0) {
this->dash_accel.x*=0.70710678118f;
}
if (this->spd.x!=0) {
this->dash_accel.y*=0.70710678118f;
}
} else if (dash && this->djump<=0) {
psfx(9);
init_object(OBJ_SMOKE,this->x,this->y);
}
}
// animation
this->spr_off+=0.25;
if (!on_ground) {
if (OBJ_is_solid(this, input,0)) {
this->spr=5;
} else {
this->spr=3;
}
} else if (P8btn(k_down)) {
this->spr=6;
} else if (P8btn(k_up)) {
this->spr=7;
} else if ((this->spd.x==0) || (!P8btn(k_left) && !P8btn(k_right))) {
this->spr=1;
} else {
this->spr=1+((int)this->spr_off)%4;
}
// next level
if (this->y<-4 && level_index()<30) { next_room(); }
// was on the ground
this->was_on_ground=on_ground;
}
static void PLAYER_draw(OBJ* this) {
// clamp in screen
if (this->x<-1 || this->x>121) {
this->x=clamp(this->x,-1,121);
this->spd.x=0;
}
set_hair_color(this->djump);
draw_hair(this,this->flip_x ? -1 : 1);
P8spr(this->spr,this->x,this->y,1,1,this->flip_x,this->flip_y);
unset_hair_color();
}
static void psfx(int num) {
if (sfx_timer<=0) {
P8sfx(num);
}
}
void create_hair(OBJ* obj) {
/*obj->hair = {};*/
for (int i=0;i<=4;i++) {
obj->hair[i] = (HAIR) {.x=obj->x,.y=obj->y,.size=P8max(1,P8min(2,3-i)), .isLast = i == 4};
}
}
static void set_hair_color(int djump) {
P8pal(8,(djump==1 ? 8 : (djump==2 ?(7+P8flr(((int)(((float)frames)/3.0))%2)*4) : 12)));
}
static void draw_hair(OBJ* obj, int facing) {
float last_x=obj->x+4-facing*2;
float last_y=obj->y+(P8btn(k_down) ? 4 : 3);
HAIR* h;
int i = 0;
do {
h = &obj->hair[i++];
h->x+=(last_x-h->x)/1.5;
h->y+=(last_y+0.5-h->y)/1.5;
P8circfill(h->x,h->y,h->size,8);
last_x=h->x;
last_y=h->y;
} while (!h->isLast);
}
static void unset_hair_color() {
P8pal(8,8);
}
//player_spawn
static void PLAYER_SPAWN_init(OBJ* this) {
P8sfx(4);
this->spr=3;
this->target.x=this->x;
this->target.y=this->y;
this->y=128;
this->spd.y=-4;
this->state=0;
this->delay=0;
this->solids=false;
create_hair(this);
}
static void PLAYER_SPAWN_update(OBJ* this) {
// jumping up
if (this->state==0) {
if (this->y < this->target.y+16) {
this->state=1;
this->delay=3;
}
// falling
} else if (this->state==1) {
this->spd.y+=0.5;
if (this->spd.y>0 && this->delay>0) {
this->spd.y=0;
this->delay-=1;
}
if (this->spd.y>0 && this->y > this->target.y) {
this->y=this->target.y;
this->spd.x = this->spd.y=0;
this->state=2;
this->delay=5;
shake=5;
init_object(OBJ_SMOKE,this->x,this->y+4);
P8sfx(5);
}
// landing
} else if (this->state==2) {
this->delay-=1;
this->spr=6;
if (this->delay<0) {
float x = this->x, y = this->y;
destroy_object(this);
init_object(OBJ_PLAYER,x,y);
}
}
}
static void PLAYER_SPAWN_draw (OBJ* this) {
set_hair_color(max_djump);
draw_hair(this,1);
P8spr(this->spr,this->x,this->y,1,1,this->flip_x,this->flip_y);
unset_hair_color();
}
//spring
static void SPRING_init(OBJ* this) {
this->hide_in=0;
this->hide_for=0;
}
static void SPRING_update(OBJ* this) {
if (this->hide_for>0) {
this->hide_for-=1;
if (this->hide_for<=0) {
this->spr=18;
this->delay=0;
}
} else if (this->spr==18) {
OBJ* hit = OBJ_collide(this, OBJ_PLAYER,0,0);
if (hit != NULL && hit->spd.y>=0) {
this->spr=19;
hit->y=this->y-4;
hit->spd.x*=0.2;
hit->spd.y=-3;
hit->djump=max_djump;
this->delay=10;
init_object(OBJ_SMOKE,this->x,this->y);
// breakable below us
OBJ* below=OBJ_collide(this, OBJ_FALL_FLOOR,0,1);
if (below != NULL) {
break_fall_floor(below);
}
psfx(8);
}
} else if (this->delay>0) {
this->delay-=1;
if (this->delay<=0) {
this->spr=18;
}
}
// begin hiding
if (this->hide_in>0) {
this->hide_in-=1;
if (this->hide_in<=0) {
this->hide_for=60;
this->spr=0;
}
}
}
static void break_spring(OBJ* obj){
obj->hide_in=15;
}
//balloon
static void BALLOON_init(OBJ* this) {
this->offset=P8rnd(1);
this->start=this->y;
this->timer=0;
this->hitbox=(HITBOX){.x=-1,.y=-1,.w=10,.h=10};
}
static void BALLOON_update(OBJ* this) {
if (this->spr==22) {
this->offset+=0.01;
#ifdef CELESTE_P8_HACKED_BALLOONS
//hacked balloons: constant y coord and hitbox. for TASes
this->hitbox=(HITBOX){.x=-1,.y=-3,.w=10,.h=14};
#else
this->y=this->start+P8sin(this->offset)*2;
#endif
OBJ* hit = OBJ_collide(this, OBJ_PLAYER, 0,0);
if (hit != NULL && hit->djump<max_djump) {
psfx(6);
init_object(OBJ_SMOKE,this->x,this->y);
hit->djump=max_djump;
this->spr=0;
this->timer=60;
}
} else if (this->timer>0) {
this->timer-=1;
} else {
psfx(7);
init_object(OBJ_SMOKE,this->x,this->y);
this->spr=22;
}
}
static void BALLOON_draw(OBJ* this) {
if (this->spr==22) {
P8spr(13+(int)(this->offset*8)%3,this->x,this->y+6, 1,1,false,false);
P8spr(this->spr,this->x,this->y, 1,1,false,false);
}
}
//fall_floor
static void FALL_FLOOR_init(OBJ* this) {
this->state=0;
//this->solid=true; //this is a typo.. not fixing in order to maintain original behaviour
}
static void FALL_FLOOR_update(OBJ* this) {
// idling
if (this->state == 0) {
if (OBJ_check(this, OBJ_PLAYER,0,-1) || OBJ_check(this, OBJ_PLAYER,-1,0) || OBJ_check(this, OBJ_PLAYER,1,0)) {
break_fall_floor(this);
}
// shaking
} else if (this->state==1) {
this->delay-=1;
if (this->delay<=0) {
this->state=2;
this->delay=60;//how long it hides for
this->collideable=false;
}
// invisible, waiting to reset
} else if (this->state==2) {
this->delay-=1;
if (this->delay<=0 && !OBJ_check(this, OBJ_PLAYER,0,0)) {
psfx(7);
this->state=0;
this->collideable=true;
init_object(OBJ_SMOKE,this->x,this->y);
}
}
}
static void FALL_FLOOR_draw(OBJ* this) {
if (this->state!=2) {
if (this->state!=1) {
P8spr(23,this->x,this->y, 1,1,false,false);
} else {
P8spr(23+(15-this->delay)/5,this->x,this->y, 1,1,false,false);
}
}
}
static void break_fall_floor(OBJ* obj) {
if (obj->state==0) {
psfx(15);
obj->state=1;
obj->delay=15;//how long until it falls
init_object(OBJ_SMOKE,obj->x,obj->y);
OBJ* hit=OBJ_collide(obj, OBJ_SPRING,0,-1);
if (hit != NULL) {
break_spring(hit);
}
}
}
//smoke
static void SMOKE_init(OBJ* this) {
this->spr=29;
this->spd.y=-0.1;
this->spd.x=0.3+P8rnd(0.2);
this->x+=-1+P8rnd(2);
this->y+=-1+P8rnd(2);
this->flip_x=maybe();
this->flip_y=maybe();
this->solids=false;
}
static void SMOKE_update(OBJ* this) {
this->spr+=0.2;
if (this->spr>=32) {
destroy_object(this);
}
}
//fruit
//tile=26,
//if_not_fruit=true,
static void FRUIT_init(OBJ* this) {
this->start=this->y;
this->off=0;
}
static void FRUIT_update(OBJ* this) {
OBJ* hit=OBJ_collide(this, OBJ_PLAYER,0,0);
if (hit!=NULL) {
hit->djump=max_djump;
sfx_timer=20;
P8sfx(13);
got_fruit[level_index()] = true;
init_object(OBJ_LIFEUP,this->x,this->y);
destroy_object(this);
return; //LEMON: added return to not modify dead object
}
this->off+=1;
this->y=this->start+P8sin(this->off/40)*2.5f;
}
//fly_fruit
//tile=28,
//if_not_fruit=true,
static void FLY_FRUIT_init(OBJ* this) {
this->start=this->y;
this->fly=false;
this->step=0.5;
this->solids=false;
this->sfx_delay=8;
}
static void FLY_FRUIT_update(OBJ* this) {
bool do_destroy_object = false; //LEMON: see PLAYER_update..
//fly away
if (this->fly) {
if (this->sfx_delay>0) {
this->sfx_delay-=1;
if (this->sfx_delay<=0) {
sfx_timer=20;
P8sfx(14);
}
}
this->spd.y=appr(this->spd.y,-3.5,0.25);
if (this->y<-16) {
do_destroy_object = true;
}
// wait
} else {
if (has_dashed) {
this->fly=true;
}
this->step+=0.05;
this->spd.y=P8sin(this->step)*0.5;
}
// collect
OBJ* hit=OBJ_collide(this, OBJ_PLAYER,0,0);
if (hit!=NULL) {
hit->djump=max_djump;
sfx_timer=20;
P8sfx(13);
got_fruit[level_index()] = true;
init_object(OBJ_LIFEUP,this->x,this->y);
do_destroy_object = true;
}
if (do_destroy_object) destroy_object(this);
}
static void FLY_FRUIT_draw(OBJ* this) {
float off=0;
if (!this->fly) {
float dir=P8sin(this->step);
if (dir<0) {
off=1+P8max(0,sign(this->y-this->start));
}
} else {
off=P8modulo(off+0.25, 3);
}
P8spr(45+off,this->x-6,this->y-2, 1,1,true,false);
P8spr(this->spr,this->x,this->y, 1,1,false,false);
P8spr(45+off,this->x+6,this->y-2, 1,1,false,false);
}
//lifeup
static void LIFEUP_init(OBJ* this) {
this->spd.y=-0.25;
this->duration=30;
this->x-=2;
this->y-=4;
this->flash=0;
this->solids=false;
}
static void LIFEUP_update(OBJ* this) {
this->duration-=1;
if (this->duration<= 0) {
destroy_object(this);
}
}
static void LIFEUP_draw(OBJ* this) {
this->flash+=0.5;
P8print("1000",this->x-2,this->y,7+((int)this->flash)%2);
}
//fake_wall
//tile=64,
//if_not_fruit=true,
static void FAKE_WALL_update(OBJ* this) {
this->hitbox=(HITBOX){.x=-1,.y=-1,.w=18,.h=18};
OBJ* hit = OBJ_collide(this, OBJ_PLAYER,0,0);
if (hit!=NULL && hit->dash_effect_time>0) {
hit->spd.x=-sign(hit->spd.x)*1.5;
hit->spd.y=-1.5;
hit->dash_time=-1;
sfx_timer=20;
P8sfx(16);
//destroy_object(this);
init_object(OBJ_SMOKE,this->x,this->y);
init_object(OBJ_SMOKE,this->x+8,this->y);
init_object(OBJ_SMOKE,this->x,this->y+8);
init_object(OBJ_SMOKE,this->x+8,this->y+8);
init_object(OBJ_FRUIT,this->x+4,this->y+4);
destroy_object(this); //LEMON: moved here. see PLAYER_update. also returning to avoid modifying removed object
return;
}
this->hitbox=(HITBOX){.x=0,.y=0,.w=16,.h=16};
}
static void FAKE_WALL_draw(OBJ* this) {
P8spr(64,this->x,this->y, 1,1,false,false);
P8spr(65,this->x+8,this->y, 1,1,false,false);
P8spr(80,this->x,this->y+8, 1,1,false,false);
P8spr(81,this->x+8,this->y+8, 1,1,false,false);
}
//key
//tile=8,
//if_not_fruit=true,
static void KEY_update(OBJ* this) {
int was=P8flr(this->spr);
this->spr=9+(P8sin((float)frames/30.0)+0.5)*1;
int is=P8flr(this->spr);
if (is==10 && is!=was) {
this->flip_x=!this->flip_x;
}
if (OBJ_check(this, OBJ_PLAYER,0,0)) {
P8sfx(23);
sfx_timer=10;
destroy_object(this);
has_key=true;
}
}
//chest
//tile=20,
//if_not_fruit=true,
static void CHEST_init(OBJ* this) {
this->x-=4;
this->start=this->x;
this->timer=20;
}
static void CHEST_update(OBJ* this) {
if (has_key) {
this->timer-=1;
this->x=this->start-1+P8rnd(3);
if (this->timer<=0) {
sfx_timer=20;
P8sfx(16);
init_object(OBJ_FRUIT,this->x,this->y-4);
destroy_object(this);
}
}
}
//platform
static void PLATFORM_init(OBJ* this) {
this->x-=4;
this->solids=false;
this->hitbox.w=16;
this->last=this->x;
}
static void PLATFORM_update(OBJ* this) {
this->spd.x=this->dir*0.65;
if (this->x<-16) { this->x=128;
} else if (this->x>128) { this->x=-16; }
if (!OBJ_check(this, OBJ_PLAYER,0,0)) {
OBJ* hit=OBJ_collide(this, OBJ_PLAYER,0,-1);
if (hit!=NULL) {
OBJ_move_x(hit, this->x-this->last,1);
}
}
this->last=this->x;
}
static void PLATFORM_draw(OBJ* this) {
P8spr(11,this->x,this->y-1, 1,1,false,false);
P8spr(12,this->x+8,this->y-1, 1,1,false,false);
}
//message
//tile=86,
//last=0,
static void MESSAGE_draw(OBJ* this) {
this->text="-- celeste mountain --#this memorial to those# perished on the climb";
if (OBJ_check(this, OBJ_PLAYER,4,0)) {
if (this->index<strlen(this->text)) {
this->index+=0.5;
if (this->index>=this->last+1) {
this->last+=1;
P8sfx(35);
}
}
this->off2.x=8;
this->off2.y=96;
for (int i=0; i<this->index; i++) {
if (this->text[i]!='#') {
P8rectfill(this->off2.x-2,this->off2.y-2,this->off2.x+7,this->off2.y+6, 7);
char charstr[2];
charstr[0] = this->text[i], charstr[1] = '\0';
P8print(charstr,this->off2.x,this->off2.y,0);
this->off2.x+=5;
} else {
this->off2.x=8;
this->off2.y+=7;
}
}
} else {
this->index=0;
this->last=0;
}
}
//big_chest
//tile=96,
static void BIG_CHEST_init(OBJ* this) {
this->state=0;
this->hitbox.w=16;
}
static void BIG_CHEST_draw(OBJ* this) {
if (this->state==0) {
OBJ* hit=OBJ_collide(this, OBJ_PLAYER,0,8);
if (hit!=NULL && OBJ_is_solid(hit, 0,1)) {
P8music(-1,500,7);
P8sfx(37);
pause_player=true;
hit->spd.x=0;
hit->spd.y=0;
this->state=1;
init_object(OBJ_SMOKE,this->x,this->y);
init_object(OBJ_SMOKE,this->x+8,this->y);
this->timer=60;
this->particle_count = 0;
}
P8spr(96,this->x,this->y, 1,1,false,false);
P8spr(97,this->x+8,this->y, 1,1,false,false);
} else if (this->state==1) {
this->timer-=1;
shake=5;
flash_bg=true;
if (this->timer<=45 && this->particle_count<50) {
this->particles[this->particle_count++] = (PARTICLE){
.x=1+P8rnd(14),
.y=0,
.spd=8+P8rnd(8),
.h=32+P8rnd(32)
};
}
if (this->timer<0) {
this->state=2;
this->particle_count=0;
flash_bg=false;
new_bg=true;
init_object(OBJ_ORB,this->x+4,this->y+4);
pause_player=false;
}
for (int i = 0; i < this->particle_count; i++) {
PARTICLE* p = &this->particles[i];
p->y+=p->spd;
P8line(this->x+p->x,this->y+8-p->y,this->x+p->x,P8min(this->y+8-p->y+p->h,this->y+8),7);
}
}
P8spr(112,this->x,this->y+8, 1,1,false,false);
P8spr(113,this->x+8,this->y+8, 1,1,false,false);
}
//orb
static void ORB_init(OBJ* this) {
this->spd.y=-4;
this->solids=false;
this->particle_count = 0;
}
static void ORB_draw(OBJ* this) {
this->spd.y=appr(this->spd.y,0,0.5);
OBJ* hit=OBJ_collide(this, OBJ_PLAYER,0,0);
bool destroy_self = false;
if (this->spd.y==0 && hit!=NULL) {
music_timer=45;
P8sfx(51);
freeze=10;
shake=10;
destroy_self = true; //LEMON: to avoid reading off dead object
max_djump=2;
hit->djump=2;
}
P8spr(102,this->x,this->y, 1,1,false,false);
float off=(float)frames/30.f;
for (float i=0; i <= 7; i+=1) {
P8circfill(this->x+4+P8cos(off+i/8.f)*8,this->y+4+P8sin(off+i/8.f)*8,1,7);
}
if (destroy_self) destroy_object(this);
}
//flag
//tile=118,
static void FLAG_init(OBJ* this) {
this->x+=5;
this->score=0;
this->show=false;
for (int i=0; i < FRUIT_COUNT; i++) {
if (got_fruit[i]) {
this->score+=1;
}
}
}
static void FLAG_draw(OBJ* this) {
this->spr=118+P8modulo(((float)frames/5.f), 3);
P8spr(this->spr,this->x,this->y, 1,1,false,false);
if (this->show) {
P8rectfill(32,2,96,31,0);
P8spr(26,55,6, 1,1,false,false);
{
char str[16];
snprintf(str, sizeof(str), "x%i", this->score);
P8print(str,64,9,7);
}
draw_time(49,16);
{
char str[16];
snprintf(str, sizeof(str), "deaths:%i", deaths);
P8print(str,48,24,7);
}
} else if (OBJ_check(this, OBJ_PLAYER,0,0)) {
P8sfx(55);
sfx_timer=30;
this->show=true;
}
}
//room_title
static void ROOM_TITLE_init(OBJ* this) {
this->delay=5;
}
static void ROOM_TITLE_draw(OBJ* this) {
this->delay-=1;
if (this->delay<-30) {
destroy_object(this);
} else if (this->delay<0) {
P8rectfill(24,58,104,70,0);
//rect(26,64-10,102,64+10,7)
//print("//-",31,64-2,13)
if (room.x==3 && room.y==1) {
P8print("old site",48,62,7);
} else if (level_index()==30) {
P8print("summit",52,62,7);
} else {
int level=(1+level_index())*100;
{
char str[16];
snprintf(str, sizeof(str), "%i m", level);
P8print(str,52+(level<1000 ? 2 : 0),62,7);
}
}
//print("//-",86,64-2,13)
draw_time(4,4);
}
}
// object functions //
//////////////////////-
static OBJ* init_object(OBJTYPE type, float x, float y) {
//if (type.if_not_fruit!=NULL && got_fruit[1+level_index()]) {
if (OBJTYPE_prop[type].if_not_fruit && got_fruit[level_index()]) {
return NULL;
}
OBJ* obj = NULL;
for (int i = 0; i < MAX_OBJECTS; i++) {
if (!objects[i].active) {
obj = &objects[i];
break;
}
}
if (!obj) {
//no more free space for objects, give up
printf("exhausted object memory..\n");
return NULL;
}
obj->active = true;
static short next_id = 0;
obj->id = next_id++;
obj->type = type;
obj->collideable=true;
obj->solids=true;
obj->spr = OBJTYPE_prop[type].tile;
obj->flip_x = obj->flip_y = false;
obj->x = x;
obj->y = y;
obj->hitbox = (HITBOX){ .x=0,.y=0,.w=8,.h=8 };
obj->spd = (VEC){.x=0,.y=0};
obj->rem = (VEC){.x=0,.y=0};
//add(objects,obj)
if (OBJ_PROP(obj).init!=NULL) {
OBJ_PROP(obj).init(obj);
}
return obj;
}
static void destroy_object(OBJ* obj) {
//shift all slots to the right of this object to the left, necessary to simulate loading jank
assert(obj >= objects && obj < objects + MAX_OBJECTS);
for (; obj+1 < objects + MAX_OBJECTS; obj++) {
*obj = *(obj+1);
}
objects[MAX_OBJECTS-1].active = false;
}
static void kill_player(OBJ* obj) {
sfx_timer=12;
P8sfx(0);
deaths+=1;
shake=10;
//destroy_object(obj);
int dead_particles_count = 0;
for (float dir=0; dir <= 7; dir+=1) {
float angle=(dir/8);
dead_particles[dead_particles_count++] = (PARTICLE){
.active = true,
.x=obj->x+4,
.y=obj->y+4,
.t=10,
.spd2=(VEC){
.x=P8sin(angle)*3,
.y=P8cos(angle)*3
}
};
restart_room();
}
destroy_object(obj); //LEMON: moved here to avoid using ->x and ->y from dead object
}
// room functions //
////////////////////
static void restart_room() {
will_restart=true;
delay_restart=15;
}
static void next_room() {
if (room.x==2 && room.y==1) {
P8music(30,500,7);
} else if (room.x==3 && room.y==1) {
P8music(20,500,7);
} else if (room.x==4 && room.y==2) {
P8music(30,500,7);
} else if (room.x==5 && room.y==3) {
P8music(30,500,7);
}
if (room.x==7) {
load_room(0,room.y+1);
} else {
load_room(room.x+1,room.y);
}
}
static bool room_just_loaded = false; //for debugging loading jank
static void load_room(int x, int y) {
has_dashed=false;
has_key=false;
room_just_loaded = true;
//int oldcount = 0;
//remove existing objects
for (int i = 0; i < MAX_OBJECTS; i++) {
//oldcount += objects[i].active ? 1 : 0;
objects[i].active = false;
}
//int newcount = 0;
//current room
room.x = x;
room.y = y;
// entities
for (int tx=0; tx <= 15; tx++) {
for (int ty=0; ty <= 15; ty++) {
int tile = P8mget(room.x*16+tx,room.y*16+ty);
if (tile==11) {
init_object(OBJ_PLATFORM,tx*8,ty*8)->dir=-1;
//newcount++;
} else if (tile==12) {
init_object(OBJ_PLATFORM,tx*8,ty*8)->dir=1;
//newcount++;
} else {
for (int type = 0; type < OBJTYPE_COUNT; type++) { //safe since types are ordered starting at 0
if (tile == OBJTYPE_prop[type].tile) {
init_object((OBJTYPE)type, tx*8, ty*8);
//newcount++;
}
}
}
}
}
//printf("load_room(): deleted %i and loaded %i objects\n", oldcount, newcount);
if (!is_title()) {
init_object(OBJ_ROOM_TITLE,0,0);
}
}
// update function //
/////////////////////
void Celeste_P8_update() {
frames=((frames+1)%30);
if (frames==0 && level_index()<30) {
seconds=((seconds+1)%60);
if (seconds==0) {
minutes+=1;
}
}
if (music_timer>0) {
music_timer-=1;
if (music_timer<=0) {
P8music(10,0,7);
}
}
if (sfx_timer>0) {
sfx_timer-=1;
}
// cancel if (freeze
if (freeze>0) { freeze-=1; return; }
// screenshake
if (shake>0) {
shake-=1;
P8camera(0,0);
if (shake>0) {
P8camera(-2+P8rnd(5),-2+P8rnd(5));
}
}
// restart (soon)
if (will_restart && delay_restart>0) {
delay_restart-=1;
if (delay_restart<=0) {
will_restart=false;
load_room(room.x,room.y);
}
}
//printf("BEGIN FRAME\n");
room_just_loaded = false;
// update each object
for (int i = 0; i < MAX_OBJECTS; i++) {
OBJ* obj = &objects[i];
redo_update_slot:
if (!obj->active) continue;
OBJ_move(obj, obj->spd.x,obj->spd.y);
//printf("update #%i (%s)\n", i, OBJ_PROP(obj).nam);
short this_id = obj->id;
if (OBJ_PROP(obj).update!=NULL) {
OBJ_PROP(obj).update(obj);
}
if (room_just_loaded) /*printf("update(): load room (player was: #%i)\n", i),*/ room_just_loaded = false;
/*LEMON: necessary to correctly simulate loading jank: due to the way pico-8's foreach() works,
* when element #i is removed and replaced by another different object, the function iterates
* over this index again. thus for example, the player is in slot N before a new room is loaded,
* all objects are deleted and new objects are spawned, and the objects now in slots [N, last] are updated
*/
if (this_id != obj->id) {
goto redo_update_slot;
}
}
//printf("END FRAME\n\n");
// start game
if (is_title()) {
if (!start_game && (P8btn(k_jump) || P8btn(k_dash))) {
P8music(-1, 0, 0);
start_game_flash=50;
start_game=true;
P8sfx(38);
}
if (start_game) {
start_game_flash-=1;
if (start_game_flash<=-30) {
begin_game();
}
}
}
}
// drawing functions //
//////////////////////-
void Celeste_P8_draw() {
if (freeze>0) { return; }
// reset all palette values
P8pal_reset();
// start game flash
if (start_game) {
int c=10;
if (start_game_flash>10) {
if (frames%10<5) {
c=7;
}
} else if (start_game_flash>5) {
c=2;
} else if (start_game_flash>0) {
c=1;
} else {
c=0;
}
if (c<10) {
P8pal(6,c),
P8pal(12,c);
P8pal(13,c);
P8pal(5,c);
P8pal(1,c);
P8pal(7,c);
}
}
// clear screen
int bg_col = 0;
if (flash_bg) {
bg_col = frames/5;
} else if (new_bg) {
bg_col=2;
}
P8rectfill(0,0,128,128,bg_col);
// clouds
if (!is_title()) {
for (int i = 0; i <= 16; i++) {
CLOUD* c = &clouds[i];
c->x += c->spd;
P8rectfill(c->x,c->y,c->x+c->w,c->y+4+(1-c->w/64.0)*12,new_bg ? 14 : 1);
if (c->x > 128) {
c->x = -c->w;
c->y = P8rnd(128-8);
}
}
}
// draw bg terrain
P8map(room.x * 16,room.y * 16,0,0,16,16,4);
// platforms/big chest
for (int i = 0; i < MAX_OBJECTS; i++) {
OBJ* o = &objects[i];
if (o->active && (o->type==OBJ_PLATFORM || o->type==OBJ_BIG_CHEST)) {
draw_object(o);
}
}
// draw terrain
int off=is_title() ? -4 : 0;
P8map(room.x*16,room.y * 16,off,0,16,16,2);
// draw objects
for (int i = 0; i < MAX_OBJECTS; i++) {
OBJ* o = &objects[i];
redo_draw:;
short this_id = o->id;
if (o->active && (o->type!=OBJ_PLATFORM && o->type!=OBJ_BIG_CHEST)) {
draw_object(o);
}
//LEMON: draw_object() could have deleted obj, and something could have been moved in its place, so check for that in order not to skip drawing an object
if (this_id != o->id) goto redo_draw;
}
// draw fg terrain
P8map(room.x * 16,room.y * 16,0,0,16,16,8);
// particles
for (int i = 0; i <= 24; i++) {
PARTICLE* p = &particles[i];
p->x += p->spd;
p->y += P8sin(p->off);
p->off+= P8min(0.05,p->spd/32);
P8rectfill(p->x,p->y,p->x+p->s,p->y+p->s,p->c);
if (p->x>128+4) {
p->x=-4;
p->y=P8rnd(128);
}
p++;
}
// dead particles
for (int i = 0; i <= 7; i++) {
PARTICLE* p = &dead_particles[i];
if (p->active) {
p->x += p->spd2.x;
p->y += p->spd2.y;
p->t -=1;
if (p->t <= 0) { p->active = false; }
P8rectfill(p->x-p->t/5,p->y-p->t/5,p->x+p->t/5,p->y+p->t/5,14+P8modulo(p->t,2));
}
p++;
}
// draw outside of the screen for screenshake
P8rectfill(-5,-5,-1,133,0);
P8rectfill(-5,-5,133,-1,0);
P8rectfill(-5,128,133,133,0);
P8rectfill(128,-5,133,133,0);
// credits
if (is_title()) {
P8print("x+c",58,80,5);
P8print("matt thorson",42,96,5);
P8print("noel berry",46,102,5);
}
if (level_index()==30) {
OBJ* p = NULL;
for (int i = 0; i < MAX_OBJECTS; i++) {
if (objects[i].active && objects[i].type==OBJ_PLAYER) {
p = &objects[i];
break;
}
}
if (p!=NULL) {
float diff=P8min(24,40-P8abs(p->x+4-64));
P8rectfill(0,0,diff,128,0);
P8rectfill(128-diff,0,128,128,0);
}
}
}
static void draw_object(OBJ* obj) {
if (OBJ_PROP(obj).draw !=NULL) {
OBJ_PROP(obj).draw(obj);
} else if (obj->spr > 0) {
P8spr(obj->spr,obj->x,obj->y,1,1,obj->flip_x,obj->flip_y);
}
//if (floorf(obj->spr) != obj->spr) printf("?%g %s\n", obj->spr, OBJ_PROP(obj).nam);
}
static void draw_time(float x, float y) {
int s=seconds;
int m=minutes%60;
int h=minutes/60;
P8rectfill(x,y,x+32,y+6,0);
{
char str[27];
snprintf(str, sizeof(str), "%.2i:%.2i:%.2i", h, m, s);
P8print(str,x+1,y+1,7);
}
}
// helper functions //
//////////////////////
static float clamp(float val, float a, float b) {
return P8max(a, P8min(b, val));
}
static float appr(float val, float target, float amount) {
return val > target
? P8max(val - amount, target)
: P8min(val + amount, target);
}
static float sign(float v) {
return v > 0 ? 1 : (v < 0 ? -1 : 0);
}
static bool maybe() {
return P8rnd(1)<0.5;
}
static bool solid_at(int x,int y,int w,int h) {
return tile_flag_at(x,y,w,h,0);
}
static bool ice_at(int x,int y,int w,int h) {
return tile_flag_at(x,y,w,h,4);
}
static bool tile_flag_at(int x,int y,int w,int h,int flag) {
for (int i=(int)P8max(0,P8flr(x/8)); i <= P8min(15,(x+w-1)/8); i++) {
for (int j=(int)P8max(0,P8flr(y/8)); j <= P8min(15,(y+h-1)/8); j++) {
if (P8fget(tile_at(i,j),flag)) {
return true;
}
}
}
return false;
}
static int tile_at(int x,int y) {
return P8mget(room.x * 16 + x, room.y * 16 + y);
}
static bool spikes_at(float x,float y,int w,int h,float xspd,float yspd) {
for (int i=(int)P8max(0,P8flr(x/8)); i <= P8min(15,(x+w-1)/8); i++) {
for (int j=(int)P8max(0,P8flr(y/8)); j <= P8min(15,(y+h-1)/8); j++) {
int tile=tile_at(i,j);
if (tile==17 && (P8modulo(y+h-1, 8)>=6 || y+h==j*8+8) && yspd>=0) {
return true;
} else if (tile==27 && P8modulo(y, 8)<=2 && yspd<=0) {
return true;
} else if (tile==43 && P8modulo(x, 8)<=2 && xspd<=0) {
return true;
} else if (tile==59 && (P8modulo(x+w-1, 8)>=6 || x+w==i*8+8) && xspd>=0) {
return true;
}
}
}
return false;
}
//////////END/////////
void Celeste_P8__DEBUG(void) {
if (is_title()) start_game = true, start_game_flash=1;
else next_room();
}
//all of the global game variables; this holds the entire game state (exc. music/sounds playing)
#define LISTGVARS(V) \
V(rnd_seed_lo) V(rnd_seed_hi) \
V(room) V(freeze) V(shake) V(will_restart) V(delay_restart) V(got_fruit) \
V(has_dashed) V(sfx_timer) V(has_key) V(pause_player) V(flash_bg) V(music_timer) \
V(new_bg) V(frames) V(seconds) V(minutes) V(deaths) V(max_djump) V(start_game) \
V(start_game_flash) V(clouds) V(particles) V(dead_particles) V(objects)
size_t Celeste_P8_get_state_size(void) {
#define V_SIZE(v) (sizeof v) +
enum { //force comptime evaluation
sz = LISTGVARS(V_SIZE) - 0
};
return sz;
#undef V_SIZE
}
void Celeste_P8_save_state(void* st_) {
assert(st_ != NULL);
char* st = (char*)st_;
#define V_SAVE(v) memcpy(st, &v, sizeof v), st += sizeof v;
LISTGVARS(V_SAVE)
#undef V_SAVE
}
void Celeste_P8_load_state(const void* st_) {
assert(st_ != NULL);
const char* st = (const char*)st_;
#define V_LOAD(v) memcpy(&v, st, sizeof v), st += sizeof v;
LISTGVARS(V_LOAD)
#undef V_LOAD
}
#undef LISTGVARS