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