1 /*
2  * Copyright (C) 2006-2019 Christopho, Solarus - http://www.solarus-games.org
3  *
4  * Solarus 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  * Solarus 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 along
15  * with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 #include "solarus/core/CommandsEffects.h"
18 #include "solarus/core/Equipment.h"
19 #include "solarus/entities/Block.h"
20 #include "solarus/entities/Jumper.h"
21 #include "solarus/hero/HeroState.h"
22 #include "solarus/hero/SwordSwingingState.h"
23 #include "solarus/lua/LuaContext.h"
24 
25 namespace Solarus {
26 
27 /**
28  * \brief Constructor.
29  * \param hero The hero to control with this state.
30  * \param state_name A name describing this state.
31  */
HeroState(Hero & hero,const std::string & state_name)32 HeroState::HeroState(Hero& hero, const std::string& state_name):
33   HeroState(state_name) {
34 
35   set_entity(hero);
36 }
37 
38 /**
39  * \brief Constructor.
40  *
41  * Call set_entity() later before starting the state.
42  *
43  * \param state_name A name describing this state.
44  */
HeroState(const std::string & state_name)45 HeroState::HeroState(const std::string& state_name):
46   State(state_name) {
47 
48 }
49 
50 /**
51  * \brief Returns the hero of this state.
52  * \return The hero.
53  */
get_entity()54 inline Hero& HeroState::get_entity() {
55   return static_cast<Hero&>(Entity::State::get_entity());
56 }
57 
58 /**
59  * \brief Returns the hero of this state.
60  * \return The hero.
61  */
get_entity() const62 inline const Hero& HeroState::get_entity() const {
63   return static_cast<const Hero&>(Entity::State::get_entity());
64 }
65 
66 /**
67  * \brief Returns the hero's sprites.
68  * \return the sprites
69  */
get_sprites() const70 const HeroSprites& HeroState::get_sprites() const {
71   return get_entity().get_hero_sprites();
72 }
73 
74 /**
75  * \overload Non-const version.
76  */
get_sprites()77 HeroSprites& HeroState::get_sprites() {
78   return get_entity().get_hero_sprites();
79 }
80 
81 /**
82  * \brief Draws this state.
83  */
draw_on_map()84 void HeroState::draw_on_map() {
85 
86   get_sprites().draw_on_map();
87 }
88 
89 /**
90  * \copydoc Entity::State::notify_attack_command_pressed
91  */
notify_attack_command_pressed()92 void HeroState::notify_attack_command_pressed() {
93   Hero& hero = get_entity();
94 
95   if (!hero.is_suspended()
96       && get_commands_effects().get_sword_key_effect() == CommandsEffects::ATTACK_KEY_SWORD
97       && hero.can_start_sword()) {
98 
99     hero.start_sword();
100   }
101 }
102 
103 /**
104  * \brief Notifies this state that an item command was just pressed.
105  * \param slot The slot activated (1 or 2).
106  */
notify_item_command_pressed(int slot)107 void HeroState::notify_item_command_pressed(int slot) {
108   Hero& hero = get_entity();
109 
110   EquipmentItem* item = get_equipment().get_item_assigned(slot);
111 
112   if (item != nullptr && hero.can_start_item(*item)) {
113     hero.start_item(*item);
114   }
115 }
116 
117 /**
118  * \copydoc Entity::State::is_block_obstacle
119  */
is_block_obstacle(Block & block)120 bool HeroState::is_block_obstacle(
121     Block& block) {
122   return block.is_hero_obstacle(get_entity());
123 }
124 
125 /**
126  * \copydoc Entity::State::is_raised_block_obstacle
127  */
is_raised_block_obstacle(CrystalBlock &)128 bool HeroState::is_raised_block_obstacle(CrystalBlock& /* raised_block */) {
129   return !get_entity().is_on_raised_blocks();
130 }
131 
132 /**
133  * \copydoc Entity::State::is_jumper_obstacle
134  */
is_jumper_obstacle(Jumper & jumper,const Rectangle & candidate_position)135 bool HeroState::is_jumper_obstacle(
136     Jumper& jumper, const Rectangle& candidate_position) {
137   const Hero& hero = get_entity();
138 
139   if (jumper.overlaps_jumping_region(hero.get_bounding_box(), false)) {
140     // The hero already overlaps the active part of the jumper.
141     // This is authorized if he arrived from another direction
142     // and thus did not activate it.
143     // This can be used to leave water pools for example.
144     return false;
145   }
146 
147   if (!jumper.overlaps_jumping_region(candidate_position, false)) {
148     // The candidate position is in the inactive region: always accept that.
149     return false;
150   }
151 
152   if (!get_can_take_jumper()) {
153     // If jumpers cannot be used in this state, consider their active region
154     // as obstacles and their inactive region as traversable.
155     // The active region should be an obstacle.
156     return true;
157   }
158 
159   // At this point, we know that the jumper can be activated.
160 
161   const bool hero_in_jump_position =
162       jumper.is_in_jump_position(hero, hero.get_bounding_box(), false);
163   const bool candidate_in_jump_position =
164       jumper.is_in_jump_position(hero, candidate_position, false);
165 
166   if (candidate_in_jump_position) {
167     // Wants to move to a valid jump position: accept.
168     return false;
169   }
170 
171   if (hero_in_jump_position) {
172     // If the hero is already correctly placed (ready to jump),
173     // make the jumper obstacle so that the player has to move in the
174     // jumper's direction during a small delay before jumping.
175     // This also prevents the hero to get inside the jumper's active region.
176     return true;
177   }
178 
179   const bool candidate_in_extended_jump_position =
180       jumper.is_in_jump_position(hero, candidate_position, true);
181 
182   if (candidate_in_extended_jump_position) {
183     // Wants to get inside the active region from an end of the jumper:
184     // don't accept this.
185     return true;
186   }
187 
188   if (!jumper.is_jump_diagonal() &&
189       hero.is_moving_towards(jumper.get_direction() / 2)) {
190     // Special case: make the jumper traversable so
191     // that the smooth movement can slide to it.
192     return false;
193   }
194 
195   if (!jumper.is_jump_diagonal() &&
196       get_name() == "swimming" &&  // TODO use inheritance instead
197       hero.is_moving_towards(((jumper.get_direction() / 2) + 2) % 4)
198   ) {
199     // Other special case: trying to enter the jumper the reverse way while
200     // swimming: we accept this to allow the hero to leave water pools.
201     // TODO I'm not sure if this behavior is really a good idea.
202     // This may change in a future version.
203     return false;
204   }
205 
206   return true;
207 }
208 
209 }
210 
211