diff --git a/Data/Audio/smooch.ogg b/Data/Audio/smooch.ogg new file mode 100644 index 0000000..cbc4868 --- /dev/null +++ b/Data/Audio/smooch.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:714c43c86eafcf69b188d45a9effe5cbdc11b3341c5e233d80ed3b3d799fff84 +size 15705 diff --git a/Data/RawAudio/airport.ogg b/Data/RawAudio/airport.ogg new file mode 100644 index 0000000..373bc5c --- /dev/null +++ b/Data/RawAudio/airport.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66c7e4a72246bd91856fdf71b50ca906fb41eddbf92105a6cbe0dfc00da7e128 +size 34566 diff --git a/Data/RawAudio/ching.ogg b/Data/RawAudio/ching.ogg new file mode 100644 index 0000000..809debe --- /dev/null +++ b/Data/RawAudio/ching.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf1553666cdd8039453453d26fbadb602826722b0a4a8291d9104da3d1cab34c +size 34345 diff --git a/Data/RawAudio/church.ogg b/Data/RawAudio/church.ogg new file mode 100644 index 0000000..13a51de --- /dev/null +++ b/Data/RawAudio/church.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b179b02d519e900ffb852adee386ffbcd3c4063763151047f5f2420aeec1bcf +size 85382 diff --git a/Data/RawAudio/club_terry.ogg b/Data/RawAudio/club_terry.ogg new file mode 100644 index 0000000..3f59a1c --- /dev/null +++ b/Data/RawAudio/club_terry.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2db3e753a4e35ca54d2c9daa92fb901852ad347338e5a1ae772ab4c60d368d77 +size 9308 diff --git a/Data/RawAudio/dog.ogg b/Data/RawAudio/dog.ogg new file mode 100644 index 0000000..2313a4b --- /dev/null +++ b/Data/RawAudio/dog.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4cf9980a69034756d61331e4e7de6a64f918ecc2f8a4a584d7b7692f771f09d +size 9703 diff --git a/Data/Audio/door_squeek.wav b/Data/RawAudio/door_squeek.wav similarity index 100% rename from Data/Audio/door_squeek.wav rename to Data/RawAudio/door_squeek.wav diff --git a/Data/Audio/ghost.ogg b/Data/RawAudio/ghost.ogg similarity index 100% rename from Data/Audio/ghost.ogg rename to Data/RawAudio/ghost.ogg diff --git a/Data/Audio/gym_bro.ogg b/Data/RawAudio/gym_bro.ogg similarity index 100% rename from Data/Audio/gym_bro.ogg rename to Data/RawAudio/gym_bro.ogg diff --git a/Data/Audio/heart_beat.ogg b/Data/RawAudio/heart_beat.ogg similarity index 100% rename from Data/Audio/heart_beat.ogg rename to Data/RawAudio/heart_beat.ogg diff --git a/Data/RawAudio/merchant_ghost.ogg b/Data/RawAudio/merchant_ghost.ogg new file mode 100644 index 0000000..e1e6b74 --- /dev/null +++ b/Data/RawAudio/merchant_ghost.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66afa22f9ae85cebf2fb1babd23c4c40a27e547c6942372137074c18b93e9e70 +size 21018 diff --git a/Data/RawAudio/merchant_gym.ogg b/Data/RawAudio/merchant_gym.ogg new file mode 100644 index 0000000..447ca43 --- /dev/null +++ b/Data/RawAudio/merchant_gym.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4459146ebf6594d01eaafb8260f2f77a8bc68c20b0b129957fa7ab2beb26098c +size 19363 diff --git a/Data/RawAudio/merchant_tech.ogg b/Data/RawAudio/merchant_tech.ogg new file mode 100644 index 0000000..1626f43 --- /dev/null +++ b/Data/RawAudio/merchant_tech.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2cca15996afb420afbea59ff0ffb551cea61876b85401ba67d58fd39ffa3781 +size 23084 diff --git a/Data/RawAudio/merchant_terry.ogg b/Data/RawAudio/merchant_terry.ogg new file mode 100644 index 0000000..f5b9808 --- /dev/null +++ b/Data/RawAudio/merchant_terry.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2462c3c4167b6015e3d97260237daf493f46110b73f76d0f2d5a77a72befeb57 +size 15768 diff --git a/Data/RawAudio/message.ogg b/Data/RawAudio/message.ogg new file mode 100644 index 0000000..fd5f53e --- /dev/null +++ b/Data/RawAudio/message.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:063cddd4f00abd63760a1cb2b7fe34f525f1374fb241b14a2b89291090be69ec +size 21393 diff --git a/Data/RawAudio/monkey.ogg b/Data/RawAudio/monkey.ogg new file mode 100644 index 0000000..2d9f372 --- /dev/null +++ b/Data/RawAudio/monkey.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2bdcbdf804363a49d73f4996af4be347b8a759ecce7f1e53060f01cf97bd502 +size 22148 diff --git a/Data/Audio/monkey_put.wav b/Data/RawAudio/monkey_put.wav similarity index 100% rename from Data/Audio/monkey_put.wav rename to Data/RawAudio/monkey_put.wav diff --git a/Data/RawAudio/portal_destroy.ogg b/Data/RawAudio/portal_destroy.ogg new file mode 100644 index 0000000..81eccb1 --- /dev/null +++ b/Data/RawAudio/portal_destroy.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09c25ca6a839d9a6529072ac2e89468c9903979d714236ffa87a21720b3b8edb +size 85563 diff --git a/Data/Audio/smooch.mp3 b/Data/RawAudio/smooch.mp3 similarity index 100% rename from Data/Audio/smooch.mp3 rename to Data/RawAudio/smooch.mp3 diff --git a/Data/Audio/tech_sound.wav b/Data/RawAudio/tech_sound.wav similarity index 100% rename from Data/Audio/tech_sound.wav rename to Data/RawAudio/tech_sound.wav diff --git a/Data/RawAudio/terry_hit.ogg b/Data/RawAudio/terry_hit.ogg new file mode 100644 index 0000000..ae49fb6 --- /dev/null +++ b/Data/RawAudio/terry_hit.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:047bb92b9cf80e962d915d488667ba2d1d45a5104a9137a4dbaef67868a38e5e +size 9277 diff --git a/Data/Audio/wifi.ogg b/Data/RawAudio/wifi.ogg similarity index 100% rename from Data/Audio/wifi.ogg rename to Data/RawAudio/wifi.ogg diff --git a/Data/RawAudio/woosh.ogg b/Data/RawAudio/woosh.ogg new file mode 100644 index 0000000..91947e7 --- /dev/null +++ b/Data/RawAudio/woosh.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69843e9c3dae5ee24bb3b11385626d73a261262d7ca1972eba73d8b9644a96a3 +size 17114 diff --git a/Data/Textures/atlas.png b/Data/Textures/atlas.png index 7e63ba6..25081ca 100644 --- a/Data/Textures/atlas.png +++ b/Data/Textures/atlas.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:07f942ce81957e8ab70fcc60ef823d89670803c9e4e17cc681213db475c6e4c4 -size 13979505 +oid sha256:218f42f2a5a33912c9d379bdc7a509552f8b9053bf8e1a783ec6331b26c9f28f +size 14076722 diff --git a/Data/Textures/atlas.txt b/Data/Textures/atlas.txt index 4e1aee4..8c492c5 100644 --- a/Data/Textures/atlas.txt +++ b/Data/Textures/atlas.txt @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:39c78d7d9439b82ff19853e42149c52f6c1098d7dbef097b16e8544a0fe53b91 -size 11501 +oid sha256:65668c0435d3a999371b40a221375eb2a810e6b720bdbe4ce318eb5203709d3d +size 11494 diff --git a/Data/Textures/atlas/intro_screen_arrows.png b/Data/Textures/atlas/intro_screen_arrows.png index a30acf8..ed8c1d6 100644 --- a/Data/Textures/atlas/intro_screen_arrows.png +++ b/Data/Textures/atlas/intro_screen_arrows.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a10a7e24cd205c2061cb3ed34c15664da61a5c307270c56d2172f8cd2a9abc3 -size 140401 +oid sha256:52621d3138efbcc53c6507db4282313f294fc0d14fc6fb7516a580bc7e62a683 +size 209733 diff --git a/Data/Textures/atlas/intro_screen_subtitle.png b/Data/Textures/atlas/intro_screen_subtitle.png index 071a8a6..1feefe6 100644 --- a/Data/Textures/atlas/intro_screen_subtitle.png +++ b/Data/Textures/atlas/intro_screen_subtitle.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f854a1d2d5af3ae8f7109f3223d7ae0c76b28d9b3d554bb9bf3c0ad80cb948ea -size 325912 +oid sha256:cd11a479ad042261786447e73a34e4400b7495cae166f0c92b3e32f958baa9d8 +size 488907 diff --git a/Data/Textures/atlas/intro_screen_terry.png b/Data/Textures/atlas/intro_screen_terry.png index e803a11..6a87056 100644 --- a/Data/Textures/atlas/intro_screen_terry.png +++ b/Data/Textures/atlas/intro_screen_terry.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:437332b4fdecfa498cb191fd2ba16b575273a56b0bc4a5b7797dc2573b27c818 -size 276903 +oid sha256:98b4b205df75226e9b9fc6e122a9f468aebb98a411878d241fa2e11040692725 +size 393205 diff --git a/Data/Textures/atlas/intro_screen_title.png b/Data/Textures/atlas/intro_screen_title.png index c25183f..2a45923 100644 --- a/Data/Textures/atlas/intro_screen_title.png +++ b/Data/Textures/atlas/intro_screen_title.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:293ce0ea5d3a90e58caa3df2834728e113bedcb14b2fb740123a6d10275647a8 -size 91162 +oid sha256:7daf4c34b19096c0f04edf28a498cbfacabb4b6d83828fc364801778800a0bed +size 118025 diff --git a/External/qoi.h b/External/qoi.h new file mode 100644 index 0000000..f2800b0 --- /dev/null +++ b/External/qoi.h @@ -0,0 +1,649 @@ +/* + +Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org +SPDX-License-Identifier: MIT + + +QOI - The "Quite OK Image" format for fast, lossless image compression + +-- About + +QOI encodes and decodes images in a lossless format. Compared to stb_image and +stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and +20% better compression. + + +-- Synopsis + +// Define `QOI_IMPLEMENTATION` in *one* C/C++ file before including this +// library to create the implementation. + +#define QOI_IMPLEMENTATION +#include "qoi.h" + +// Encode and store an RGBA buffer to the file system. The qoi_desc describes +// the input pixel data. +qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){ + .width = 1920, + .height = 1080, + .channels = 4, + .colorspace = QOI_SRGB +}); + +// Load and decode a QOI image from the file system into a 32bbp RGBA buffer. +// The qoi_desc struct will be filled with the width, height, number of channels +// and colorspace read from the file header. +qoi_desc desc; +void *rgba_pixels = qoi_read("image.qoi", &desc, 4); + + + +-- Documentation + +This library provides the following functions; +- qoi_read -- read and decode a QOI file +- qoi_decode -- decode the raw bytes of a QOI image from memory +- qoi_write -- encode and write a QOI file +- qoi_encode -- encode an rgba buffer into a QOI image in memory + +See the function declaration below for the signature and more information. + +If you don't want/need the qoi_read and qoi_write functions, you can define +QOI_NO_STDIO before including this library. + +This library uses malloc() and free(). To supply your own malloc implementation +you can define QOI_MALLOC and QOI_FREE before including this library. + +This library uses memset() to zero-initialize the index. To supply your own +implementation you can define QOI_ZEROARR before including this library. + + +-- Data Format + +A QOI file has a 14 byte header, followed by any number of data "chunks" and an +8-byte end marker. + +struct qoi_header_t { + char magic[4]; // magic bytes "qoif" + uint32_t width; // image width in pixels (BE) + uint32_t height; // image height in pixels (BE) + uint8_t channels; // 3 = RGB, 4 = RGBA + uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear +}; + +Images are encoded row by row, left to right, top to bottom. The decoder and +encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An +image is complete when all pixels specified by width * height have been covered. + +Pixels are encoded as + - a run of the previous pixel + - an index into an array of previously seen pixels + - a difference to the previous pixel value in r,g,b + - full r,g,b or r,g,b,a values + +The color channels are assumed to not be premultiplied with the alpha channel +("un-premultiplied alpha"). + +A running array[64] (zero-initialized) of previously seen pixel values is +maintained by the encoder and decoder. Each pixel that is seen by the encoder +and decoder is put into this array at the position formed by a hash function of +the color value. In the encoder, if the pixel value at the index matches the +current pixel, this index position is written to the stream as QOI_OP_INDEX. +The hash function for the index is: + + index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64 + +Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The +bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All +values encoded in these data bits have the most significant bit on the left. + +The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the +presence of an 8-bit tag first. + +The byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte. + + +The possible chunks are: + + +.- QOI_OP_INDEX ----------. +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----------------| +| 0 0 | index | +`-------------------------` +2-bit tag b00 +6-bit index into the color index array: 0..63 + +A valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the +same index. QOI_OP_RUN should be used instead. + + +.- QOI_OP_DIFF -----------. +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----+-----+-----| +| 0 1 | dr | dg | db | +`-------------------------` +2-bit tag b01 +2-bit red channel difference from the previous pixel between -2..1 +2-bit green channel difference from the previous pixel between -2..1 +2-bit blue channel difference from the previous pixel between -2..1 + +The difference to the current channel values are using a wraparound operation, +so "1 - 2" will result in 255, while "255 + 1" will result in 0. + +Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as +0 (b00). 1 is stored as 3 (b11). + +The alpha value remains unchanged from the previous pixel. + + +.- QOI_OP_LUMA -------------------------------------. +| Byte[0] | Byte[1] | +| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | +|-------+-----------------+-------------+-----------| +| 1 0 | green diff | dr - dg | db - dg | +`---------------------------------------------------` +2-bit tag b10 +6-bit green channel difference from the previous pixel -32..31 +4-bit red channel difference minus green channel difference -8..7 +4-bit blue channel difference minus green channel difference -8..7 + +The green channel is used to indicate the general direction of change and is +encoded in 6 bits. The red and blue channels (dr and db) base their diffs off +of the green channel difference and are encoded in 4 bits. I.e.: + dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g) + db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g) + +The difference to the current channel values are using a wraparound operation, +so "10 - 13" will result in 253, while "250 + 7" will result in 1. + +Values are stored as unsigned integers with a bias of 32 for the green channel +and a bias of 8 for the red and blue channel. + +The alpha value remains unchanged from the previous pixel. + + +.- QOI_OP_RUN ------------. +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----------------| +| 1 1 | run | +`-------------------------` +2-bit tag b11 +6-bit run-length repeating the previous pixel: 1..62 + +The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64 +(b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and +QOI_OP_RGBA tags. + + +.- QOI_OP_RGB ------------------------------------------. +| Byte[0] | Byte[1] | Byte[2] | Byte[3] | +| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | +|-------------------------+---------+---------+---------| +| 1 1 1 1 1 1 1 0 | red | green | blue | +`-------------------------------------------------------` +8-bit tag b11111110 +8-bit red channel value +8-bit green channel value +8-bit blue channel value + +The alpha value remains unchanged from the previous pixel. + + +.- QOI_OP_RGBA ---------------------------------------------------. +| Byte[0] | Byte[1] | Byte[2] | Byte[3] | Byte[4] | +| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | +|-------------------------+---------+---------+---------+---------| +| 1 1 1 1 1 1 1 1 | red | green | blue | alpha | +`-----------------------------------------------------------------` +8-bit tag b11111111 +8-bit red channel value +8-bit green channel value +8-bit blue channel value +8-bit alpha channel value + +*/ + + +/* ----------------------------------------------------------------------------- +Header - Public functions */ + +#ifndef QOI_H +#define QOI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions. +It describes either the input format (for qoi_write and qoi_encode), or is +filled with the description read from the file header (for qoi_read and +qoi_decode). + +The colorspace in this qoi_desc is an enum where + 0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel + 1 = all channels are linear +You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely +informative. It will be saved to the file header, but does not affect +how chunks are en-/decoded. */ + +#define QOI_SRGB 0 +#define QOI_LINEAR 1 + +typedef struct { + unsigned int width; + unsigned int height; + unsigned char channels; + unsigned char colorspace; +} qoi_desc; + +#ifndef QOI_NO_STDIO + +/* Encode raw RGB or RGBA pixels into a QOI image and write it to the file +system. The qoi_desc struct must be filled with the image width, height, +number of channels (3 = RGB, 4 = RGBA) and the colorspace. + +The function returns 0 on failure (invalid parameters, or fopen or malloc +failed) or the number of bytes written on success. */ + +int qoi_write(const char *filename, const void *data, const qoi_desc *desc); + + +/* Read and decode a QOI image from the file system. If channels is 0, the +number of channels from the file header is used. If channels is 3 or 4 the +output format will be forced into this number of channels. + +The function either returns NULL on failure (invalid data, or malloc or fopen +failed) or a pointer to the decoded pixels. On success, the qoi_desc struct +will be filled with the description from the file header. + +The returned pixel data should be free()d after use. */ + +void *qoi_read(const char *filename, qoi_desc *desc, int channels); + +#endif /* QOI_NO_STDIO */ + + +/* Encode raw RGB or RGBA pixels into a QOI image in memory. + +The function either returns NULL on failure (invalid parameters or malloc +failed) or a pointer to the encoded data on success. On success the out_len +is set to the size in bytes of the encoded data. + +The returned qoi data should be free()d after use. */ + +void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len); + + +/* Decode a QOI image from memory. + +The function either returns NULL on failure (invalid parameters or malloc +failed) or a pointer to the decoded pixels. On success, the qoi_desc struct +is filled with the description from the file header. + +The returned pixel data should be free()d after use. */ + +void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels); + + +#ifdef __cplusplus +} +#endif +#endif /* QOI_H */ + + +/* ----------------------------------------------------------------------------- +Implementation */ + +#ifdef QOI_IMPLEMENTATION +#include +#include + +#ifndef QOI_MALLOC + #define QOI_MALLOC(sz) malloc(sz) + #define QOI_FREE(p) free(p) +#endif +#ifndef QOI_ZEROARR + #define QOI_ZEROARR(a) memset((a),0,sizeof(a)) +#endif + +#define QOI_OP_INDEX 0x00 /* 00xxxxxx */ +#define QOI_OP_DIFF 0x40 /* 01xxxxxx */ +#define QOI_OP_LUMA 0x80 /* 10xxxxxx */ +#define QOI_OP_RUN 0xc0 /* 11xxxxxx */ +#define QOI_OP_RGB 0xfe /* 11111110 */ +#define QOI_OP_RGBA 0xff /* 11111111 */ + +#define QOI_MASK_2 0xc0 /* 11000000 */ + +#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11) +#define QOI_MAGIC \ + (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \ + ((unsigned int)'i') << 8 | ((unsigned int)'f')) +#define QOI_HEADER_SIZE 14 + +/* 2GB is the max file size that this implementation can safely handle. We guard +against anything larger than that, assuming the worst case with 5 bytes per +pixel, rounded down to a nice clean value. 400 million pixels ought to be +enough for anybody. */ +#define QOI_PIXELS_MAX ((unsigned int)400000000) + +typedef union { + struct { unsigned char r, g, b, a; } rgba; + unsigned int v; +} qoi_rgba_t; + +static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1}; + +static void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) { + bytes[(*p)++] = (0xff000000 & v) >> 24; + bytes[(*p)++] = (0x00ff0000 & v) >> 16; + bytes[(*p)++] = (0x0000ff00 & v) >> 8; + bytes[(*p)++] = (0x000000ff & v); +} + +static unsigned int qoi_read_32(const unsigned char *bytes, int *p) { + unsigned int a = bytes[(*p)++]; + unsigned int b = bytes[(*p)++]; + unsigned int c = bytes[(*p)++]; + unsigned int d = bytes[(*p)++]; + return a << 24 | b << 16 | c << 8 | d; +} + +void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) { + int i, max_size, p, run; + int px_len, px_end, px_pos, channels; + unsigned char *bytes; + const unsigned char *pixels; + qoi_rgba_t index[64]; + qoi_rgba_t px, px_prev; + + if ( + data == NULL || out_len == NULL || desc == NULL || + desc->width == 0 || desc->height == 0 || + desc->channels < 3 || desc->channels > 4 || + desc->colorspace > 1 || + desc->height >= QOI_PIXELS_MAX / desc->width + ) { + return NULL; + } + + max_size = + desc->width * desc->height * (desc->channels + 1) + + QOI_HEADER_SIZE + sizeof(qoi_padding); + + p = 0; + bytes = (unsigned char *) QOI_MALLOC(max_size); + if (!bytes) { + return NULL; + } + + qoi_write_32(bytes, &p, QOI_MAGIC); + qoi_write_32(bytes, &p, desc->width); + qoi_write_32(bytes, &p, desc->height); + bytes[p++] = desc->channels; + bytes[p++] = desc->colorspace; + + + pixels = (const unsigned char *)data; + + QOI_ZEROARR(index); + + run = 0; + px_prev.rgba.r = 0; + px_prev.rgba.g = 0; + px_prev.rgba.b = 0; + px_prev.rgba.a = 255; + px = px_prev; + + px_len = desc->width * desc->height * desc->channels; + px_end = px_len - desc->channels; + channels = desc->channels; + + for (px_pos = 0; px_pos < px_len; px_pos += channels) { + px.rgba.r = pixels[px_pos + 0]; + px.rgba.g = pixels[px_pos + 1]; + px.rgba.b = pixels[px_pos + 2]; + + if (channels == 4) { + px.rgba.a = pixels[px_pos + 3]; + } + + if (px.v == px_prev.v) { + run++; + if (run == 62 || px_pos == px_end) { + bytes[p++] = QOI_OP_RUN | (run - 1); + run = 0; + } + } + else { + int index_pos; + + if (run > 0) { + bytes[p++] = QOI_OP_RUN | (run - 1); + run = 0; + } + + index_pos = QOI_COLOR_HASH(px) % 64; + + if (index[index_pos].v == px.v) { + bytes[p++] = QOI_OP_INDEX | index_pos; + } + else { + index[index_pos] = px; + + if (px.rgba.a == px_prev.rgba.a) { + signed char vr = px.rgba.r - px_prev.rgba.r; + signed char vg = px.rgba.g - px_prev.rgba.g; + signed char vb = px.rgba.b - px_prev.rgba.b; + + signed char vg_r = vr - vg; + signed char vg_b = vb - vg; + + if ( + vr > -3 && vr < 2 && + vg > -3 && vg < 2 && + vb > -3 && vb < 2 + ) { + bytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2); + } + else if ( + vg_r > -9 && vg_r < 8 && + vg > -33 && vg < 32 && + vg_b > -9 && vg_b < 8 + ) { + bytes[p++] = QOI_OP_LUMA | (vg + 32); + bytes[p++] = (vg_r + 8) << 4 | (vg_b + 8); + } + else { + bytes[p++] = QOI_OP_RGB; + bytes[p++] = px.rgba.r; + bytes[p++] = px.rgba.g; + bytes[p++] = px.rgba.b; + } + } + else { + bytes[p++] = QOI_OP_RGBA; + bytes[p++] = px.rgba.r; + bytes[p++] = px.rgba.g; + bytes[p++] = px.rgba.b; + bytes[p++] = px.rgba.a; + } + } + } + px_prev = px; + } + + for (i = 0; i < (int)sizeof(qoi_padding); i++) { + bytes[p++] = qoi_padding[i]; + } + + *out_len = p; + return bytes; +} + +void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) { + const unsigned char *bytes; + unsigned int header_magic; + unsigned char *pixels; + qoi_rgba_t index[64]; + qoi_rgba_t px; + int px_len, chunks_len, px_pos; + int p = 0, run = 0; + + if ( + data == NULL || desc == NULL || + (channels != 0 && channels != 3 && channels != 4) || + size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding) + ) { + return NULL; + } + + bytes = (const unsigned char *)data; + + header_magic = qoi_read_32(bytes, &p); + desc->width = qoi_read_32(bytes, &p); + desc->height = qoi_read_32(bytes, &p); + desc->channels = bytes[p++]; + desc->colorspace = bytes[p++]; + + if ( + desc->width == 0 || desc->height == 0 || + desc->channels < 3 || desc->channels > 4 || + desc->colorspace > 1 || + header_magic != QOI_MAGIC || + desc->height >= QOI_PIXELS_MAX / desc->width + ) { + return NULL; + } + + if (channels == 0) { + channels = desc->channels; + } + + px_len = desc->width * desc->height * channels; + pixels = (unsigned char *) QOI_MALLOC(px_len); + if (!pixels) { + return NULL; + } + + QOI_ZEROARR(index); + px.rgba.r = 0; + px.rgba.g = 0; + px.rgba.b = 0; + px.rgba.a = 255; + + chunks_len = size - (int)sizeof(qoi_padding); + for (px_pos = 0; px_pos < px_len; px_pos += channels) { + if (run > 0) { + run--; + } + else if (p < chunks_len) { + int b1 = bytes[p++]; + + if (b1 == QOI_OP_RGB) { + px.rgba.r = bytes[p++]; + px.rgba.g = bytes[p++]; + px.rgba.b = bytes[p++]; + } + else if (b1 == QOI_OP_RGBA) { + px.rgba.r = bytes[p++]; + px.rgba.g = bytes[p++]; + px.rgba.b = bytes[p++]; + px.rgba.a = bytes[p++]; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) { + px = index[b1]; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) { + px.rgba.r += ((b1 >> 4) & 0x03) - 2; + px.rgba.g += ((b1 >> 2) & 0x03) - 2; + px.rgba.b += ( b1 & 0x03) - 2; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) { + int b2 = bytes[p++]; + int vg = (b1 & 0x3f) - 32; + px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f); + px.rgba.g += vg; + px.rgba.b += vg - 8 + (b2 & 0x0f); + } + else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) { + run = (b1 & 0x3f); + } + + index[QOI_COLOR_HASH(px) % 64] = px; + } + + pixels[px_pos + 0] = px.rgba.r; + pixels[px_pos + 1] = px.rgba.g; + pixels[px_pos + 2] = px.rgba.b; + + if (channels == 4) { + pixels[px_pos + 3] = px.rgba.a; + } + } + + return pixels; +} + +#ifndef QOI_NO_STDIO +#include + +int qoi_write(const char *filename, const void *data, const qoi_desc *desc) { + FILE *f = fopen(filename, "wb"); + int size, err; + void *encoded; + + if (!f) { + return 0; + } + + encoded = qoi_encode(data, desc, &size); + if (!encoded) { + fclose(f); + return 0; + } + + fwrite(encoded, 1, size, f); + fflush(f); + err = ferror(f); + fclose(f); + + QOI_FREE(encoded); + return err ? 0 : size; +} + +void *qoi_read(const char *filename, qoi_desc *desc, int channels) { + FILE *f = fopen(filename, "rb"); + int size, bytes_read; + void *pixels, *data; + + if (!f) { + return NULL; + } + + fseek(f, 0, SEEK_END); + size = ftell(f); + if (size <= 0 || fseek(f, 0, SEEK_SET) != 0) { + fclose(f); + return NULL; + } + + data = QOI_MALLOC(size); + if (!data) { + fclose(f); + return NULL; + } + + bytes_read = fread(data, 1, size, f); + fclose(f); + pixels = (bytes_read != size) ? NULL : qoi_decode(data, bytes_read, desc, channels); + QOI_FREE(data); + return pixels; +} + +#endif /* QOI_NO_STDIO */ +#endif /* QOI_IMPLEMENTATION */ diff --git a/External/qoiconv.c b/External/qoiconv.c new file mode 100644 index 0000000..caef2ee --- /dev/null +++ b/External/qoiconv.c @@ -0,0 +1,91 @@ +/* + +Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org +SPDX-License-Identifier: MIT + + +Command line tool to convert between png <> qoi format + +Requires: + -"stb_image.h" (https://github.com/nothings/stb/blob/master/stb_image.h) + -"stb_image_write.h" (https://github.com/nothings/stb/blob/master/stb_image_write.h) + -"qoi.h" (https://github.com/phoboslab/qoi/blob/master/qoi.h) + +Compile with: + gcc qoiconv.c -std=c99 -O3 -o qoiconv + +*/ + + +#define STB_IMAGE_IMPLEMENTATION +#define STBI_ONLY_PNG +#define STBI_NO_LINEAR +#include "stb_image.h" + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + +#define QOI_IMPLEMENTATION +#include "qoi.h" + + +#define STR_ENDS_WITH(S, E) (strcmp(S + strlen(S) - (sizeof(E)-1), E) == 0) + +int main(int argc, char **argv) { + if (argc < 3) { + puts("Usage: qoiconv "); + puts("Examples:"); + puts(" qoiconv input.png output.qoi"); + puts(" qoiconv input.qoi output.png"); + exit(1); + } + + void *pixels = NULL; + int w, h, channels; + if (STR_ENDS_WITH(argv[1], ".png")) { + if(!stbi_info(argv[1], &w, &h, &channels)) { + printf("Couldn't read header %s\n", argv[1]); + exit(1); + } + + // Force all odd encodings to be RGBA + if(channels != 3) { + channels = 4; + } + + pixels = (void *)stbi_load(argv[1], &w, &h, NULL, channels); + } + else if (STR_ENDS_WITH(argv[1], ".qoi")) { + qoi_desc desc; + pixels = qoi_read(argv[1], &desc, 0); + channels = desc.channels; + w = desc.width; + h = desc.height; + } + + if (pixels == NULL) { + printf("Couldn't load/decode %s\n", argv[1]); + exit(1); + } + + int encoded = 0; + if (STR_ENDS_WITH(argv[2], ".png")) { + encoded = stbi_write_png(argv[2], w, h, channels, pixels, 0); + } + else if (STR_ENDS_WITH(argv[2], ".qoi")) { + encoded = qoi_write(argv[2], pixels, &(qoi_desc){ + .width = w, + .height = h, + .channels = channels, + .colorspace = QOI_SRGB + }); + } + + if (!encoded) { + printf("Couldn't write/encode %s\n", argv[2]); + exit(1); + } + + free(pixels); + return 0; +} diff --git a/External/tely b/External/tely index db33db4..135fd17 160000 --- a/External/tely +++ b/External/tely @@ -1 +1 @@ -Subproject commit db33db4944dd98e91e40f9a62d6607169a43fbbd +Subproject commit 135fd1783947016e783c141102ff6cf635f45c9f diff --git a/build_assets.bat b/build_assets.bat index c8d2974..6ba4949 100644 --- a/build_assets.bat +++ b/build_assets.bat @@ -10,4 +10,4 @@ if not exist "%sprite_packer%" ( exit /B 1 ) -%sprite_packer% 6144x6144 %script_dir%\Data\Textures\sprite_spec.txt %script_dir%\Data\Textures\atlas || exit /b 1 +%sprite_packer% 5100x5100 %script_dir%\Data\Textures\sprite_spec.txt %script_dir%\Data\Textures\atlas || exit /b 1 diff --git a/feely_pona.cpp b/feely_pona.cpp index e4ca5de..b8837f4 100644 --- a/feely_pona.cpp +++ b/feely_pona.cpp @@ -220,6 +220,7 @@ static void FP_Game_MoveEntity(FP_Game *game, FP_GameEntityHandle entity_handle, case FP_EntityType_AirportTerryPlane: case FP_EntityType_MobSpawner: case FP_EntityType_PortalMonkey: break; + case FP_EntityType_Billboard: break; } if (!entity_collides_with_collider) @@ -325,7 +326,6 @@ static void FP_PlayReset(FP_Game *game, TELY_Platform *platform) Dqn_V2 sprite_rect_scaled = sprite_rect.size * size_scale; entity->local_hit_box_size = sprite_rect_scaled; - FP_Entity_AddDebugEditorFlags(game, entity->handle); FP_Game_EntityActionReset(game, entity->handle, FP_GAME_ENTITY_ACTION_INFINITE_TIMER, render_data.sprite); play->map = entity; } @@ -472,6 +472,13 @@ static void FP_PlayReset(FP_Game *game, TELY_Platform *platform) game->play.heart = FP_Entity_CreateHeart(game, base_mid_p, "Heart"); play->tile_size = 37; + // NOTE: Create billboads ====================================================================== + FP_Entity_CreateBillboard(game, Dqn_V2_InitNx2(1311, -951), FP_EntityBillboardState_Dash, "Dash Billboard"); + FP_Entity_CreateBillboard(game, Dqn_V2_InitNx2(1954, 855), FP_EntityBillboardState_Attack, "Attack Billboard"); + FP_Entity_CreateBillboard(game, Dqn_V2_InitNx2(2047, -984), FP_EntityBillboardState_RangeAttack, "Range Attack Billboard"); + FP_Entity_CreateBillboard(game, Dqn_V2_InitNx2(2036, -700), FP_EntityBillboardState_Monkey, "Monkey Billboard"); + FP_Entity_CreateBillboard(game, Dqn_V2_InitNx2(2098, 1171), FP_EntityBillboardState_Strafe, "Strafe Billboard"); + // NOTE: Camera ================================================================================ play->camera.world_pos = base_mid_p - Dqn_V2_InitV2I(platform->core.window_size * .5f); play->camera.scale = Dqn_V2_InitNx1(1); @@ -511,26 +518,38 @@ void TELY_DLL_Init(void *user_data) game->jetbrains_mono_font = platform->func_load_font(assets, DQN_STRING8("JetBrains Mono NL (Regular)"), DQN_STRING8("Data/Fonts/JetBrainsMonoNL-Regular.ttf"), font_size); game->talkco_font = platform->func_load_font(assets, DQN_STRING8("Talkco"), DQN_STRING8("Data/Fonts/Talkco.otf"), font_size); game->talkco_font_large = platform->func_load_font(assets, DQN_STRING8("Talkco"), DQN_STRING8("Data/Fonts/Talkco.otf"), DQN_CAST(uint16_t)(font_size * 1.5f)); + game->talkco_font_xlarge = platform->func_load_font(assets, DQN_STRING8("Talkco"), DQN_STRING8("Data/Fonts/Talkco.otf"), DQN_CAST(uint16_t)(font_size * 2.f)); game->audio[FP_GameAudio_TerryHit] = platform->func_load_audio(assets, DQN_STRING8("Terry Hit"), DQN_STRING8("Data/Audio/terry_hit.ogg")); - game->audio[FP_GameAudio_Smooch] = platform->func_load_audio(assets, DQN_STRING8("Smooch"), DQN_STRING8("Data/Audio/smooch.mp3")); - game->audio[FP_GameAudio_Woosh] = platform->func_load_audio(assets, DQN_STRING8("Woosh"), DQN_STRING8("Data/Audio/woosh.ogg")); - game->audio[FP_GameAudio_Ching] = platform->func_load_audio(assets, DQN_STRING8("Ching"), DQN_STRING8("Data/Audio/ching.ogg")); - game->audio[FP_GameAudio_Church] = platform->func_load_audio(assets, DQN_STRING8("Church"), DQN_STRING8("Data/Audio/church.ogg")); - game->audio[FP_GameAudio_Plane] = platform->func_load_audio(assets, DQN_STRING8("Plane"), DQN_STRING8("Data/Audio/airport.ogg")); - game->audio[FP_GameAudio_Club] = platform->func_load_audio(assets, DQN_STRING8("Club"), DQN_STRING8("Data/Audio/club_terry.ogg")); - game->audio[FP_GameAudio_Dog] = platform->func_load_audio(assets, DQN_STRING8("Dog"), DQN_STRING8("Data/Audio/dog.ogg")); - game->audio[FP_GameAudio_MerchantTerry] = platform->func_load_audio(assets, DQN_STRING8("Door"), DQN_STRING8("Data/Audio/merchant_terry.ogg")); - game->audio[FP_GameAudio_MerchantGhost] = platform->func_load_audio(assets, DQN_STRING8("Ghost"), DQN_STRING8("Data/Audio/merchant_ghost.ogg")); - game->audio[FP_GameAudio_MerchantGym] = platform->func_load_audio(assets, DQN_STRING8("Gym"), DQN_STRING8("Data/Audio/merchant_gym.ogg")); - game->audio[FP_GameAudio_MerchantPhone] = platform->func_load_audio(assets, DQN_STRING8("Phone"), DQN_STRING8("Data/Audio/merchant_tech.ogg")); - game->audio[FP_GameAudio_Message] = platform->func_load_audio(assets, DQN_STRING8("Message"), DQN_STRING8("Data/Audio/message.ogg")); - game->audio[FP_GameAudio_Monkey] = platform->func_load_audio(assets, DQN_STRING8("Monkey"), DQN_STRING8("Data/Audio/monkey.ogg")); - game->audio[FP_GameAudio_PortalDestroy] = platform->func_load_audio(assets, DQN_STRING8("Portal Destroy"), DQN_STRING8("Data/Audio/portal_destroy.ogg")); + game->audio[FP_GameAudio_Ching] = platform->func_load_audio(assets, DQN_STRING8("Ching"), DQN_STRING8("Data/Audio/ching.ogg")); + game->audio[FP_GameAudio_Church] = platform->func_load_audio(assets, DQN_STRING8("Church"), DQN_STRING8("Data/Audio/church.ogg")); + game->audio[FP_GameAudio_Club] = platform->func_load_audio(assets, DQN_STRING8("Club"), DQN_STRING8("Data/Audio/club_terry.ogg")); + game->audio[FP_GameAudio_Dog] = platform->func_load_audio(assets, DQN_STRING8("Dog"), DQN_STRING8("Data/Audio/dog.ogg")); + game->audio[FP_GameAudio_MerchantGhost] = platform->func_load_audio(assets, DQN_STRING8("Ghost"), DQN_STRING8("Data/Audio/merchant_ghost.ogg")); + game->audio[FP_GameAudio_MerchantGym] = platform->func_load_audio(assets, DQN_STRING8("Gym"), DQN_STRING8("Data/Audio/merchant_gym.ogg")); + game->audio[FP_GameAudio_MerchantPhone] = platform->func_load_audio(assets, DQN_STRING8("Phone"), DQN_STRING8("Data/Audio/merchant_tech.ogg")); + game->audio[FP_GameAudio_MerchantTerry] = platform->func_load_audio(assets, DQN_STRING8("Door"), DQN_STRING8("Data/Audio/merchant_terry.ogg")); + game->audio[FP_GameAudio_Message] = platform->func_load_audio(assets, DQN_STRING8("Message"), DQN_STRING8("Data/Audio/message.ogg")); + game->audio[FP_GameAudio_Monkey] = platform->func_load_audio(assets, DQN_STRING8("Monkey"), DQN_STRING8("Data/Audio/monkey.ogg")); + game->audio[FP_GameAudio_Plane] = platform->func_load_audio(assets, DQN_STRING8("Plane"), DQN_STRING8("Data/Audio/airport.ogg")); + game->audio[FP_GameAudio_PortalDestroy] = platform->func_load_audio(assets, DQN_STRING8("Portal Destroy"), DQN_STRING8("Data/Audio/portal_destroy.ogg")); + game->audio[FP_GameAudio_Smooch] = platform->func_load_audio(assets, DQN_STRING8("Smooch"), DQN_STRING8("Data/Audio/smooch.ogg")); + game->audio[FP_GameAudio_Woosh] = platform->func_load_audio(assets, DQN_STRING8("Woosh"), DQN_STRING8("Data/Audio/woosh.ogg")); // NOTE: Load sprite sheets ==================================================================== platform->user_data = game; game->atlas_sprite_sheet = FP_LoadSpriteSheetFromSpec(platform, assets, &platform->arena, DQN_STRING8("atlas")); FP_PlayReset(game, platform); + + FP_GameControls *controls = &game->controls; + controls->up.scan_code = TELY_PlatformInputScanCode_W; + controls->down.scan_code = TELY_PlatformInputScanCode_S; + controls->left.scan_code = TELY_PlatformInputScanCode_A; + controls->right.scan_code = TELY_PlatformInputScanCode_D; + controls->attack.scan_code = TELY_PlatformInputScanCode_J; + controls->range_attack.scan_code = TELY_PlatformInputScanCode_K; + controls->build_mode.scan_code = TELY_PlatformInputScanCode_H; + controls->strafe.scan_code = TELY_PlatformInputScanCode_L; + controls->dash.scan_code = TELY_PlatformInputScanCode_N; } struct FP_GetClosestPortalMonkeyResult @@ -601,6 +620,8 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform bool const action_has_finished = !entering_new_state && game->play.clock_ms >= action->end_at_clock_ms; action->state = action->next_state; + FP_GameControls const *controls = &game->controls; + FP_EntityRenderData render_data = FP_Entity_GetRenderData(game, entity->type, entity->action.state, entity->direction); switch (entity->type) { case FP_EntityType_Terry: { @@ -635,7 +656,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform // NOTE: Check if we are nearby a monkey and picking it up FP_GetClosestPortalMonkeyResult closest_monkey = FP_GetClosestPortalMonkey(game, entity->handle); if (closest_monkey.dist < DQN_SQUARED(FP_Game_MetersToPixelsNx1(game->play, 2.f))) { - if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) { + if (TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code)) { entity->carried_monkey = closest_monkey.entity; picked_up_monkey_this_frame = true; TELY_Audio_Play(audio, game->audio[FP_GameAudio_Monkey], 1.f); @@ -644,15 +665,15 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform } if (FP_Game_IsNilEntityHandle(game, entity->carried_monkey)) { - if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) || + if (TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code) || TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_X)) { FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Attack); - } else if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_K) || + } else if (TELY_Platform_InputScanCodeIsPressed(input, controls->range_attack.scan_code) || TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_Y)) { FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_RangeAttack); } } else { - if (!picked_up_monkey_this_frame && TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) { + if (!picked_up_monkey_this_frame && TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code)) { FP_GameEntity *portal_monkey = FP_Game_GetEntity(game, entity->carried_monkey); portal_monkey->local_pos = FP_Game_CalcEntityWorldPos(game, entity->handle); entity->carried_monkey = {}; @@ -702,7 +723,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform // NOTE: Check if we are nearby a monkey and picking it up FP_GetClosestPortalMonkeyResult closest_monkey = FP_GetClosestPortalMonkey(game, entity->handle); if (closest_monkey.dist < DQN_SQUARED(FP_Game_MetersToPixelsNx1(game->play, 2.f))) { - if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) { + if (TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code)) { entity->carried_monkey = closest_monkey.entity; picked_up_monkey_this_frame = true; TELY_Audio_Play(audio, game->audio[FP_GameAudio_Monkey], 1.f); @@ -711,10 +732,10 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform } if (FP_Game_IsNilEntityHandle(game, entity->carried_monkey)) { - if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) || + if (TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code) || TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_X)) { FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Attack); - } else if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_K) || + } else if (TELY_Platform_InputScanCodeIsPressed(input, controls->range_attack.scan_code) || TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_Y)) { FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_RangeAttack); } @@ -722,12 +743,12 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform } if (FP_Game_IsNilEntityHandle(game, entity->carried_monkey)) { - if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_N) || + if (TELY_Platform_InputScanCodeIsPressed(input, controls->dash.scan_code) || TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_A)) { FP_Game_EntityTransitionState(game, entity, FP_EntityTerryState_Dash); } } else { - if (!picked_up_monkey_this_frame && TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) { + if (!picked_up_monkey_this_frame && TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code)) { FP_GameEntity *portal_monkey = FP_Game_GetEntity(game, entity->carried_monkey); portal_monkey->local_pos = FP_Game_CalcEntityWorldPos(game, entity->handle); entity->carried_monkey = {}; @@ -808,7 +829,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform } if (we_are_clicked_entity) { - if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) || + if (TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code) || TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_X)) { FP_Game_EntityTransitionState(game, entity, FP_EntitySmoochieState_Attack); } else if (acceleration_meters_per_s->x || acceleration_meters_per_s->y) { @@ -902,7 +923,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform } if (we_are_clicked_entity) { - if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) || + if (TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code) || TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_X)) { FP_Game_EntityTransitionState(game, entity, FP_EntitySmoochieState_Attack); } @@ -929,7 +950,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform } if (we_are_clicked_entity) { - if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) || + if (TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code) || TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_X)) { FP_Game_EntityTransitionState(game, entity, FP_EntityClingerState_Attack); } else if (acceleration_meters_per_s->x || acceleration_meters_per_s->y) { @@ -972,7 +993,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform } if (we_are_clicked_entity) { - if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) || + if (TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code) || TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_X)) { FP_Game_EntityTransitionState(game, entity, FP_EntityClingerState_Attack); } @@ -1183,7 +1204,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform } if (we_are_clicked_entity) { - if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) || + if (TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code) || TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_X)) { FP_Game_EntityTransitionState(game, entity, FP_EntityCatfishState_Attack); } else if (acceleration_meters_per_s->x || acceleration_meters_per_s->y) { @@ -1225,7 +1246,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform } if (we_are_clicked_entity) { - if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J) || + if (TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code) || TELY_Platform_InputGamepadKeyIsPressed(input, 0, TELY_PlatformInputGamepadKey_X)) { FP_Game_EntityTransitionState(game, entity, FP_EntityCatfishState_Attack); } @@ -1316,6 +1337,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform } } break; case FP_EntityType_PortalMonkey: break; + case FP_EntityType_Billboard: break; } switch (entity->type) { @@ -1401,6 +1423,7 @@ void FP_EntityActionStateMachine(FP_Game *game, TELY_Audio *audio, TELY_Platform case FP_EntityType_AirportTerryPlane: break; case FP_EntityType_MobSpawner: case FP_EntityType_PortalMonkey: break; + case FP_EntityType_Billboard: break; } } @@ -1408,6 +1431,8 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input { Dqn_Profiler_ZoneScopeWithIndex("FP_Update", FP_ProfileZone_FPUpdate); + FP_GameControls const *controls = &game->controls; + game->play.update_counter++; game->play.clock_ms = DQN_CAST(uint64_t)(platform->input.timer_s * 1000.f); Dqn_ProfilerZone update_zone = Dqn_Profiler_BeginZoneWithIndex(DQN_STRING8("FP_Update: Entity loop"), FP_ProfileZone_FPUpdate_EntityLoop); @@ -1417,13 +1442,13 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input game->play.clicked_entity = game->play.prev_active_entity; // NOTE: Keyboard movement input - if (TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_W)) + if (TELY_Platform_InputScanCodeIsDown(input, controls->up.scan_code)) dir_vector.y = -1.f; - if (TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_A)) + if (TELY_Platform_InputScanCodeIsDown(input, controls->left.scan_code)) dir_vector.x = -1.f; - if (TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_S)) + if (TELY_Platform_InputScanCodeIsDown(input, controls->down.scan_code)) dir_vector.y = +1.f; - if (TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_D)) + if (TELY_Platform_InputScanCodeIsDown(input, controls->right.scan_code)) dir_vector.x = +1.f; // NOTE: Gamepad movement input @@ -1527,6 +1552,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input case FP_EntityType_AirportTerryPlane: break; case FP_EntityType_MobSpawner: case FP_EntityType_PortalMonkey: break; + case FP_EntityType_Billboard: break; } if (move_entity) { @@ -1943,6 +1969,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input case FP_EntityType_AirportTerryPlane: case FP_EntityType_MobSpawner: case FP_EntityType_PortalMonkey: break; + case FP_EntityType_Billboard: break; } } @@ -1963,7 +1990,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input if (game->play.clicked_entity == entity->handle) { if (game->play.in_game_menu == FP_GameInGameMenu_Nil || game->play.in_game_menu == FP_GameInGameMenu_Build) { - if (TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_H)) + if (TELY_Platform_InputScanCodeIsPressed(input, controls->build_mode.scan_code)) game->play.in_game_menu = DQN_CAST(FP_GameInGameMenu)(DQN_CAST(uint32_t)game->play.in_game_menu ^ FP_GameInGameMenu_Build); } @@ -2019,7 +2046,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input } if (game->play.build_mode_can_place_building && - TELY_Platform_InputScanCodeIsPressed(input, TELY_PlatformInputScanCode_J)) { + TELY_Platform_InputScanCodeIsPressed(input, controls->attack.scan_code)) { if (placeable_building.type == FP_EntityType_ClubTerry) { FP_Entity_CreateClubTerry(game, placement_pos, "Club Terry"); TELY_Audio_Play(audio, game->audio[FP_GameAudio_Club], 1.f); @@ -2052,8 +2079,7 @@ void FP_Update(TELY_Platform *platform, FP_Game *game, TELY_PlatformInput *input acceleration_meters_per_s *= 1.f; // TODO(doyle): Penalise the player } - // NOTE: Left-shift lets us strafe in the same direction - if (!TELY_Platform_InputScanCodeIsDown(input, TELY_PlatformInputScanCode_L)) { + if (!TELY_Platform_InputScanCodeIsDown(input, controls->strafe.scan_code)) { if (acceleration_meters_per_s.x) entity->direction = acceleration_meters_per_s.x > 0.f ? FP_GameDirection_Right : FP_GameDirection_Left; else if (acceleration_meters_per_s.y) @@ -2585,6 +2611,54 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer, } } } + + if (entity->type == FP_EntityType_Billboard) { + FP_EntityBillboardState state = DQN_CAST(FP_EntityBillboardState)entity->action.state; + FP_GameKeyBind const *key_bind = nullptr; + Dqn_V2 draw_p = {}; + Dqn_V4 colour = TELY_COLOUR_BLACK_V4; + switch (state) { + case FP_EntityBillboardState_Attack: { + key_bind = &game->controls.attack; + draw_p = Dqn_Rect_InterpolatedPoint(world_hit_box, Dqn_V2_InitNx2(0.65f, 0.2f)); + colour = TELY_Colour_V4InitRGBU32(0xFFE726); + } break; + + case FP_EntityBillboardState_Dash: { + key_bind = &game->controls.dash; + draw_p = Dqn_Rect_InterpolatedPoint(world_hit_box, Dqn_V2_InitNx2(0.62f, -0.07f)); + colour = TELY_Colour_V4InitRGBU32(0xFFE726); + } break; + + case FP_EntityBillboardState_Monkey: { + } break; + + case FP_EntityBillboardState_RangeAttack: { + key_bind = &game->controls.range_attack; + draw_p = Dqn_Rect_InterpolatedPoint(world_hit_box, Dqn_V2_InitNx2(0.33f, -0.2f)); + colour = TELY_Colour_V4InitRGBU32(0x364659); + } break; + + case FP_EntityBillboardState_Strafe: { + key_bind = &game->controls.dash; + draw_p = Dqn_Rect_InterpolatedPoint(world_hit_box, Dqn_V2_InitNx2(0.52f, -0.15f)); + colour = TELY_Colour_V4InitRGBU32(0xFF68A8); + } break; + } + + if (key_bind) { + TELY_Render_PushColourV4(renderer, colour); + TELY_Render_PushFont(renderer, game->talkco_font_xlarge); + DQN_DEFER { + TELY_Render_PopFont(renderer); + TELY_Render_PopColourV4(renderer); + }; + + DQN_ASSERT(key_bind->scan_code >= TELY_PlatformInputScanCode_A && key_bind->scan_code <= TELY_PlatformInputScanCode_Z); + char scan_code_ch = DQN_CAST(char)('A' + (key_bind->scan_code - TELY_PlatformInputScanCode_A)); + TELY_Render_TextF(renderer, draw_p, Dqn_V2_InitNx1(0.5f), "[%c]", scan_code_ch); + } + } } // NOTE: Render overlay UI ===================================================================== @@ -2673,7 +2747,7 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer, bool const have_enough_coins = player->coins >= *mapping.building_base_price; // NOTE: Buy trigger + animation =============================== { - TELY_PlatformInputScanCode key = TELY_PlatformInputScanCode_J; + TELY_PlatformInputScanCode key = game->controls.attack.scan_code; bool trigger_buy_anim = false; if (have_enough_coins) { @@ -2760,7 +2834,7 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer, bool const have_enough_coins = player->coins >= *mapping.upgrade_base_price; // NOTE: Buy trigger + animation =============================== { - TELY_PlatformInputScanCode key = TELY_PlatformInputScanCode_K; + TELY_PlatformInputScanCode key = game->controls.range_attack.scan_code; bool trigger_buy_anim = false; if (have_enough_coins) { @@ -3235,31 +3309,13 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer, Dqn_Rect window_rect = Dqn_Rect_InitNx4(0, 0, DQN_CAST(Dqn_f32)platform->core.window_size.w, DQN_CAST(Dqn_f32)platform->core.window_size.h); if (game->play.state == FP_GameState_IntroScreen || game->play.state == FP_GameState_LoseGame) { - Dqn_f32 tex_scalar = 0.f; + Dqn_f32 tex_scalar = 0.f; { TELY_AssetSpriteAnimation *anim = TELY_Asset_GetSpriteAnimation(&game->atlas_sprite_sheet, g_anim_names.intro_screen_terry); Dqn_Rect tex_rect = game->atlas_sprite_sheet.rects.data[anim->index]; - Dqn_f32 desired_width = window_rect.size.x; + Dqn_f32 desired_width = window_rect.size.x * .25f; tex_scalar = desired_width / tex_rect.size.w; } - - // NOTE: Draw title text =========================================================== - { - TELY_AssetSpriteAnimation *anim = TELY_Asset_GetSpriteAnimation(&game->atlas_sprite_sheet, g_anim_names.intro_screen_title); - Dqn_Rect tex_rect = game->atlas_sprite_sheet.rects.data[anim->index]; - Dqn_Rect dest_rect = {}; - dest_rect.size = tex_rect.size * tex_scalar; - dest_rect.pos = Dqn_Rect_InterpolatedPoint(window_rect, Dqn_V2_InitNx2(0.5f, 0.5f)) - (dest_rect.size * .5f); - - TELY_Render_TextureColourV4(renderer, - game->atlas_sprite_sheet.tex_handle, - tex_rect, - dest_rect, - Dqn_V2_Zero /*rotate origin*/, - 0.f /*rotation*/, - TELY_COLOUR_WHITE_V4); - } - if (game->play.state == FP_GameState_IntroScreen) { // NOTE: Draw terry logo =========================================================== { @@ -3277,29 +3333,12 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer, 0.f /*rotation*/, TELY_COLOUR_WHITE_V4); } - - // NOTE: Draw title subtitle ======================================================= - { - TELY_AssetSpriteAnimation *anim = TELY_Asset_GetSpriteAnimation(&game->atlas_sprite_sheet, g_anim_names.intro_screen_subtitle); - Dqn_Rect tex_rect = game->atlas_sprite_sheet.rects.data[anim->index]; - Dqn_Rect dest_rect = {}; - dest_rect.size = tex_rect.size * tex_scalar; - dest_rect.pos = Dqn_Rect_InterpolatedPoint(window_rect, Dqn_V2_InitNx2(0.5f, 0.5f)) - (dest_rect.size * .5f); - - TELY_Render_TextureColourV4(renderer, - game->atlas_sprite_sheet.tex_handle, - tex_rect, - dest_rect, - Dqn_V2_Zero /*rotate origin*/, - 0.f /*rotation*/, - TELY_COLOUR_WHITE_V4); - } } else { // NOTE: Draw end screen logo ====================================================== TELY_AssetSpriteAnimation *anim = TELY_Asset_GetSpriteAnimation(&game->atlas_sprite_sheet, g_anim_names.end_screen); Dqn_Rect tex_rect = game->atlas_sprite_sheet.rects.data[anim->index]; Dqn_Rect dest_rect = {}; - dest_rect.size = tex_rect.size * tex_scalar; + dest_rect.size = tex_rect.size * (tex_scalar * 1.5f); dest_rect.pos = Dqn_Rect_InterpolatedPoint(window_rect, Dqn_V2_InitNx2(0.5f, 0.5f)) - (dest_rect.size * .5f); TELY_Render_TextureColourV4(renderer, @@ -3328,11 +3367,44 @@ void FP_Render(FP_Game *game, TELY_Platform *platform, TELY_Renderer *renderer, TELY_COLOUR_WHITE_V4); } + // NOTE: Draw title text =============================================================== + { + TELY_AssetSpriteAnimation *anim = TELY_Asset_GetSpriteAnimation(&game->atlas_sprite_sheet, g_anim_names.intro_screen_title); + Dqn_Rect tex_rect = game->atlas_sprite_sheet.rects.data[anim->index]; + Dqn_Rect dest_rect = {}; + dest_rect.size = tex_rect.size * (tex_scalar * 1.5f); + dest_rect.pos = Dqn_Rect_InterpolatedPoint(window_rect, Dqn_V2_InitNx2(0.5f, 0.20f)) - (dest_rect.size * .5f); + + TELY_Render_TextureColourV4(renderer, + game->atlas_sprite_sheet.tex_handle, + tex_rect, + dest_rect, + Dqn_V2_Zero /*rotate origin*/, + 0.f /*rotation*/, + TELY_COLOUR_WHITE_V4); + } + + // NOTE: Draw title subtitle =========================================================== + if (game->play.state == FP_GameState_IntroScreen) { + TELY_AssetSpriteAnimation *anim = TELY_Asset_GetSpriteAnimation(&game->atlas_sprite_sheet, g_anim_names.intro_screen_subtitle); + Dqn_Rect tex_rect = game->atlas_sprite_sheet.rects.data[anim->index]; + Dqn_Rect dest_rect = {}; + dest_rect.size = tex_rect.size * tex_scalar; + dest_rect.pos = Dqn_Rect_InterpolatedPoint(window_rect, Dqn_V2_InitNx2(0.5f, 0.8f)) - (dest_rect.size * .5f); + + TELY_Render_TextureColourV4(renderer, + game->atlas_sprite_sheet.tex_handle, + tex_rect, + dest_rect, + Dqn_V2_Zero /*rotate origin*/, + 0.f /*rotation*/, + TELY_COLOUR_WHITE_V4); + } TELY_Render_PushFont(renderer, game->inter_regular_font); Dqn_f32 t = (DQN_SINF(DQN_CAST(Dqn_f32)input->timer_s * 5.f) + 1.f) / 2.f; TELY_Render_PushColourV4(renderer, TELY_Colour_V4Alpha(TELY_COLOUR_WHITE_V4, t)); - TELY_Render_TextF(renderer, Dqn_Rect_InterpolatedPoint(window_rect, Dqn_V2_InitNx2(0.5f, 0.925f)), Dqn_V2_InitNx1(0.5f), "Press enter to %s", game->play.state == FP_GameState_Play ? "start" : "restart"); + TELY_Render_TextF(renderer, Dqn_Rect_InterpolatedPoint(window_rect, Dqn_V2_InitNx2(0.5f, 0.925f)), Dqn_V2_InitNx1(0.5f), "Press enter to %s", game->play.state == FP_GameState_IntroScreen ? "start" : "restart"); TELY_Render_PopColourV4(renderer); TELY_Render_PopFont(renderer); diff --git a/feely_pona_build.cpp b/feely_pona_build.cpp index 206dec3..53f6d4b 100644 --- a/feely_pona_build.cpp +++ b/feely_pona_build.cpp @@ -226,6 +226,31 @@ int main(int argc, char const **argv) } } + // NOTE: QOI Converter ========================================================================= + uint64_t qoi_converter_timings[2] = {}; + { + qoi_converter_timings[0] = Dqn_OS_PerfCounterNow(); + DQN_DEFER { qoi_converter_timings[1] = Dqn_OS_PerfCounterNow(); }; + + Dqn_CPPBuildContext build_context = {}; + build_context.compiler = Dqn_CPPBuildCompiler_MSVC; + build_context.compile_files = Dqn_Slice_InitCArrayCopy(scratch.arena, { + Dqn_CPPBuildCompileFile{{}, Dqn_FsPath_ConvertF(scratch.arena, "%.*s/External/qoiconv.c", DQN_STRING_FMT(code_dir)) }, + }); + + build_context.compile_flags = Dqn_Slice_InitCArrayCopy(scratch.arena, {DQN_STRING8("cl"), DQN_STRING8("-O2"), DQN_STRING8("-MT"), DQN_STRING8("/nologo")}); + build_context.link_flags = Dqn_Slice_InitCArrayCopy(scratch.arena, {DQN_STRING8("/link"), DQN_STRING8("/incremental:no")}); + build_context.include_dirs = Dqn_Slice_InitCArrayCopy(scratch.arena, {Dqn_FsPath_ConvertF(scratch.arena, "%.*s/External/stb", DQN_STRING_FMT(tely_dir))}); + build_context.build_dir = build_dir; + + if (dry_run) { + Dqn_String8 cmd = Dqn_CPPBuild_ToCommandLine(build_context, Dqn_CPPBuildMode_AlwaysRebuild, scratch.allocator); + Dqn_Print_StdLnF(Dqn_PrintStd_Out, "%.*s\n", DQN_STRING_FMT(cmd)); + } else { + Dqn_CPPBuild_ExecOrAbort(build_context, Dqn_CPPBuildMode_CacheBuild); + } + } + // NOTE: Feely Pona Sprite Packer ============================================================== uint64_t feely_pona_sprite_packer_timings[2] = {}; { @@ -235,14 +260,19 @@ int main(int argc, char const **argv) Dqn_CPPBuildContext build_context = {}; build_context.compiler = Dqn_CPPBuildCompiler_MSVC; build_context.compile_files = Dqn_Slice_InitCArrayCopy(scratch.arena, { - Dqn_CPPBuildCompileFile{{}, Dqn_FsPath_ConvertF(scratch.arena, "%.*s/feely_pona_sprite_packer.h", DQN_STRING_FMT(code_dir)) }, + Dqn_CPPBuildCompileFile{{}, Dqn_FsPath_ConvertF(scratch.arena, "%.*s/feely_pona_sprite_packer.cpp", DQN_STRING_FMT(code_dir)) }, }); build_context.compile_flags = common_compile_flags; build_context.link_flags = common_link_flags; build_context.build_dir = build_dir; - Dqn_CPPBuild_ExecOrAbort(build_context, Dqn_CPPBuildMode_CacheBuild); + if (dry_run) { + Dqn_String8 cmd = Dqn_CPPBuild_ToCommandLine(build_context, Dqn_CPPBuildMode_AlwaysRebuild, scratch.allocator); + Dqn_Print_StdLnF(Dqn_PrintStd_Out, "%.*s\n", DQN_STRING_FMT(cmd)); + } else { + Dqn_CPPBuild_ExecOrAbort(build_context, Dqn_CPPBuildMode_CacheBuild); + } } // NOTE: Feely Pona No DLL ===================================================================== @@ -493,6 +523,7 @@ int main(int argc, char const **argv) Dqn_Print_StdLnF(Dqn_PrintStd_Out, "\n-- Dqn_CPPBuild Timings (%.2fms)", Dqn_OS_PerfCounterMs(build_timings[0], build_timings[1])); Dqn_Print_StdLnF(Dqn_PrintStd_Out, " robocopy: %.2fms", Dqn_OS_PerfCounterMs(robocopy_timings[0], robocopy_timings[1])); Dqn_Print_StdLnF(Dqn_PrintStd_Out, " raylib: %.2fms", Dqn_OS_PerfCounterMs(raylib_pc_timings[0], raylib_pc_timings[1])); + Dqn_Print_StdLnF(Dqn_PrintStd_Out, " qoi_converter: %.2fms", Dqn_OS_PerfCounterMs(qoi_converter_timings[0], qoi_converter_timings[1])); Dqn_Print_StdLnF(Dqn_PrintStd_Out, " feely pona sprite packer: %.2fms", Dqn_OS_PerfCounterMs(feely_pona_sprite_packer_timings[0], feely_pona_sprite_packer_timings[1])); Dqn_Print_StdLnF(Dqn_PrintStd_Out, " feely pona (no dll): %.2fms", Dqn_OS_PerfCounterMs(feely_pona_no_dll_timings[0], feely_pona_no_dll_timings[1])); Dqn_Print_StdLnF(Dqn_PrintStd_Out, " feely pona (dll): %.2fms", Dqn_OS_PerfCounterMs(feely_pona_dll_timings[0], feely_pona_dll_timings[1])); diff --git a/feely_pona_entity.h b/feely_pona_entity.h index c8cea44..b6b2e57 100644 --- a/feely_pona_entity.h +++ b/feely_pona_entity.h @@ -24,6 +24,7 @@ enum FP_EntityType FP_EntityType_Smoochie, FP_EntityType_Terry, FP_EntityType_PhoneMessageProjectile, + FP_EntityType_Billboard, FP_EntityType_Count, }; @@ -127,6 +128,15 @@ enum FP_EntityHeartState FP_EntityHeartState_Idle, }; +enum FP_EntityBillboardState +{ + FP_EntityBillboardState_Attack, + FP_EntityBillboardState_Dash, + FP_EntityBillboardState_Monkey, + FP_EntityBillboardState_RangeAttack, + FP_EntityBillboardState_Strafe, +}; + struct FP_EntityRenderData { FP_Meters height; diff --git a/feely_pona_entity_create.cpp b/feely_pona_entity_create.cpp index a49afa8..c11e15e 100644 --- a/feely_pona_entity_create.cpp +++ b/feely_pona_entity_create.cpp @@ -39,20 +39,20 @@ FP_EntityRenderData FP_Entity_GetRenderData(FP_Game *game, FP_EntityType type, u case FP_EntityTerryState_Attack: { switch (direction) { - case FP_GameDirection_Up: result.anim_name = g_anim_names.terry_attack_up; break; - case FP_GameDirection_Down: result.anim_name = g_anim_names.terry_attack_down; break; - case FP_GameDirection_Left: result.anim_name = g_anim_names.terry_attack_side; break; - case FP_GameDirection_Right: result.anim_name = g_anim_names.terry_attack_side; result.flip = TELY_AssetFlip_X; break; + case FP_GameDirection_Up: result.anim_name = g_anim_names.terry_attack_up; result.height.meters *= 1.5f; break; + case FP_GameDirection_Down: result.anim_name = g_anim_names.terry_attack_down; result.height.meters *= 1.6f; break; + case FP_GameDirection_Left: result.anim_name = g_anim_names.terry_attack_side; result.height.meters *= 1.5f; break; + case FP_GameDirection_Right: result.anim_name = g_anim_names.terry_attack_side; result.height.meters *= 1.5f; result.flip = TELY_AssetFlip_X; break; case FP_GameDirection_Count: DQN_INVALID_CODE_PATH; break; } } break; case FP_EntityTerryState_RangeAttack: { switch (direction) { - case FP_GameDirection_Up: result.anim_name = g_anim_names.terry_attack_phone_up; break; - case FP_GameDirection_Down: result.anim_name = g_anim_names.terry_attack_phone_down; break; - case FP_GameDirection_Left: result.anim_name = g_anim_names.terry_attack_phone_side; result.flip = TELY_AssetFlip_X; break; - case FP_GameDirection_Right: result.anim_name = g_anim_names.terry_attack_phone_side; break; + case FP_GameDirection_Up: result.anim_name = g_anim_names.terry_attack_phone_up; result.height.meters *= 1.25f; break; + case FP_GameDirection_Down: result.anim_name = g_anim_names.terry_attack_phone_down; result.height.meters *= 1.25f; break; + case FP_GameDirection_Left: result.anim_name = g_anim_names.terry_attack_phone_side; result.height.meters *= 1.25f; result.flip = TELY_AssetFlip_X; break; + case FP_GameDirection_Right: result.anim_name = g_anim_names.terry_attack_phone_side; result.height.meters *= 1.25f; break; case FP_GameDirection_Count: DQN_INVALID_CODE_PATH; break; } } break; @@ -129,7 +129,7 @@ FP_EntityRenderData FP_Entity_GetRenderData(FP_Game *game, FP_EntityType type, u } break; case FP_EntityType_MerchantPhoneCompany: { - result.height.meters = 3.66f; + result.height.meters = 5.f; FP_EntityMerchantPhoneCompanyState state = DQN_CAST(FP_EntityMerchantPhoneCompanyState)raw_state; switch (state) { case FP_EntityMerchantPhoneCompanyState_Idle: result.anim_name = g_anim_names.merchant_phone_company; break; @@ -263,6 +263,18 @@ FP_EntityRenderData FP_Entity_GetRenderData(FP_Game *game, FP_EntityType type, u result.anim_name = g_anim_names.portal_monk; break; } break; + case FP_EntityType_Billboard: { + result.height.meters = 12.f; + FP_EntityBillboardState state = DQN_CAST(FP_EntityBillboardState)raw_state; + switch (state) { + case FP_EntityBillboardState_Attack: result.anim_name = g_anim_names.map_billboard_attack; break; + case FP_EntityBillboardState_Dash: result.anim_name = g_anim_names.map_billboard_dash; break; + case FP_EntityBillboardState_Monkey: result.anim_name = g_anim_names.map_billboard_monkey; break; + case FP_EntityBillboardState_RangeAttack: result.anim_name = g_anim_names.map_billboard_range_attack; break; + case FP_EntityBillboardState_Strafe: result.anim_name = g_anim_names.map_billboard_strafe; break; + } + } break; + case FP_EntityType_Count: DQN_INVALID_CODE_PATH; break; } @@ -819,3 +831,27 @@ static FP_GameEntityHandle FP_Entity_CreateAirportTerryPlane(FP_Game *game, Dqn_ entity->base_acceleration_per_s.meters = 32.f; return result; } + +static FP_GameEntityHandle FP_Entity_CreateBillboard(FP_Game *game, Dqn_V2 pos, FP_EntityBillboardState state, DQN_FMT_STRING_ANNOTATE char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + FP_GameEntity *entity = FP_Game_MakeEntityPointerFV(game, fmt, args); + FP_GameEntityHandle result = entity->handle; + va_end(args); + + entity->action.state = state; + entity->action.next_state = state; + entity->type = FP_EntityType_Billboard; + FP_EntityRenderData render_data = FP_Entity_GetRenderData(game, entity->type, state, FP_GameDirection_Down); + entity->sprite_height = render_data.height; + uint64_t duration_ms = FP_GAME_ENTITY_ACTION_INFINITE_TIMER; + FP_Game_EntityActionReset(game, result, duration_ms, render_data.sprite); + + entity->local_pos = pos; + FP_Entity_AddDebugEditorFlags(game, result); + + entity->local_hit_box_offset = Dqn_V2_InitNx2(0, render_data.render_size.h * .1f); + entity->local_hit_box_size = Dqn_V2_InitNx2(render_data.render_size.w, render_data.render_size.h - (render_data.render_size.h * .4f)); + return result; +} diff --git a/feely_pona_game.cpp b/feely_pona_game.cpp index 8f076c9..c6be49d 100644 --- a/feely_pona_game.cpp +++ b/feely_pona_game.cpp @@ -912,6 +912,7 @@ static void FP_Game_EntityTransitionState(FP_Game *game, FP_GameEntity *entity, case FP_EntityType_AirportTerryPlane: case FP_EntityType_MobSpawner: case FP_EntityType_PortalMonkey: break; + case FP_EntityType_Billboard: break; } // NOTE: If no returns are hit above we proceed with the state change entity->action.next_state = desired_state; diff --git a/feely_pona_game.h b/feely_pona_game.h index 67d0c7a..52e0733 100644 --- a/feely_pona_game.h +++ b/feely_pona_game.h @@ -352,18 +352,39 @@ struct FP_GamePlay FP_GameState state; }; +struct FP_GameKeyBind +{ + TELY_PlatformInputScanCode scan_code; + TELY_PlatformInputGamepadKey gamepad_key; +}; + +struct FP_GameControls +{ + FP_GameKeyBind up; + FP_GameKeyBind down; + FP_GameKeyBind left; + FP_GameKeyBind right; + FP_GameKeyBind attack; + FP_GameKeyBind range_attack; + FP_GameKeyBind build_mode; + FP_GameKeyBind strafe; + FP_GameKeyBind dash; +}; + struct FP_Game { - TELY_AssetFontHandle inter_regular_font_large; - TELY_AssetFontHandle inter_regular_font; - TELY_AssetFontHandle inter_italic_font; - TELY_AssetFontHandle jetbrains_mono_font; - TELY_AssetFontHandle talkco_font; - TELY_AssetFontHandle talkco_font_large; - TELY_AssetAudioHandle audio[FP_GameAudio_Count]; - TELY_AssetSpriteSheet atlas_sprite_sheet; - TELY_RFui rfui; - FP_GamePlay play; + TELY_AssetFontHandle inter_regular_font_large; + TELY_AssetFontHandle inter_regular_font; + TELY_AssetFontHandle inter_italic_font; + TELY_AssetFontHandle jetbrains_mono_font; + TELY_AssetFontHandle talkco_font; + TELY_AssetFontHandle talkco_font_large; + TELY_AssetFontHandle talkco_font_xlarge; + TELY_AssetAudioHandle audio[FP_GameAudio_Count]; + TELY_AssetSpriteSheet atlas_sprite_sheet; + TELY_RFui rfui; + FP_GamePlay play; + FP_GameControls controls; }; struct FP_GameAStarNode diff --git a/feely_pona_sprite_packer.cpp b/feely_pona_sprite_packer.cpp index 7edfec2..1cee10d 100644 --- a/feely_pona_sprite_packer.cpp +++ b/feely_pona_sprite_packer.cpp @@ -58,7 +58,7 @@ static Dqn_String8 SpriteAnimNameFromFilePath(Dqn_String8 path) int main(int argc, char const *argv[]) { - Dqn_Library_Init(); + Dqn_Library_Init(Dqn_LibraryOnInit_Nil); Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(nullptr); if (argc != 4) { diff --git a/project.rdbg b/project.rdbg index 3a57102..371468c 100644 Binary files a/project.rdbg and b/project.rdbg differ