1// Zeekling - flyer that swoops down when she spots the player
2// Copyright (C) 2005 Matthias Braun <matze@braunis.de>
3// Copyright (C) 2006 Christoph Sommer <christoph.sommer@2006.expires.deltadevelopment.de>
4//
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18#include "badguy/zeekling.hpp"
19
20#include <math.h>
21
22#include "math/random.hpp"
23#include "object/player.hpp"
24#include "sprite/sprite.hpp"
25
26Zeekling::Zeekling(const ReaderMapping& reader) :
27 BadGuy(reader, "images/creatures/zeekling/zeekling.sprite"),
28 speed(gameRandom.randf(130.0f, 171.0f)),
29 diveRecoverTimer(),
30 state(FLYING),
31 last_player(nullptr),
32 last_player_pos(),
33 last_self_pos()
34{
35 m_physic.enable_gravity(false);
36}
37
38void
39Zeekling::initialize()
40{
41 m_physic.set_velocity_x(m_dir == Direction::LEFT ? -speed : speed);
42 m_sprite->set_action(m_dir == Direction::LEFT ? "left" : "right");
43}
44
45bool
46Zeekling::collision_squished(GameObject& object)
47{
48 m_sprite->set_action(m_dir == Direction::LEFT ? "squished-left" : "squished-right");
49 kill_squished(object);
50 return true;
51}
52
53void
54Zeekling::onBumpHorizontal()
55{
56 if (m_frozen)
57 {
58 m_physic.set_velocity_x(0);
59 return;
60 }
61 if (state == FLYING) {
62 m_dir = (m_dir == Direction::LEFT ? Direction::RIGHT : Direction::LEFT);
63 m_sprite->set_action(m_dir == Direction::LEFT ? "left" : "right");
64 m_physic.set_velocity_x(m_dir == Direction::LEFT ? -speed : speed);
65 } else
66 if (state == DIVING) {
67 m_dir = (m_dir == Direction::LEFT ? Direction::RIGHT : Direction::LEFT);
68 state = FLYING;
69 m_sprite->set_action(m_dir == Direction::LEFT ? "left" : "right");
70 m_physic.set_velocity_x(m_dir == Direction::LEFT ? -speed : speed);
71 m_physic.set_velocity_y(0);
72 } else
73 if (state == CLIMBING) {
74 m_dir = (m_dir == Direction::LEFT ? Direction::RIGHT : Direction::LEFT);
75 m_sprite->set_action(m_dir == Direction::LEFT ? "left" : "right");
76 m_physic.set_velocity_x(m_dir == Direction::LEFT ? -speed : speed);
77 } else {
78 assert(false);
79 }
80}
81
82void
83Zeekling::onBumpVertical()
84{
85 if (m_frozen || BadGuy::get_state() == STATE_BURNING)
86 {
87 m_physic.set_velocity_y(0);
88 m_physic.set_velocity_x(0);
89 return;
90 }
91 if (state == FLYING) {
92 m_physic.set_velocity_y(0);
93 } else
94 if (state == DIVING) {
95 state = CLIMBING;
96 m_physic.set_velocity_y(-speed);
97 m_sprite->set_action(m_dir == Direction::LEFT ? "left" : "right");
98 } else
99 if (state == CLIMBING) {
100 state = FLYING;
101 m_physic.set_velocity_y(0);
102 }
103}
104
105void
106Zeekling::collision_solid(const CollisionHit& hit)
107{
108 if (m_sprite->get_action() == "squished-left" ||
109 m_sprite->get_action() == "squished-right")
110 {
111 return;
112 }
113
114 if (hit.top || hit.bottom) {
115 onBumpVertical();
116 } else if (hit.left || hit.right) {
117 onBumpHorizontal();
118 }
119}
120
121/** linear prediction of player and badguy positions to decide if we should enter the DIVING state */
122bool
123Zeekling::should_we_dive()
124{
125 if (m_frozen)
126 return false;
127
128 const auto player = get_nearest_player();
129 if (player && last_player && (player == last_player)) {
130
131 // get positions, calculate movement
132 const Vector& player_pos = player->get_pos();
133 const Vector player_mov = (player_pos - last_player_pos);
134 const Vector self_pos = m_col.m_bbox.p1();
135 const Vector self_mov = (self_pos - last_self_pos);
136
137 // new vertical speed to test with
138 float vy = 2*fabsf(self_mov.x);
139
140 // do not dive if we are not above the player
141 float height = player_pos.y - self_pos.y;
142 if (height <= 0) return false;
143
144 // do not dive if we are too far above the player
145 if (height > 512) return false;
146
147 // do not dive if we would not descend faster than the player
148 float relSpeed = vy - player_mov.y;
149 if (relSpeed <= 0) return false;
150
151 // guess number of frames to descend to same height as player
152 float estFrames = height / relSpeed;
153
154 // guess where the player would be at this time
155 float estPx = (player_pos.x + (estFrames * player_mov.x));
156
157 // guess where we would be at this time
158 float estBx = (self_pos.x + (estFrames * self_mov.x));
159
160 // near misses are OK, too
161 if (fabsf(estPx - estBx) < 8) return true;
162 }
163
164 // update last player tracked, as well as our positions
165 last_player = player;
166 if (player) {
167 last_player_pos = player->get_pos();
168 last_self_pos = m_col.m_bbox.p1();
169 }
170
171 return false;
172}
173
174void
175Zeekling::active_update(float dt_sec) {
176 if (state == FLYING) {
177 if (should_we_dive()) {
178 state = DIVING;
179 m_physic.set_velocity_y(2*fabsf(m_physic.get_velocity_x()));
180 m_sprite->set_action(m_dir == Direction::LEFT ? "diving-left" : "diving-right");
181 }
182 BadGuy::active_update(dt_sec);
183 return;
184 } else if (state == DIVING) {
185 BadGuy::active_update(dt_sec);
186 return;
187 } else if (state == CLIMBING) {
188 // stop climbing when we're back at initial height
189 if (get_pos().y <= m_start_position.y) {
190 state = FLYING;
191 m_physic.set_velocity_y(0);
192 }
193 BadGuy::active_update(dt_sec);
194 return;
195 } else {
196 assert(false);
197 }
198}
199
200void
201Zeekling::freeze()
202{
203 BadGuy::freeze();
204 m_physic.enable_gravity(true);
205}
206
207void
208Zeekling::unfreeze()
209{
210 BadGuy::unfreeze();
211 m_physic.enable_gravity(false);
212 state = FLYING;
213 initialize();
214}
215
216bool
217Zeekling::is_freezable() const
218{
219 return true;
220}
221
222/* EOF */
223