2024-03-30 21:25:39 +08:00
|
|
|
|
|
|
|
#include "mapper_misc.h"
|
|
|
|
#include "app.h"
|
|
|
|
#include "entity.h"
|
2024-03-30 22:05:49 +08:00
|
|
|
#include "physics.h"
|
2024-04-02 08:56:35 +08:00
|
|
|
#include "player.h"
|
2024-04-18 11:11:51 +08:00
|
|
|
#include "render_util.h"
|
2024-03-30 21:25:39 +08:00
|
|
|
#include "types.h"
|
2024-04-30 06:57:24 +08:00
|
|
|
#include "ui.h"
|
2024-03-30 21:25:39 +08:00
|
|
|
#include "util/assert.h"
|
2024-04-16 21:08:21 +08:00
|
|
|
#include "util/rand.h"
|
2024-03-30 21:25:39 +08:00
|
|
|
#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);
|
|
|
|
}
|
2024-04-02 15:56:49 +08:00
|
|
|
if (misc->respawn_pos)
|
|
|
|
free(misc->respawn_pos);
|
2024-04-14 15:22:55 +08:00
|
|
|
if (misc->change_level)
|
|
|
|
free(misc->change_level);
|
2024-03-30 21:25:39 +08:00
|
|
|
free(misc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-04-02 15:56:49 +08:00
|
|
|
#define ABSOLUTE_BOX(entity, box) ((entity->position) ? (box2_Offset(box, entity->position->position)) : (box))
|
|
|
|
#define ABSOLUTE_VEC(entity, pnt) ((entity->position) ? (vec2_Add(pnt, entity->position->position)) : (pnt))
|
|
|
|
|
|
|
|
|
2024-03-30 21:25:39 +08:00
|
|
|
void misc_thinker_HazardRespawn(App *app, Entity *e, Duration deltaTime) {
|
2024-04-02 15:56:49 +08:00
|
|
|
if (!e->misc || !e->misc->respawn_pos) {
|
2024-03-30 21:25:39 +08:00
|
|
|
WARN("called on an entity without misc or misc.respawn", 0);
|
2024-04-02 09:29:46 +08:00
|
|
|
e->thinker = NULL;
|
2024-03-30 21:25:39 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (app->player->player == NULL) // No player
|
|
|
|
return;
|
2024-03-30 22:05:49 +08:00
|
|
|
Component_Player *p = app->player->player;
|
|
|
|
Box2 playerbox = physics_HitboxAbsolute(p->super->hitbox);
|
2024-03-30 21:25:39 +08:00
|
|
|
|
2024-04-02 15:56:49 +08:00
|
|
|
Box2 worldbox = ABSOLUTE_BOX(e, e->misc->trigger);
|
|
|
|
Vec2 worldspawn = ABSOLUTE_VEC(e, *e->misc->respawn_pos);
|
2024-03-30 22:05:49 +08:00
|
|
|
if (box2_Intersects(worldbox, playerbox, NULL))
|
2024-03-30 21:25:39 +08:00
|
|
|
p->hazardRespawn = worldspawn;
|
|
|
|
}
|
|
|
|
|
2024-04-02 08:56:35 +08:00
|
|
|
void misc_thinker_Hazard(App *app, Entity *e, Duration deltaTime) {
|
2024-04-02 15:56:49 +08:00
|
|
|
if (!e->misc || !(e->misc->trigger_flags & misc_Hazard)) {
|
|
|
|
WARN("called on an entity without misc or misc.flags|Hazard", 0);
|
2024-04-02 09:29:46 +08:00
|
|
|
e->thinker = NULL;
|
2024-04-02 08:56:35 +08:00
|
|
|
return;
|
|
|
|
}
|
2024-04-16 21:08:21 +08:00
|
|
|
|
|
|
|
// Emit some black particles
|
|
|
|
// BTW, very bad (but reliable) way to store a number here!
|
|
|
|
static const FillMode mode = {
|
|
|
|
.rop2 = 13, // R2_COPYPEN
|
|
|
|
.style = 0, // BS_SOLID
|
|
|
|
.hatch = 0,
|
|
|
|
.rotate = {.microseconds = 0},
|
|
|
|
.dissolve = {.microseconds = 0},
|
|
|
|
.fadein = false,
|
|
|
|
.bg = 0xffffff,
|
|
|
|
.fg = 0};
|
|
|
|
uint64_t emitCooldown = 400.0 * 40000.0 / e->misc->trigger.size.x; // 40 msec across 400px
|
|
|
|
TimePoint lastEmit = {.microseconds = (uint64_t)e->thinkerData};
|
2024-04-22 05:28:50 +08:00
|
|
|
if (gametime_Since(app->time, lastEmit).microseconds > emitCooldown) {
|
|
|
|
lastEmit = gametime_Now(app->time);
|
2024-04-16 21:08:21 +08:00
|
|
|
lastEmit.microseconds -= 10000 * rand_Double01();
|
|
|
|
Box2 worldbox = ABSOLUTE_BOX(e, e->misc->trigger);
|
|
|
|
particle_Emit(
|
|
|
|
app->particle,
|
|
|
|
0,
|
|
|
|
vec2_Random(
|
|
|
|
worldbox.lefttop.x + 20,
|
|
|
|
worldbox.lefttop.x + worldbox.size.x - 20,
|
|
|
|
worldbox.lefttop.y + 20.0,
|
|
|
|
worldbox.lefttop.y + 40.0),
|
|
|
|
vec2(0.0, rand_DoubleRange(-200.0, -280.0)),
|
|
|
|
rand_DoubleRange(1, 1.5),
|
|
|
|
rand_DoubleRange(18, 30),
|
|
|
|
20, duration_FromSeconds(0), &mode);
|
|
|
|
e->thinkerData = (void *)lastEmit.microseconds;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-04-02 08:56:35 +08:00
|
|
|
if (app->player->player == NULL) // No player
|
|
|
|
return;
|
|
|
|
Component_Player *p = app->player->player;
|
|
|
|
Box2 playerbox = physics_HitboxAbsolute(p->super->hitbox);
|
|
|
|
|
2024-04-02 15:56:49 +08:00
|
|
|
Box2 worldbox = ABSOLUTE_BOX(e, e->misc->trigger);
|
2024-04-02 08:56:35 +08:00
|
|
|
if (box2_Intersects(worldbox, playerbox, NULL))
|
|
|
|
player_HazardHarm(app->player);
|
|
|
|
}
|
|
|
|
|
2024-03-30 21:25:39 +08:00
|
|
|
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);
|
2024-04-02 09:29:46 +08:00
|
|
|
e->thinker = NULL;
|
2024-03-30 21:25:39 +08:00
|
|
|
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;
|
|
|
|
}
|
2024-03-30 22:05:49 +08:00
|
|
|
Component_Player *p = app->player->player;
|
|
|
|
Box2 playerbox = physics_HitboxAbsolute(p->super->hitbox);
|
2024-03-30 21:25:39 +08:00
|
|
|
|
2024-04-02 15:56:49 +08:00
|
|
|
Box2 worldbox = ABSOLUTE_BOX(e, e->misc->trigger);
|
2024-03-30 22:05:49 +08:00
|
|
|
if (box2_Intersects(worldbox, playerbox, NULL))
|
2024-03-30 21:25:39 +08:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2024-04-02 09:29:46 +08:00
|
|
|
void misc_thinker_ToLive(App *app, Entity *e, Duration deltaTime) {
|
|
|
|
if (!e->misc || e->misc->tolive.microseconds == 0) {
|
|
|
|
WARN("called on an entity without misc or misc.tolive", 0);
|
|
|
|
e->thinker = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-04-22 05:28:50 +08:00
|
|
|
if (e->misc->tolive.microseconds < gametime_Now(app->time).microseconds) {
|
2024-04-02 09:29:46 +08:00
|
|
|
// After its allocated time
|
|
|
|
entity_Delete(app->entity, e->id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-14 15:22:55 +08:00
|
|
|
void misc_thinker_ChangeLevel(App *app, Entity *e, Duration deltaTime) {
|
|
|
|
if (!e->misc || !e->misc->change_level) {
|
|
|
|
WARN("called on an entity without misc or misc.change_level", 0);
|
|
|
|
e->thinker = NULL;
|
|
|
|
return;
|
|
|
|
}
|
2024-04-18 11:11:51 +08:00
|
|
|
|
|
|
|
// Copied from Hazard thinker
|
|
|
|
uint64_t emitCooldown = 400.0 * 60000.0 / e->misc->trigger.size.x; // 60 msec across 400px
|
|
|
|
TimePoint lastEmit = {.microseconds = (uint64_t)e->thinkerData};
|
2024-04-22 05:28:50 +08:00
|
|
|
if (gametime_Since(app->time, lastEmit).microseconds > emitCooldown) {
|
|
|
|
lastEmit = gametime_Now(app->time);
|
2024-04-18 11:11:51 +08:00
|
|
|
lastEmit.microseconds -= 10000 * rand_Double01();
|
|
|
|
Box2 worldbox = ABSOLUTE_BOX(e, e->misc->trigger);
|
|
|
|
particle_Emit(
|
|
|
|
app->particle,
|
|
|
|
0,
|
|
|
|
vec2_Random(
|
|
|
|
worldbox.lefttop.x + 20,
|
|
|
|
worldbox.lefttop.x + worldbox.size.x - 20,
|
|
|
|
worldbox.lefttop.y + 20.0,
|
|
|
|
worldbox.lefttop.y + 40.0),
|
|
|
|
vec2(0.0, rand_DoubleRange(-250.0, -300.0)),
|
|
|
|
rand_DoubleRange(1, 1.5),
|
|
|
|
rand_DoubleRange(20, 30),
|
|
|
|
20, duration_FromSeconds(0), &render_ModeDefault);
|
|
|
|
e->thinkerData = (void *)lastEmit.microseconds;
|
|
|
|
}
|
2024-04-28 19:59:16 +08:00
|
|
|
|
|
|
|
if (app->player->player == NULL) // No player
|
|
|
|
return;
|
|
|
|
|
2024-04-14 15:22:55 +08:00
|
|
|
Component_Player *p = app->player->player;
|
|
|
|
Box2 playerbox = physics_HitboxAbsolute(p->super->hitbox);
|
|
|
|
|
|
|
|
Box2 worldbox = ABSOLUTE_BOX(e, e->misc->trigger);
|
|
|
|
if (box2_Intersects(worldbox, playerbox, NULL))
|
2024-04-30 06:57:24 +08:00
|
|
|
ui_EnterIntermission(app->ui, e->misc->change_level);
|
2024-04-14 15:22:55 +08:00
|
|
|
}
|
|
|
|
|
2024-03-30 21:25:39 +08:00
|
|
|
|
2024-04-15 14:18:12 +08:00
|
|
|
void misc_thinker_CameraFocus(App *app, Entity *e, Duration deltaTime) {
|
|
|
|
if (!e->misc || !(e->misc->trigger_flags & misc_CameraFocus)) {
|
|
|
|
WARN("called on an entity without misc or misc.flags&misc_CameraFocus", 0);
|
|
|
|
e->thinker = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (app->player->player == NULL) // No player
|
|
|
|
return;
|
|
|
|
Component_Player *p = app->player->player;
|
|
|
|
|
|
|
|
Box2 worldbox = ABSOLUTE_BOX(e, e->misc->trigger);
|
|
|
|
if (box2_Contains(worldbox, p->super->position->position)) {
|
|
|
|
app->camera->target = &e->misc->trigger;
|
|
|
|
} else {
|
|
|
|
if (app->camera->target == &e->misc->trigger)
|
|
|
|
// Player just left this box
|
|
|
|
app->camera->target = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-03-30 21:25:39 +08:00
|
|
|
// Utility functions for creating misc entities
|
|
|
|
void misc_InstantiateTextbox(App *app, Entity *e, const char *text, Box2 trigger_box, float offset) {
|
2024-03-30 22:05:49 +08:00
|
|
|
ASSERT(e->misc == NULL && "Instantiate must be called with e.misc not set");
|
|
|
|
ASSERT((e->render == NULL || e->render->custom == NULL) && "Instantiate for Textbox must be called with e.render.custom not set");
|
2024-03-30 21:25:39 +08:00
|
|
|
e->misc = zero_malloc(sizeof(Component_Misc));
|
2024-04-02 15:56:49 +08:00
|
|
|
e->misc->trigger = trigger_box;
|
2024-03-30 21:25:39 +08:00
|
|
|
e->misc->textbox = zero_malloc(sizeof(misc_Textbox));
|
|
|
|
|
2024-04-02 15:56:49 +08:00
|
|
|
e->misc->textbox->text = copy_malloc(text);
|
|
|
|
e->misc->textbox->offset = offset;
|
2024-03-30 21:25:39 +08:00
|
|
|
|
2024-03-30 22:05:49 +08:00
|
|
|
if (!e->render)
|
2024-04-16 10:30:37 +08:00
|
|
|
e->render = render_NewComponentFunc(e, &misc_render_Textbox, NULL);
|
2024-03-30 22:05:49 +08:00
|
|
|
else
|
|
|
|
e->render->custom = &misc_render_Textbox;
|
2024-03-30 21:25:39 +08:00
|
|
|
e->thinker = &misc_thinker_Textbox;
|
|
|
|
}
|
2024-03-30 22:05:49 +08:00
|
|
|
|
|
|
|
void misc_InstantiateHazardRespawn(App *app, Entity *e, Box2 trigger_box, Vec2 respawn_pos) {
|
|
|
|
ASSERT(e->misc == NULL && "Instantiate must be called with e.misc not set");
|
2024-04-02 15:56:49 +08:00
|
|
|
e->misc = zero_malloc(sizeof(Component_Misc));
|
|
|
|
e->misc->trigger = trigger_box;
|
|
|
|
e->misc->respawn_pos = copy_malloc_size(&respawn_pos, sizeof(Vec2));
|
2024-03-30 22:05:49 +08:00
|
|
|
|
|
|
|
e->thinker = &misc_thinker_HazardRespawn;
|
|
|
|
}
|
2024-04-02 08:56:35 +08:00
|
|
|
|
|
|
|
void misc_InstantiateHazard(App *app, Entity *e, Box2 trigger_box) {
|
|
|
|
ASSERT(e->misc == NULL && "Instantiate must be called with e.misc not set");
|
2024-04-02 15:56:49 +08:00
|
|
|
e->misc = zero_malloc(sizeof(Component_Misc));
|
|
|
|
e->misc->trigger = trigger_box;
|
|
|
|
e->misc->trigger_flags = misc_Hazard;
|
2024-04-02 08:56:35 +08:00
|
|
|
|
|
|
|
e->thinker = &misc_thinker_Hazard;
|
|
|
|
}
|
2024-04-02 09:29:46 +08:00
|
|
|
|
|
|
|
void misc_InstantiateToLive(App *app, Entity *e, Duration duration, TimePoint since) {
|
|
|
|
ASSERT(e->misc == NULL && "Instantiate must be called with e.misc not set");
|
|
|
|
e->misc = zero_malloc(sizeof(Component_Misc));
|
|
|
|
e->misc->tolive = time_After(since, duration);
|
|
|
|
|
|
|
|
e->thinker = &misc_thinker_ToLive;
|
|
|
|
}
|
2024-04-14 15:22:55 +08:00
|
|
|
|
|
|
|
void misc_InstantiateChangeLevel(App *app, Entity *e, Box2 trigger_box, const char *next_level) {
|
|
|
|
ASSERT(e->misc == NULL && "Instantiate must be called with e.misc not set");
|
|
|
|
e->misc = zero_malloc(sizeof(Component_Misc));
|
|
|
|
e->misc->trigger = trigger_box;
|
|
|
|
e->misc->change_level = copy_malloc(next_level);
|
|
|
|
e->thinker = &misc_thinker_ChangeLevel;
|
|
|
|
}
|
2024-04-15 14:18:12 +08:00
|
|
|
|
|
|
|
void misc_InstantiateCameraFocus(App *app, Entity *e, Box2 trigger_box) {
|
|
|
|
ASSERT(e->misc == NULL && "Instantiate must be called with e.misc not set");
|
|
|
|
e->misc = zero_malloc(sizeof(Component_Misc));
|
|
|
|
e->misc->trigger = trigger_box;
|
|
|
|
e->misc->trigger_flags = misc_CameraFocus;
|
|
|
|
e->thinker = &misc_thinker_CameraFocus;
|
|
|
|
}
|