Misc functions like textbox

This commit is contained in:
Edgaru089 2024-03-30 21:25:39 +08:00
parent 93ffbfd66c
commit c69fe1738c
15 changed files with 454 additions and 48 deletions

View File

@ -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

View File

@ -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

View File

@ -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 <string.h>
// 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);
}
}

View File

@ -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;

87
mapper_misc.c Normal file
View File

@ -0,0 +1,87 @@
#include "mapper_misc.h"
#include "app.h"
#include "entity.h"
#include "types.h"
#include "util/assert.h"
#include <stdio.h>
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;
}

61
mapper_misc.h Normal file
View File

@ -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

67
mapper_misc_render.cpp Normal file
View File

@ -0,0 +1,67 @@
#include "mapper_misc.h"
#include "app.h"
#include "entity.h"
#include "render_util.h"
#include "util/assert.h"
#include <stdio.h>
#include <math.h>
#include <easyx.h>
#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);
}

View File

@ -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) {

View File

@ -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;

View File

@ -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);
}

View File

@ -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,6 +43,8 @@ extern "C" void render_DrawPrimitiveW(App *app, render_Primitive *p, Vec2 offset
vector_Push(buff, &rounded);
}
// See if any of the points are in the camera box
if (needDraw) {
// Set the colors
setlinecolor(p->fg);
setfillcolor(p->fg);
@ -65,4 +73,5 @@ extern "C" void render_DrawPrimitiveW(App *app, render_Primitive *p, Vec2 offset
fillpolygon((POINT *)vector_Data(buff), vector_Size(buff));
break;
}
}
}

29
render_component.c Normal file
View File

@ -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);
}

35
render_component.h Normal file
View File

@ -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

11
types.c
View File

@ -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,

View File

@ -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 {