From f721b86bb979f82e2867fa45da638a233efa4158 Mon Sep 17 00:00:00 2001 From: Noel Berry Date: Sun, 3 Jan 2021 21:42:45 -0800 Subject: [PATCH] boss!!! --- CMakeLists.txt | 2 +- content/map/12x0.png | Bin 243 -> 243 bytes content/sprites/ghostfrog.ase | Bin 0 -> 2729 bytes src/components/animator.cpp | 2 +- src/components/animator.h | 1 + src/components/collider.cpp | 28 +++- src/components/collider.h | 2 + src/components/ghost_frog.cpp | 268 ++++++++++++++++++++++++++++++++++ src/components/ghost_frog.h | 48 ++++++ src/components/orb.cpp | 42 ++++++ src/components/orb.h | 20 +++ src/components/player.cpp | 38 +++-- src/components/player.h | 2 +- src/factory.cpp | 55 +++++++ src/factory.h | 2 + src/game.cpp | 8 +- src/masks.h | 1 + src/world.h | 1 + 18 files changed, 499 insertions(+), 21 deletions(-) create mode 100644 content/sprites/ghostfrog.ase create mode 100644 src/components/ghost_frog.cpp create mode 100644 src/components/ghost_frog.h create mode 100644 src/components/orb.cpp create mode 100644 src/components/orb.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a1b1b0..d94350f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,7 @@ add_executable(game src/components/timer.h src/components/timer.cpp src/components/enemy.h -) + "src/components/ghost_frog.h" "src/components/ghost_frog.cpp" "src/components/orb.h" "src/components/orb.cpp") # Reference blah target_link_libraries(game blah) diff --git a/content/map/12x0.png b/content/map/12x0.png index 491b93b292833a72f5ebe088d5983cb21e6f84dd..d51bc1e90b20ca798ad1c589e4242fbf8867fc71 100644 GIT binary patch delta 34 pcmey&_?dCSdgfDec1_%*E5dy@?1+5O0b>Rr@O1TaS?83{1ONiT4JQBq delta 34 pcmey&_?dCSdgeE8y(ezc6-oRXR>E@o`w<2p@O1TaS?83{1OON~4u=2$ diff --git a/content/sprites/ghostfrog.ase b/content/sprites/ghostfrog.ase new file mode 100644 index 0000000000000000000000000000000000000000..d014a7888bf5c97df77939b8066a0fb0cee9d5f4 GIT binary patch literal 2729 zcmb`IeN>WH7{ITJ35dEh2|qHbmChVO%gS=0tq)V%%(*gGPUNE`(?l{kY-(zLwiLBA zG*9K|Qy1CFO;p75e01B8$Zj)?|yW7kAPoS1u;17{qSTZ93>s_zEYwT%EVXb2URR{~Sw(}3YZA#lUG0ATF0 zOkml(dBE27Twt@b3YcBs1I&zKVnSF20na-f2CxWN0qBD}h=VmKgD=Q}DQJQt2!b7` zffq=D5$ND97zP%g0P!I>M24^s69Pgs2sIs#M2B=wG*Cb$$d4o8i|DMIa6zNLVUvwf z%+4qg&j7vp2>22Kh4Ui#1Y|t%+_8ZX677g2z+JsYK*Al{wogJNy;(E~al~Q{cPkk| zIlkJAksu<9BQ`~pS7&i;5nm9&=Zejcnf@ID;_*4Wg)ljDtc(n`w@uLJ5#d}uo%QO+ zJ>dfHbm&gd&G9jP{3{r?7p{!ycul^CFx_*I6{4ZI;@(?c)i<&UJ9! zR=a?-#`WIikRy8)lO;wcNF`GwTRwOa|EP@`*m~pa_Tpy$1#LX9YeRx{_e1u(xVfIG zaA;GUw>)RLw5b34hZ3o>WR1sP6=bSET6o8!i{Co> z2Yq2GKf5bo#p97*BvWE&$^)y8V*Z!EUH(rBN^SGL!49fl*0AbIp$VF=z9dO~%^`Ne z!A36w7Vj_vluB9Q)HCwkC5{;^3uL(Mm&l~oEJSZ-!BSmLk_#qh^vjcHUG%=XsD7q( zC)qKeep&2P>yg#smb|>a=;O29%Po}!$A2(Ji+0r2>MB9E7w@JLR*S4M1f@_3Jvn$K z{FJ5J4{uUJfWghLm+z6F3y&|d^0ey@f)jyPLb7)K6ummMgR>M@+|;NxLor6n&l%|Zd$L1tIp#eW{FB2hr7jIwEDd`gOCZ(&5Yt0jx zL?i#c1h?$YFZ`Y~c%Czj*!2EG3%2Y2@bez#>Vv|OMW?6SC0#hFcoIT2jcbv*uc=!y z(DeB&p?lVyz=ZD6(%KH=>VvAo>+Tly6y@r#4H|^s;0>mL+beiac7%5wnCz*s&ZU=awZ^{hnT+Rn zPBY7(v5_HSema~^@(nPMAEfmiKKjjrSkCpjxZY!QmuPvi!sYPFVD&>maDD*_T>o6H zJ2E^7k8hUl$mna>>NF5`i&;f;Q(=vj*r8A{%ei$$*~j@UyE!IH{dR4tsdvcL9J%da z$()@yMd}o7wYF=8v*%gUNVZ$ASEbR17NghruEw0yMtl37T+{7U`{aCEw7FB`l%-9E z&O)k9hAo<^cW@`a>w_CEq~?bB+z3-0$=(u!LKL?)7iv{>rIM~yq3EgDAzY>XH!re^ zNhGLVV$DM-@ruyX(WNVj%<{?7{+t=k%8e_#N=|APxlZjHE0sBOO(h=BMe_YSoaN)C za_YtG;e<>LqoSQueA{;T*OBXzmL=@_-_!=IkDHZnCbe>BFtOucvE2aS;Xv+dUihyJ z{@*iA&&wYEiNNc&mpOWmLh!^ex#M7FN|7=+e1o}Mqe$CJH1jGmR6CsY@skjr)gEo) z7UrEJt4G>n4!AVP3hsOw*dQwCFpODj(fEn)V4W}4$++tN3xo5sbyJmcZc%GJ|FQR} z1cmdx?~Rq&S|i=2C1{Oo^o*>~|Nh$MG?fposition, m_sprite->origin, scale, 0)); + Mat3x2::create_transform(entity()->position + offset, m_sprite->origin, scale, 0)); auto& anim = m_sprite->animations[m_animation_index]; auto& frame = anim.frames[m_frame_index]; diff --git a/src/components/animator.h b/src/components/animator.h index 3e9aab8..5875637 100644 --- a/src/components/animator.h +++ b/src/components/animator.h @@ -17,6 +17,7 @@ namespace TL public: Vec2 scale = Vec2::one; + Point offset = Point::zero; Animator() = default; Animator(const String& sprite); diff --git a/src/components/collider.cpp b/src/components/collider.cpp index 2d708b6..3fa490d 100644 --- a/src/components/collider.cpp +++ b/src/components/collider.cpp @@ -71,6 +71,11 @@ void Collider::set_cells(int x, int y, int w, int h, bool value) } bool Collider::check(uint32_t mask, Point offset) const +{ + return first(mask, offset) != nullptr; +} + +Collider* Collider::first(uint32_t mask, Point offset) { if (world()) { @@ -80,13 +85,32 @@ bool Collider::check(uint32_t mask, Point offset) const if (other != this && (other->mask & mask) == mask && overlaps(other, offset)) - return true; + return other; other = (Collider*)other->next(); } } - return false; + return nullptr; +} + +const Collider* Collider::first(uint32_t mask, Point offset) const +{ + if (world()) + { + auto other = world()->first(); + while (other) + { + if (other != this && + (other->mask & mask) == mask && + overlaps(other, offset)) + return other; + + other = (Collider*)other->next(); + } + } + + return nullptr; } bool Collider::overlaps(const Collider* other, Point offset) const diff --git a/src/components/collider.h b/src/components/collider.h index 0b46348..17abcf5 100644 --- a/src/components/collider.h +++ b/src/components/collider.h @@ -31,6 +31,8 @@ namespace TL void set_cell(int x, int y, bool value); void set_cells(int x, int y, int w, int h, bool value); + Collider* first(uint32_t mask, Point offset = Point::zero); + const Collider* first(uint32_t mask, Point offset = Point::zero) const; bool check(uint32_t mask, Point offset = Point::zero) const; bool overlaps(const Collider* other, Point offset = Point::zero) const; diff --git a/src/components/ghost_frog.cpp b/src/components/ghost_frog.cpp new file mode 100644 index 0000000..ea8f135 --- /dev/null +++ b/src/components/ghost_frog.cpp @@ -0,0 +1,268 @@ +#include "ghost_frog.h" +#include "mover.h" +#include "hurtable.h" +#include "player.h" +#include "animator.h" +#include "orb.h" +#include "../masks.h" +#include "../factory.h" + +using namespace TL; + +GhostFrog::GhostFrog() +{ + +} + +void GhostFrog::awake() +{ + m_home = entity()->position; +} + +void GhostFrog::update() +{ + m_timer += Time::delta; + + auto player = world()->first(); + auto mover = get(); + auto anim = get(); + auto hitbox = get(); + + // no player - turn off AI + if (!player) + return; + + auto x = entity()->position.x; + auto y = entity()->position.y; + auto player_x = player->entity()->position.x; + + // update sprite + if (mover->on_ground()) + { + + } + + // flip sprite + anim->scale = Vec2(m_facing, 1); + + // NORMAL STATE + if (m_state == st_readying_attack) + { + m_facing = Calc::sign(player_x - x); + if (m_facing == 0) + m_facing = 1; + + float target_x = player_x + 32 * -m_facing; + mover->speed.x = Calc::approach(mover->speed.x, Calc::sign(target_x - x) * 40, 400 * Time::delta); + mover->friction = 100; + anim->play("run"); + + if (m_timer > 3.0f || + (m_timer > 1.0f && hitbox->check(Mask::solid, Point(-m_facing * 8, 0)))) + { + mover->speed.x = 0; + set_state(st_perform_slash); + } + } + // SLASH STATE + else if (m_state == st_perform_slash) + { + // start attack anim + anim->play("attack"); + mover->friction = 500; + + // after 0.8s, do the lunge + if (Time::on_time(m_timer, 0.8f)) + { + mover->speed.x = m_facing * 250; + hitbox->set_rect(RectI(-4 + m_facing * 4, -12, 8, 12)); + + RectI rect(8, -8, 20, 8); + if (m_facing < 0) + rect.x = -(rect.x + rect.w); + + if (m_attack_collider) + m_attack_collider->destroy(); + m_attack_collider = entity()->add(Collider::make_rect(rect)); + m_attack_collider->mask = Mask::enemy; + } + // turn off attack collider + else if (Time::on_time(m_timer, anim->animation()->duration() - 1.0f)) + { + if (m_attack_collider) + m_attack_collider->destroy(); + m_attack_collider = nullptr; + } + // end attack state + else if (m_timer >= anim->animation()->duration()) + { + hitbox->set_rect(RectI(-4, -12, 8, 12)); + + if (health > 0) + { + set_state(st_readying_attack); + } + else + { + phase = 1; + health = max_health_2; + m_side = Calc::rand_int(0, 2) == 0 ? -1 : 1; + set_state(st_floating); + } + } + } + // FLOATING STATE + else if (m_state == st_floating) + { + anim->play("float"); + + mover->friction = 0; + mover->collider = nullptr; + + float target_y = m_home.y - 50; + float target_x = m_home.x + m_side * 50; + + if (Calc::sign(target_y - y) != Calc::sign(target_y - m_last_pos.y)) + { + mover->speed.y = 0; + entity()->position.y = target_y; + } + else + mover->speed.y = Calc::approach(mover->speed.y, Calc::sign(target_y - y) * 50, 800 * Time::delta); + + if (Calc::abs(y - target_y) < 8) + mover->speed.x = Calc::approach(mover->speed.x, Calc::sign(target_x - x) * 80, 800 * Time::delta); + else + mover->speed.x = 0; + + if (m_timer > 5.0f || (Calc::abs(target_x - x) < 8 && Calc::abs(target_y - y) < 8)) + set_state(st_shoot); + } + // SHOOTING STATE + else if (m_state == st_shoot) + { + mover->speed = Calc::approach(mover->speed, Vec2::zero, 300 * Time::delta); + + m_facing = Calc::sign(player_x - x); + if (m_facing == 0) + m_facing = 1; + + if (Time::on_time(m_timer, 1.0f)) + { + anim->play("reflect"); + } + else if (Time::on_time(m_timer, 1.2f)) + { + Factory::orb(world(), entity()->position + Point(m_facing * 12, -8)); + m_reflect_count = 0; + } + else if (Time::on_time(m_timer, 1.4f)) + { + anim->play("float"); + set_state(st_reflect); + } + } + // REFLECT STATE + else if (m_state == st_reflect) + { + if (Time::on_time(m_timer, 0.4f)) + anim->play("float"); + + auto orb = world()->first(); + if (!orb) + { + if (m_timer > 1.0f) + { + m_side = -m_side; + set_state(st_floating); + } + } + else if (!orb->towards_player) + { + if (m_reflect_count < 2) + { + if (Vec2(orb->entity()->position - orb->target()).length() < 16) + { + anim->play("reflect"); + orb->on_hit(); + m_reflect_count++; + m_timer = 0; + } + } + else + { + if (Vec2(orb->entity()->position - orb->target()).length() < 8) + { + Factory::pop(world(), entity()->position + Point(0, -8)); + orb->entity()->destroy(); + on_hurt(nullptr); + m_timer = 0; + } + } + } + } + // DEAD STATE + else if (m_state == st_dead_state) + { + anim->play("dead"); + + if (Time::on_interval(0.25f)) + { + auto offset = Point(Calc::rand_int(-16, 16), Calc::rand_int(-16, 16)); + Factory::pop(world(), entity()->position + Point(0, -8) + offset); + } + + if (Time::on_time(m_timer, 3.0f)) + { + for (int x = -1; x < 2; x ++) + for (int y = -1; y < 2; y ++) + Factory::pop(world(), entity()->position + Point(x * 12, -8 + y * 12)); + + Time::pause_for(0.3f); + entity()->destroy(); + } + } + + if (m_state == st_floating || m_state == st_shoot || m_state == st_reflect) + { + anim->offset.y = Calc::sin(Time::elapsed * 2) * 3; + } + + m_last_pos = entity()->position; +} + +void GhostFrog::on_hurt(Hurtable* hurtable) +{ + if (health > 0) + { + health--; + + if (health <= 0 && phase > 0) + { + set_state(st_dead_state); + } + + if (m_state == st_waiting) + { + Factory::pop(world(), entity()->position + Point(0, -8)); + Time::pause_for(0.25f); + set_state(st_readying_attack); + } + } +} + +void GhostFrog::on_hit_x(Mover* mover) +{ + +} + +void GhostFrog::on_hit_y(Mover* mover) +{ + +} + +void GhostFrog::set_state(int state) +{ + m_state = state; + m_timer = 0; +} diff --git a/src/components/ghost_frog.h b/src/components/ghost_frog.h new file mode 100644 index 0000000..ead3b84 --- /dev/null +++ b/src/components/ghost_frog.h @@ -0,0 +1,48 @@ +#pragma once +#include "../world.h" +#include + +using namespace Blah; + +namespace TL +{ + class Hurtable; + class Mover; + class Collider; + + class GhostFrog : public Component + { + public: + static constexpr int st_waiting = 0; + static constexpr int st_readying_attack = 1; + static constexpr int st_perform_slash = 2; + static constexpr int st_floating = 3; + static constexpr int st_shoot = 4; + static constexpr int st_reflect = 5; + static constexpr int st_dead_state = 6; + + static constexpr int max_health_1 = 10; + static constexpr int max_health_2 = 3; + int health = max_health_1; + int phase = 0; + + GhostFrog(); + + void awake() override; + void update() override; + void on_hurt(Hurtable* hurtable); + void on_hit_x(Mover* mover); + void on_hit_y(Mover* mover); + + private: + void set_state(int state); + float m_timer = 0; + int m_state = st_waiting; + int m_facing = 1; + int m_side = 1; + int m_reflect_count = 0; + Point m_home; + Point m_last_pos; + Collider* m_attack_collider = nullptr; + }; +} \ No newline at end of file diff --git a/src/components/orb.cpp b/src/components/orb.cpp new file mode 100644 index 0000000..0faf6dc --- /dev/null +++ b/src/components/orb.cpp @@ -0,0 +1,42 @@ +#include "orb.h" +#include "mover.h" +#include "hurtable.h" +#include "player.h" +#include "ghost_frog.h" +#include "../masks.h" +#include "../factory.h" + +using namespace TL; + +Point Orb::target() const +{ + auto player = world()->first(); + auto ghost = world()->first(); + + if (player && ghost) + return (towards_player ? player->entity()->position : ghost->entity()->position) + Point(0, -8); + + return Point(0, 0); +} + +void Orb::update() +{ + auto mover = get(); + auto diff = Vec2(target() - entity()->position).normal(); + mover->speed = diff * speed; +} + +void Orb::destroyed() +{ + Factory::pop(world(), entity()->position); +} + +void Orb::on_hit() +{ + towards_player = !towards_player; + speed += 40; + + auto hurt = get(); + if (towards_player) + hurt->stun_timer = 0; +} \ No newline at end of file diff --git a/src/components/orb.h b/src/components/orb.h new file mode 100644 index 0000000..943de8f --- /dev/null +++ b/src/components/orb.h @@ -0,0 +1,20 @@ +#pragma once +#include "../world.h" +#include + +using namespace Blah; + +namespace TL +{ + class Orb : public Component + { + public: + float speed = 40; + bool towards_player = true; + + Point target() const; + void update() override; + void destroyed() override; + void on_hit(); + }; +} \ No newline at end of file diff --git a/src/components/player.cpp b/src/components/player.cpp index 65ba487..c2b6872 100644 --- a/src/components/player.cpp +++ b/src/components/player.cpp @@ -2,6 +2,7 @@ #include "mover.h" #include "animator.h" #include "collider.h" +#include "orb.h" #include "../masks.h" using namespace TL; @@ -220,22 +221,31 @@ void Player::update() } // Hurt Check! - if (m_invincible_timer <= 0 && hitbox->check(Mask::enemy)) + if (m_invincible_timer <= 0) { - Time::pause_for(0.1f); - anim->play("hurt"); - - if (m_attack_collider) + auto hit = hitbox->first(Mask::enemy); + if (hit) { - m_attack_collider->destroy(); - m_attack_collider = nullptr; + Time::pause_for(0.1f); + anim->play("hurt"); + + if (m_attack_collider) + { + m_attack_collider->destroy(); + m_attack_collider = nullptr; + } + + mover->speed = Vec2(-m_facing * 100, -80); + + health--; + m_hurt_timer = hurt_duration; + m_invincible_timer = invincible_duration; + m_state = st_hurt; + + // hack: + // destroy orb + if (hit->get()) + hit->entity()->destroy(); } - - mover->speed = Vec2(-m_facing * 100, -80); - - health--; - m_hurt_timer = hurt_duration; - m_invincible_timer = invincible_duration; - m_state = st_hurt; } } diff --git a/src/components/player.h b/src/components/player.h index 03945af..5d4aea9 100644 --- a/src/components/player.h +++ b/src/components/player.h @@ -14,7 +14,7 @@ namespace TL static constexpr int st_attack = 1; static constexpr int st_hurt = 2; static constexpr int st_start = 3; - static constexpr int max_health = 3; + static constexpr int max_health = 30; int health = max_health; diff --git a/src/factory.cpp b/src/factory.cpp index 286de16..e8b4622 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -7,6 +7,8 @@ #include "components/hurtable.h" #include "components/timer.h" #include "components/enemy.h" +#include "components/ghost_frog.h" +#include "components/orb.h" using namespace TL; @@ -19,6 +21,7 @@ Entity* Factory::player(World* world, Point position) anim->depth = -10; auto hitbox = en->add(Collider::make_rect(RectI(-4, -12, 8, 12))); + hitbox->mask = Mask::player; auto mover = en->add(Mover()); mover->collider = hitbox; @@ -334,3 +337,55 @@ Entity* Factory::blob(World* world, Point position) return en; } +Entity* Factory::ghost_frog(World* world, Point position) +{ + auto en = world->add_entity(position); + en->add(GhostFrog()); + en->add(Enemy()); + + auto anim = en->add(Animator("ghostfrog")); + anim->play("sword"); + anim->depth = -5; + + auto hitbox = en->add(Collider::make_rect(RectI(-4, -12, 8, 12))); + hitbox->mask = Mask::enemy; + + auto mover = en->add(Mover()); + mover->collider = hitbox; + mover->gravity = 0; + mover->friction = 100; + mover->on_hit_x = [](Mover* self) { self->get()->on_hit_x(self); }; + mover->on_hit_y = [](Mover* self) { self->get()->on_hit_y(self); }; + + auto hurtable = en->add(Hurtable()); + hurtable->hurt_by = Mask::player_attack; + hurtable->collider = hitbox; + hurtable->on_hurt = [](Hurtable* self) { self->get()->on_hurt(self); }; + + return en; +} + +Entity* Factory::orb(World* world, Point position) +{ + auto en = world->add_entity(position); + en->add(Orb()); + + auto anim = en->add(Animator("bullet")); + anim->play("idle"); + anim->depth = -5; + + auto hitbox = en->add(Collider::make_rect(RectI(-4, -4, 8, 8))); + hitbox->mask = Mask::enemy; + + auto mover = en->add(Mover()); + mover->collider = hitbox; + mover->on_hit_x = [](Mover* self) { Factory::pop(self->world(), self->entity()->position); self->entity()->destroy(); }; + mover->on_hit_y = [](Mover* self) { Factory::pop(self->world(), self->entity()->position); self->entity()->destroy(); }; + + auto hurtable = en->add(Hurtable()); + hurtable->hurt_by = Mask::player_attack; + hurtable->collider = en->add(Collider::make_rect(RectI(-8, -8, 16, 16))); + hurtable->on_hurt = [](Hurtable* self) { self->get()->on_hit(); }; + + return en; +} diff --git a/src/factory.h b/src/factory.h index fcb49d7..2bf4807 100644 --- a/src/factory.h +++ b/src/factory.h @@ -16,5 +16,7 @@ namespace TL Entity* mosquito(World* world, Point position); Entity* door(World* world, Point position, bool wait_for_player = false); Entity* blob(World* world, Point position); + Entity* ghost_frog(World* world, Point position); + Entity* orb(World* world, Point position); } } \ No newline at end of file diff --git a/src/game.cpp b/src/game.cpp index 64a6d9d..e7b8891 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -29,7 +29,7 @@ void Game::startup() m_draw_colliders = false; // load first room - load_room(Point(11, 0)); + load_room(Point(12, 0)); camera = Vec2(room.x * width, room.y * height); } @@ -132,13 +132,17 @@ void Game::load_room(Point cell, bool is_reload) // closing door case 0x847e87: - Factory::door(&world, world_position, true); + Factory::door(&world, world_position, !is_reload); break; // blob case 0x3f3f74: Factory::blob(&world, world_position); break; + + case 0x76428a: + Factory::ghost_frog(&world, world_position + Point(-4, 0)); + break; } } } diff --git a/src/masks.h b/src/masks.h index 16e6b24..30d6220 100644 --- a/src/masks.h +++ b/src/masks.h @@ -9,5 +9,6 @@ namespace TL static constexpr uint32_t jumpthru = 1 << 1; static constexpr uint32_t player_attack = 1 << 2; static constexpr uint32_t enemy = 1 << 3; + static constexpr uint32_t player = 1 << 4; }; } \ No newline at end of file diff --git a/src/world.h b/src/world.h index f80fa6e..d7a124d 100644 --- a/src/world.h +++ b/src/world.h @@ -245,6 +245,7 @@ namespace TL entity->m_components.push_back(instance); // and we're done! + instance->awake(); return instance; }