1// SuperTux - Boss "GhostTree"
2// Copyright (C) 2007 Matthias Braun <matze@braunis.de>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17#include "badguy/ghosttree.hpp"
18
19#include <algorithm>
20#include <math.h>
21
22#include "audio/sound_manager.hpp"
23#include "badguy/root.hpp"
24#include "badguy/treewillowisp.hpp"
25#include "math/random.hpp"
26#include "object/lantern.hpp"
27#include "object/player.hpp"
28#include "sprite/sprite.hpp"
29#include "sprite/sprite_manager.hpp"
30#include "supertux/sector.hpp"
31
32static const size_t WILLOWISP_COUNT = 10;
33static const float ROOT_TOP_OFFSET = 64;
34static const float WILLOWISP_TOP_OFFSET = -64;
35static const Vector SUCK_TARGET_OFFSET = Vector(-16,-16);
36static const float SUCK_TARGET_SPREAD = 8;
37
38GhostTree::GhostTree(const ReaderMapping& mapping) :
39 BadGuy(mapping, "images/creatures/ghosttree/ghosttree.sprite", LAYER_OBJECTS - 10),
40 mystate(STATE_IDLE),
41 willowisp_timer(),
42 willo_spawn_y(0),
43 willo_radius(200),
44 willo_speed(1.8f),
45 willo_color(0),
46 glow_sprite(SpriteManager::current()->create("images/creatures/ghosttree/ghosttree-glow.sprite")),
47 colorchange_timer(),
48 suck_timer(),
49 root_timer(),
50 treecolor(0),
51 suck_lantern_color(),
52 suck_lantern(nullptr),
53 willowisps()
54{
55 set_colgroup_active(COLGROUP_TOUCHABLE);
56 SoundManager::current()->preload("sounds/tree_howling.ogg");
57 SoundManager::current()->preload("sounds/tree_suck.ogg");
58}
59
60void
61GhostTree::die()
62{
63 mystate = STATE_DYING;
64 m_sprite->set_action("dying", 1);
65 glow_sprite->set_action("dying", 1);
66
67 for (const auto& willo : willowisps) {
68 willo->vanish();
69 }
70 run_dead_script();
71}
72
73void
74GhostTree::activate()
75{
76 willowisp_timer.start(1.0f, true);
77 colorchange_timer.start(13, true);
78 root_timer.start(5, true);
79}
80
81void
82GhostTree::active_update(float /*dt_sec*/)
83{
84 if (mystate == STATE_IDLE) {
85 if (colorchange_timer.check()) {
86 SoundManager::current()->play("sounds/tree_howling.ogg", get_pos());
87 suck_timer.start(3);
88 treecolor = (treecolor + 1) % 3;
89
90 Color col;
91 switch (treecolor) {
92 case 0: col = Color(1, 0, 0); break;
93 case 1: col = Color(0, 1, 0); break;
94 case 2: col = Color(0, 0, 1); break;
95 case 3: col = Color(1, 1, 0); break;
96 case 4: col = Color(1, 0, 1); break;
97 case 5: col = Color(0, 1, 1); break;
98 default: assert(false);
99 }
100 glow_sprite->set_color(col);
101 }
102
103 if (suck_timer.check()) {
104 Color col = glow_sprite->get_color();
105 SoundManager::current()->play("sounds/tree_suck.ogg", get_pos());
106 for (const auto& willo : willowisps) {
107 if (willo->get_color() == col) {
108 willo->start_sucking(
109 m_col.m_bbox.get_middle() + SUCK_TARGET_OFFSET
110 + Vector(gameRandom.randf(-SUCK_TARGET_SPREAD, SUCK_TARGET_SPREAD),
111 gameRandom.randf(-SUCK_TARGET_SPREAD, SUCK_TARGET_SPREAD)));
112 }
113 }
114 mystate = STATE_SUCKING;
115 }
116
117 if (willowisp_timer.check()) {
118 if (willowisps.size() < WILLOWISP_COUNT) {
119 Vector pos = Vector(m_col.m_bbox.get_width() / 2, m_col.m_bbox.get_height() / 2 + willo_spawn_y + WILLOWISP_TOP_OFFSET);
120 auto& willowisp = Sector::get().add<TreeWillOWisp>(this, pos, 200 + willo_radius, willo_speed);
121 willowisps.push_back(&willowisp);
122
123 willo_spawn_y -= 40;
124 if (willo_spawn_y < -160)
125 willo_spawn_y = 0;
126
127 willo_radius += 20;
128 if (willo_radius > 120)
129 willo_radius = 0;
130
131 if (willo_speed == 1.8f) {
132 willo_speed = 1.5f;
133 } else {
134 willo_speed = 1.8f;
135 }
136
137 do {
138 willo_color = (willo_color + 1) % 3;
139 } while(willo_color == treecolor);
140
141 switch (willo_color) {
142 case 0: willowisp.set_color(Color(1, 0, 0)); break;
143 case 1: willowisp.set_color(Color(0, 1, 0)); break;
144 case 2: willowisp.set_color(Color(0, 0, 1)); break;
145 case 3: willowisp.set_color(Color(1, 1, 0)); break;
146 case 4: willowisp.set_color(Color(1, 0, 1)); break;
147 case 5: willowisp.set_color(Color(0, 1, 1)); break;
148 default: assert(false);
149 }
150 }
151 }
152
153 if (root_timer.check()) {
154 /* TODO indicate root with an animation */
155 auto player = get_nearest_player();
156 if (player) {
157 Sector::get().add<Root>(Vector(player->get_bbox().get_left(), m_col.m_bbox.get_bottom()+ROOT_TOP_OFFSET));
158 }
159 }
160 } else if (mystate == STATE_SWALLOWING) {
161 if (suck_lantern) {
162 // suck in lantern
163 assert (suck_lantern);
164 Vector pos = suck_lantern->get_pos();
165 Vector delta = m_col.m_bbox.get_middle() + SUCK_TARGET_OFFSET - pos;
166 Vector dir_ = delta.unit();
167 if (delta.norm() < 1) {
168 dir_ = delta;
169 suck_lantern->ungrab(*this, Direction::RIGHT);
170 suck_lantern->remove_me();
171 suck_lantern = nullptr;
172 m_sprite->set_action("swallow", 1);
173 } else {
174 pos += dir_;
175 suck_lantern->grab(*this, pos, Direction::RIGHT);
176 }
177 } else {
178 // wait until lantern is swallowed
179 if (m_sprite->animation_done()) {
180 if (is_color_deadly(suck_lantern_color)) {
181 die();
182 } else {
183 m_sprite->set_action("normal");
184 mystate = STATE_IDLE;
185 spawn_lantern();
186 }
187 }
188 }
189 }
190}
191
192bool
193GhostTree::is_color_deadly(Color color) const
194{
195 if (color == Color(0,0,0)) return false;
196 Color my_color = glow_sprite->get_color();
197 return ((my_color.red != color.red) || (my_color.green != color.green) || (my_color.blue != color.blue));
198}
199
200void
201GhostTree::willowisp_died(TreeWillOWisp* willowisp)
202{
203 if ((mystate == STATE_SUCKING) && (willowisp->was_sucked)) {
204 mystate = STATE_IDLE;
205 }
206 willowisps.erase(std::find_if(willowisps.begin(), willowisps.end(),
207 [willowisp](GameObject* lhs)
208 {
209 return lhs == willowisp;
210 }));
211}
212
213void
214GhostTree::draw(DrawingContext& context)
215{
216 BadGuy::draw(context);
217
218 context.push_transform();
219 if (mystate == STATE_SUCKING) {
220 context.set_alpha(0.5f + fmodf(g_game_time, 0.5f));
221 } else {
222 context.set_alpha(0.5f);
223 }
224 glow_sprite->draw(context.light(), get_pos(), m_layer);
225 context.pop_transform();
226}
227
228bool
229GhostTree::collides(GameObject& other, const CollisionHit& ) const
230{
231 if (mystate != STATE_SUCKING) return false;
232 if (dynamic_cast<Lantern*>(&other)) return true;
233 if (dynamic_cast<Player*>(&other)) return true;
234 return false;
235}
236
237HitResponse
238GhostTree::collision(GameObject& other, const CollisionHit& )
239{
240 if (mystate != STATE_SUCKING) return ABORT_MOVE;
241
242 auto player = dynamic_cast<Player*>(&other);
243 if (player) {
244 player->kill(false);
245 }
246
247 Lantern* lantern = dynamic_cast<Lantern*>(&other);
248 if (lantern) {
249 suck_lantern = lantern;
250 suck_lantern->grab(*this, suck_lantern->get_pos(), Direction::RIGHT);
251 suck_lantern_color = lantern->get_color();
252 mystate = STATE_SWALLOWING;
253 }
254
255 return ABORT_MOVE;
256}
257
258void
259GhostTree::spawn_lantern()
260{
261 Sector::get().add<Lantern>(m_col.m_bbox.get_middle() + SUCK_TARGET_OFFSET);
262}
263
264/* EOF */
265