diff --git a/app_render.cpp b/app_render.cpp index a8da3b3..4552232 100644 --- a/app_render.cpp +++ b/app_render.cpp @@ -21,6 +21,8 @@ extern "C" { void app_Render(App *app) { + render_SetModes(render_ModeDefault, time_Now()); + for (tree_Node *i = tree_FirstNode(app->entity->entities); i != NULL; i = tree_Node_Next(i)) { @@ -38,6 +40,17 @@ void app_Render(App *app) { (int)round(box.lefttop.x + box.size.x), (int)round(box.lefttop.y + box.size.y)); } + if (e->misc) { + if (e->misc->textbox) { + setlinecolor(RGB(0, 0, 255)); + Box2 box = camera_TransformBox2(app->camera, box2_Offset(e->misc->textbox->trigger_box, e->position->position)); + rectangle( + (int)round(box.lefttop.x), + (int)round(box.lefttop.y), + (int)round(box.lefttop.x + box.size.x), + (int)round(box.lefttop.y + box.size.y)); + } + } } static FillMode mode_rotate = { @@ -51,6 +64,8 @@ void app_Render(App *app) { render_DrawBundleW(app, render_FindBundle("info_plate"), vec2(600, 550)); + render_DrawBundleW(app, render_FindBundle("info_plate_small_1"), vec2(250, 200)); + render_DrawBundleW(app, render_FindBundle("info_plate_small_2"), vec2(750, 200)); // Draw particles diff --git a/bundles.txt b/bundles.txt index ece069a..7951b98 100644 --- a/bundles.txt +++ b/bundles.txt @@ -31,3 +31,79 @@ P -28.000000 -43.000000 P 25.000000 -29.000000 ENDPRIM ENDBUNDLE + +BUNDLE info_plate_small_1 +PRIM POLY +FG 255 255 255 +BG 0 0 0 +P 9.000000 -83.000000 +P 39.000000 -57.000000 +P 35.000000 -16.000000 +P 18.000000 0.000000 +P -12.000000 0.000000 +P -28.000000 -28.000000 +P -26.000000 -65.000000 +ENDPRIM +PRIM LINESTRIP +FG 0 0 0 +BG 0 0 0 +P 7.000000 -80.000000 +P 28.000000 -55.000000 +P 26.000000 -22.000000 +P 15.000000 -6.000000 +ENDPRIM +PRIM LINES +FG 0 0 0 +BG 0 0 0 +P -18.000000 -63.000000 +P 15.000000 -52.000000 +P -15.000000 -46.000000 +P 16.000000 -38.000000 +P -16.000000 -25.000000 +P 14.000000 -23.000000 +ENDPRIM +ENDBUNDLE + +BUNDLE info_plate_small_2 +PRIM POLY +FG 255 255 255 +BG 0 0 0 +P 7.000000 -79.000000 +P 45.000000 -63.000000 +P 49.000000 -23.000000 +P 34.000000 0.000000 +P -6.000000 0.000000 +P -24.000000 -23.000000 +P -24.000000 -56.000000 +ENDPRIM +PRIM LINESTRIP +FG 0 0 0 +BG 0 0 0 +P 7.000000 -76.000000 +P 33.000000 -52.000000 +P 37.000000 -24.000000 +P 31.000000 -4.000000 +ENDPRIM +PRIM LINES +FG 0 0 0 +BG 0 0 0 +P -16.000000 -48.000000 +P 15.000000 -55.000000 +P -12.000000 -37.000000 +P 20.000000 -41.000000 +P -6.000000 -18.000000 +P 24.000000 -27.000000 +P 12.000000 -13.000000 +P 27.000000 -15.000000 +ENDPRIM +ENDBUNDLE + +BUNDLE spike +PRIM POLY +FG 255 255 255 +BG 0 0 0 +P 0.000000 -50.000000 +P 15.000000 0.000000 +P -15.000000 0.000000 +ENDPRIM +ENDBUNDLE diff --git a/entity.c b/entity.c index 3bcfcba..69ce489 100644 --- a/entity.c +++ b/entity.c @@ -2,6 +2,7 @@ #include "entity.h" #include "app.h" #include "camera.h" +#include "mapper_misc.h" #include "physics.h" #include "util/assert.h" #include "util/tree.h" @@ -11,6 +12,23 @@ #include +// Frees every member if they exists. +static void _entity_FreeMembers(Entity *e) { + if (e->position) + free(e->position); + if (e->hitbox) + free(e->hitbox); + if (e->player) + free(e->player); + if (e->name) + free(e->name); + if (e->render) + render_DeleteComponent(e->render); + if (e->misc) + misc_DeleteComponent(e->misc); +} + + System_Entity *entity_NewSystem(App *super) { System_Entity *sys = malloc(sizeof(System_Entity)); @@ -32,14 +50,7 @@ void entity_DeleteSystem(System_Entity *sys) { player_DeleteEntity(sys->super->player, e->id); camera_DeleteEntity(sys->super->camera, e->id); - if (e->position) - free(e->position); - if (e->hitbox) - free(e->hitbox); - if (e->player) - free(e->player); - if (e->name) - free(e->name); + _entity_FreeMembers(e); } tree_Destroy(sys->entities); @@ -87,14 +98,7 @@ static inline void _entity_Delete(System_Entity *sys, uintptr_t id) { player_DeleteEntity(sys->super->player, e->id); camera_DeleteEntity(sys->super->camera, e->id); - if (e->position) - free(e->position); - if (e->hitbox) - free(e->hitbox); - if (e->player) - free(e->player); - if (e->name) - free(e->name); + _entity_FreeMembers(e); tree_Delete(sys->entities, node); } @@ -116,6 +120,6 @@ void entity_Advance(System_Entity *sys, Duration deltaTime) { Entity *e = (Entity *)(i->data); if (e->thinker) - e->thinker(e, deltaTime); + e->thinker(sys->super, e, deltaTime); } } diff --git a/entity.h b/entity.h index c282a48..3bfe180 100644 --- a/entity.h +++ b/entity.h @@ -6,6 +6,8 @@ #include "util/tree.h" #include "physics.h" #include "player.h" +#include "render_component.h" +#include "mapper_misc.h" #ifdef __cplusplus @@ -13,8 +15,9 @@ extern "C" { #endif +typedef struct _App App; typedef struct _Entity Entity; -typedef void (*entity_Thinker)(Entity *e, Duration deltaTime); // The Thinker function type assigned to Entities +typedef void (*entity_Thinker)(App *app, Entity *e, Duration deltaTime); // The Thinker function type assigned to Entities // Entity. @@ -25,6 +28,8 @@ typedef struct _Entity { Component_Position *position; Component_Hitbox *hitbox; Component_Player *player; + Component_Render *render; + Component_Misc *misc; entity_Thinker thinker; // Called by System_Entity each frame if not NULL. void *thinkerData; // Data managed by the Thinker, if exists. @@ -39,8 +44,6 @@ typedef struct _Entity { } while (false) -typedef struct _App App; - // Entity manager. typedef struct { App *super; diff --git a/mapper_misc.c b/mapper_misc.c new file mode 100644 index 0000000..b4ce704 --- /dev/null +++ b/mapper_misc.c @@ -0,0 +1,87 @@ + +#include "mapper_misc.h" +#include "app.h" +#include "entity.h" +#include "types.h" +#include "util/assert.h" +#include + + +void misc_DeleteComponent(Component_Misc *misc) { + if (misc) { + if (misc->textbox) { + if (misc->textbox->text) + free(misc->textbox->text); + free(misc->textbox); + } + if (misc->respawn) + free(misc->respawn); + if (misc->hazard) + free(misc->hazard); + free(misc); + } +} + + +void misc_thinker_HazardRespawn(App *app, Entity *e, Duration deltaTime) { + if (!e->misc || !e->misc->respawn) { + WARN("called on an entity without misc or misc.respawn", 0); + return; + } + if (app->player->player == NULL) // No player + return; + Component_Player *p = app->player->player; + + Box2 worldbox; // Offset if position component exists + Vec2 worldspawn; + if (e->position) { + worldbox = box2_Offset(e->misc->respawn->trigger_box, e->position->position); + worldspawn = vec2_Add(e->misc->respawn->respawn_pos, e->position->position); + } else { + worldbox = e->misc->respawn->trigger_box; + worldspawn = e->misc->respawn->respawn_pos; + } + + if (box2_Contains(worldbox, p->super->position->position)) + p->hazardRespawn = worldspawn; +} + +void misc_thinker_Textbox(App *app, Entity *e, Duration deltaTime) { + if (!e->misc || !e->misc->textbox) { + WARN("called on an entity without misc or misc.textbox", 0); + return; + } + misc_Textbox *t = e->misc->textbox; + if (app->player->player == NULL) { // No player, fade out + t->progress = fmaxf(0.0f, t->progress - duration_Seconds(deltaTime) * MISC_TEXTBOX_FADEIN_SPEED); + return; + } + Component_Player *p = app->player->player; + + Box2 worldbox; // Offset if position component exists + ASSERT(e->position && "Textboxes must have a position component"); + worldbox = box2_Offset(t->trigger_box, e->position->position); + + if (box2_Contains(worldbox, p->super->position->position)) + // Fade in + t->progress = fminf(1.0f, t->progress + duration_Seconds(deltaTime) * MISC_TEXTBOX_FADEIN_SPEED); + else + // Fade out + t->progress = fmaxf(0.0f, t->progress - duration_Seconds(deltaTime) * MISC_TEXTBOX_FADEIN_SPEED); +} + + +// Utility functions for creating misc entities +void misc_InstantiateTextbox(App *app, Entity *e, const char *text, Box2 trigger_box, float offset) { + ASSERT(e->misc == NULL && "Instantiate must be called with e.misc not set yet"); + ASSERT(e->render == NULL && "Instantiate must be called with e.render not set yet"); + e->misc = zero_malloc(sizeof(Component_Misc)); + e->misc->textbox = zero_malloc(sizeof(misc_Textbox)); + + e->misc->textbox->text = copy_malloc(text); + e->misc->textbox->trigger_box = trigger_box; + e->misc->textbox->offset = offset; + + e->render = render_NewComponentFunc(app, &misc_render_Textbox, NULL); + e->thinker = &misc_thinker_Textbox; +} diff --git a/mapper_misc.h b/mapper_misc.h new file mode 100644 index 0000000..f84ff61 --- /dev/null +++ b/mapper_misc.h @@ -0,0 +1,61 @@ +#pragma once + +#include "types.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _App App; +typedef struct _Entity Entity; + + +#define MISC_TEXTBOX_FADEIN_SPEED 5.0 // Fades in/out in 1/5 seconds + +// A textbox that shows itself when the player is close +typedef struct { + char *text; // Allocated & copied + Box2 trigger_box; // Relative + float progress; // [0.0, 1.0] + float offset; // Offset of the top of the text to its origin, usually negative +} misc_Textbox; + +// Thinker for textboxes +// Tracks the player & see if they're in the box +void misc_thinker_Textbox(App *app, Entity *e, Duration deltaTime); +// Render callback for textboxes +void misc_render_Textbox(App *app, Entity *e, Vec2 entity_screen_pos, void *user); + + +// Hazard respawn point +typedef struct { + Box2 trigger_box; + Vec2 respawn_pos; +} misc_HazardRespawn; + +// Thinker for hazard respawns +// Tracks the player & sets its respawn point +void misc_thinker_HazardRespawn(App *app, Entity *e, Duration deltaTime); + + +// Misc data an entity in the map might want. +// Used as patches for quick logic like hazard respawn & textbox +typedef struct { + misc_Textbox *textbox; + misc_HazardRespawn *respawn; + Box2 *hazard; // Harms the player on contact if not null, relative +} Component_Misc; + +// Deletes everything nested in misc, and then itself. +void misc_DeleteComponent(Component_Misc *misc); + + +// Inserts the required components for a textbox. +// Creates misc + render components & sets the thinker. +void misc_InstantiateTextbox(App *app, Entity *e, const char *text, Box2 trigger_box, float offset); + + +#ifdef __cplusplus +} +#endif diff --git a/mapper_misc_render.cpp b/mapper_misc_render.cpp new file mode 100644 index 0000000..d31dffd --- /dev/null +++ b/mapper_misc_render.cpp @@ -0,0 +1,67 @@ + +#include "mapper_misc.h" +#include "app.h" +#include "entity.h" +#include "render_util.h" +#include "util/assert.h" +#include +#include +#include + + +#ifdef __MINGW32__ +#define NCHAR char +#else +#define NCHAR wchar_t +#endif + +namespace { +static vector_Vector *tbuf; + +void convert_text(const char *str) { + if (!tbuf) + tbuf = vector_Create(sizeof(NCHAR)); + + const NCHAR zero = 0; + + vector_Clear(tbuf); + int len = strlen(str); + int i = 0; + while (i < len) { + NCHAR wc = str[i]; + vector_Push(tbuf, &wc); + i++; + } + + vector_Push(tbuf, &zero); +} +} // namespace + + +extern "C" void misc_render_Textbox(App *app, Entity *e, Vec2 entity_screen_pos, void *user) { + if (!e->misc || !e->misc->textbox) { + WARN("called on an entity without misc or misc.textbox", 0); + return; + } + misc_Textbox *t = e->misc->textbox; + + // If we're too dim to see, quit early + if (t->progress < EPS) + return; + + // Set the color + int rgb = (int)round(fminf(t->progress, 1.0f) * 255.0); + settextcolor(RGB(rgb, rgb, rgb)); + + // Compute the bounding rect + RECT rect; + ASSERT(e->position && "Textboxes must have a position component"); + rect.left = (LONG)round(entity_screen_pos.x) - 20; + rect.right = (LONG)round(entity_screen_pos.x) + 20; + rect.top = (LONG)round(entity_screen_pos.y) + t->offset; + rect.top = (LONG)round(entity_screen_pos.y) + t->offset + 40; + + // Convert & draw + convert_text(t->text); + drawtext((LPCTSTR)vector_Data(tbuf), &rect, DT_CENTER | DT_NOCLIP); +} diff --git a/player.c b/player.c index 5e6a95b..165115c 100644 --- a/player.c +++ b/player.c @@ -75,7 +75,7 @@ void player_Advance(System_Player *sys, Duration deltaTime) { sys->super->particle, vec2_Add(vec2_Random(-20, 20, -10, 10), to_pos), vec2(0, -100), 2, 6, 6, - duration_FromSeconds(0), &render_ModeDefault); + duration_FromSeconds(0), &render_ModeInverse); } // Particles when dashing if (time_Since(p->lastDash).microseconds < dashLength.microseconds && dabs(p->super->position->velocity.x) > EPS) { diff --git a/player.h b/player.h index edd99ba..1799fd4 100644 --- a/player.h +++ b/player.h @@ -25,6 +25,8 @@ typedef struct { double storedSpeedY; // Speed stored in the middle of a dash bool onGround; // If the player is on the ground? bool moveLeft, moveRight; // If the player is moving left/right? + + Vec2 hazardRespawn; // Where the last hazard respawn is } Component_Player; diff --git a/render_bundle.c b/render_bundle.c index 009cd06..4a8923e 100644 --- a/render_bundle.c +++ b/render_bundle.c @@ -37,6 +37,8 @@ void render_DeleteBundle(render_Bundle *b) { vector_Destroy(p->points); } vector_Destroy(b->prims); + if (b->name) + free(b->name); free(b); } diff --git a/render_bundle_draw.cpp b/render_bundle_draw.cpp index 3d29d0e..534f60a 100644 --- a/render_bundle_draw.cpp +++ b/render_bundle_draw.cpp @@ -19,6 +19,8 @@ extern "C" void render_DrawPrimitiveW(App *app, render_Primitive *p, Vec2 offset buff = vector_Create(sizeof(POINT)); vector_Clear(buff); + bool needDraw = false; + // Construct the points in screen space for (int i = 0; i < vector_Size(p->points); i++) { Vec2 realpos = vec2_Add(*((Vec2 *)vector_At(p->points, i)), offset); @@ -27,8 +29,12 @@ extern "C" void render_DrawPrimitiveW(App *app, render_Primitive *p, Vec2 offset // Really weird fprintf(stderr, "[WARN][render_DrawPrimitiveW] called without a Camera system\n"); screenpos = realpos; - } else + needDraw = true; + } else { screenpos = camera_TransformVec2(app->camera, realpos); + needDraw = needDraw || box2_Contains(app->camera->cam, realpos); + } + // Round the screen position to ints POINT rounded; @@ -37,32 +43,35 @@ extern "C" void render_DrawPrimitiveW(App *app, render_Primitive *p, Vec2 offset vector_Push(buff, &rounded); } - // Set the colors - setlinecolor(p->fg); - setfillcolor(p->fg); - setbkcolor(p->bg); - render_SetModes(p->mode, time_Now()); + // See if any of the points are in the camera box + if (needDraw) { + // Set the colors + setlinecolor(p->fg); + setfillcolor(p->fg); + setbkcolor(p->bg); + render_SetModes(p->mode, time_Now()); - // Draw the converted primitive - switch (p->type) { - case render_Lines: - if (vector_Size(buff) % 2 != 0) - WARN("render_Lines drawed odd numbers of points", 0); - for (int i = 0; i < vector_Size(buff) - 1; i += 2) { - POINT p0 = *(POINT *)vector_At(buff, i); - POINT p1 = *(POINT *)vector_At(buff, i + 1); - line(p0.x, p0.y, p1.x, p1.y); - } - break; - case render_LineStrip: - for (int i = 0; i < vector_Size(buff) - 1; i++) { - POINT p0 = *(POINT *)vector_At(buff, i); - POINT p1 = *(POINT *)vector_At(buff, i + 1); - line(p0.x, p0.y, p1.x, p1.y); - } - break; - case render_Polygon: - fillpolygon((POINT *)vector_Data(buff), vector_Size(buff)); - break; + // Draw the converted primitive + switch (p->type) { + case render_Lines: + if (vector_Size(buff) % 2 != 0) + WARN("render_Lines drawed odd numbers of points", 0); + for (int i = 0; i < vector_Size(buff) - 1; i += 2) { + POINT p0 = *(POINT *)vector_At(buff, i); + POINT p1 = *(POINT *)vector_At(buff, i + 1); + line(p0.x, p0.y, p1.x, p1.y); + } + break; + case render_LineStrip: + for (int i = 0; i < vector_Size(buff) - 1; i++) { + POINT p0 = *(POINT *)vector_At(buff, i); + POINT p1 = *(POINT *)vector_At(buff, i + 1); + line(p0.x, p0.y, p1.x, p1.y); + } + break; + case render_Polygon: + fillpolygon((POINT *)vector_Data(buff), vector_Size(buff)); + break; + } } } diff --git a/render_component.c b/render_component.c new file mode 100644 index 0000000..b512cce --- /dev/null +++ b/render_component.c @@ -0,0 +1,29 @@ + +#include "render_component.h" +#include "app.h" +#include "render_bundle.h" +#include "types.h" + + +Component_Render *render_NewComponent(App *app, const char *bundle_name) { + render_Bundle *b = render_FindBundle(bundle_name); + if (b == NULL) // Not found + return NULL; + + Component_Render *r = zero_malloc(sizeof(Component_Render)); + r->super = app; + r->bundle = b; + return r; +} + +Component_Render *render_NewComponentFunc(App *app, render_CustomFunc func, void *data) { + Component_Render *r = zero_malloc(sizeof(Component_Render)); + r->super = app; + r->custom = func; + r->custom_data = data; + return r; +} + +void render_DeleteComponent(Component_Render *r) { + free(r); +} diff --git a/render_component.h b/render_component.h new file mode 100644 index 0000000..8204209 --- /dev/null +++ b/render_component.h @@ -0,0 +1,35 @@ +#pragma once + +#include "render_bundle.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _App App; +typedef struct _Entity Entity; + +typedef void (*render_CustomFunc)(App *app, Entity *e, Vec2 entity_screen_pos, void *user); + +// Rendering component. +// This is mostly for components requiring static renders. +typedef struct { + App *super; + + render_Bundle *bundle; // A render bundle, usually found by render_FindBundle() + render_CustomFunc custom; // Custom rendering function + void *custom_data; // User data for the callback +} Component_Render; + + +// Creates a new component with a static render bundle +Component_Render *render_NewComponent(App *app, const char *bundle_name); +// Creates a new component with a callback for rendering +Component_Render *render_NewComponentFunc(App *app, render_CustomFunc func, void *data); +void render_DeleteComponent(Component_Render *r); + + +#ifdef __cplusplus +} +#endif diff --git a/types.c b/types.c index 9ff02b1..67ee331 100644 --- a/types.c +++ b/types.c @@ -82,6 +82,17 @@ bool box2_Intersects(const Box2 x, const Box2 y, Box2 *out_intersection) { } } +bool box2_Contains(const Box2 box, const Vec2 point) { + return box.lefttop.x < point.x && + box.lefttop.x + box.size.x > point.x && + box.lefttop.y < point.y && + box.lefttop.y + box.size.y > point.y; +} + +bool box2_NotZero(Box2 box) { + return box.size.x > EPS && box.size.y > EPS; +} + Vec2 box2_Center(Box2 box) { return vec2( box.lefttop.x + box.size.x / 2.0, diff --git a/types.h b/types.h index 1a00d71..ece0e7b 100644 --- a/types.h +++ b/types.h @@ -58,6 +58,8 @@ typedef struct { // Intersection test. bool box2_Intersects(const Box2 x, const Box2 y, Box2 *out_intersection); +// Contain test. +bool box2_Contains(const Box2 box, const Vec2 point); Vec2 box2_Center(Box2 box); Box2 box2_FromCenter(Vec2 center, Vec2 size); @@ -66,6 +68,9 @@ Box2 box2_Offset(Box2 box, Vec2 offset); Box2 box2_OffsetX(Box2 box, double offsetX); Box2 box2_OffsetY(Box2 box, double offsetY); +// Returns true if the area of the box is not 0. +bool box2_NotZero(Box2 box); + // Time duration. typedef struct {