Added reload of levels on F7 (Update levelpack) to ease the test of changes.
[enigmagame.git] / src / stones.cc
blob0b2acc4c7f93f6752a22fa8c841894b3aca3a9b8
1 /*
2 * Copyright (C) 2002,2003,2004 Daniel Heck
3 * Copyright (C) 2008,2009 Ronald Lamprecht
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
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.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "errors.hh"
22 #include "stones_internal.hh"
23 #include "server.hh"
24 #include "client.hh"
25 #include "player.hh"
26 #include "Inventory.hh"
27 #include "main.hh"
29 using namespace std;
31 namespace enigma {
33 /* -------------------- Helper routines -------------------- */
35 void Stone::on_creation(GridPos p) {
36 // notify rubberbands that may now exceed max/min limits
37 ObjectList olist = getAttr("rubbers"); // a private deletion resistant copy
38 for (ObjectList::iterator itr = olist.begin(); itr != olist.end(); ++itr)
39 SendMessage(*itr, "_recheck");
40 GridObject::on_creation(p);
43 void Stone::transform(std::string kind) {
44 Stone *newStone = MakeStone(kind.c_str());
45 transferIdentity(newStone); // subclasses may hook this call
46 ObjectList olist = getAttr("rubbers");
47 for (ObjectList::iterator itr = olist.begin(); itr != olist.end(); ++itr) {
48 (*itr)->setAttr("anchor2", newStone);
50 olist = getAttr("wires");
51 for (ObjectList::iterator itr = olist.begin(); itr != olist.end(); ++itr) {
52 (*itr)->setAttr((this == (*itr)->getAttr("anchor1")) ? "anchor1" : "anchor2", newStone);
54 SetStone(get_pos(), newStone);
59 /*! Determine whether the actor hitting the stone can move stone
60 and return either the direction the stone should move or NODIR. */
61 Direction get_push_direction (const StoneContact &sc)
63 ActorInfo *ai = sc.actor->get_actorinfo();
64 Direction dir = contact_face(sc);
66 // Make sure the speed component towards the face of the stone is
67 // large enough and pointing towards the stone.
68 if (dir!=enigma::NODIR && ai->vel * sc.normal < -4)
69 return reverse(dir);
70 return NODIR;
73 /* Move a stone (by sending an impulse) Called when an actor hits a
74 stone. */
75 bool maybe_push_stone (const StoneContact &sc)
77 Direction dir = get_push_direction(sc);
78 if (dir != enigma::NODIR) {
79 sc.actor->send_impulse(sc.stonepos, dir);
80 return GetStone(sc.stonepos) == 0; // return true only if stone vanished
82 return false;
86 Stone::Stone() : freeze_check_running (false) {
89 Stone::Stone(const char * kind) : GridObject (kind), freeze_check_running (false) {
92 Stone::~Stone() {
95 const StoneTraits &Stone::get_traits() const
97 static StoneTraits default_traits = {
98 "INVALID", st_INVALID, stf_none, material_stone, 1.0, MOVABLE_PERSISTENT
100 return default_traits;
103 std::string Stone::getClass() const {
104 const StoneTraits &tr = get_traits();
105 ASSERT(tr.id != st_INVALID, XLevelRuntime, "Stone with invalid traits based class name");
106 return tr.name;
109 StoneResponse Stone::collision_response(const StoneContact &) {
110 return STONE_REBOUND;
114 void Stone::actor_hit(const StoneContact &sc)
116 if (is_movable())
117 maybe_push_stone (sc);
120 void Stone::actor_touch(const StoneContact &sc) {
123 void Stone::on_impulse(const Impulse& impulse) {
124 if (is_movable()) {
125 int id = getId();
126 move_stone(impulse.dir); // may kill the stone!
128 if (Object::getObject(id) != NULL) // not killed?
129 propagateImpulse(impulse);
133 void Stone::propagateImpulse(const Impulse& impulse) {
134 if (!impulse.byWire) {
135 ObjectList olist = getAttr("fellows");
136 for (ObjectList::iterator it = olist.begin(); it != olist.end(); ++it) {
137 Stone *fellow = dynamic_cast<Stone *>(*it);
138 if (fellow != NULL) {
139 Impulse wireImpulse(this, fellow->get_pos(), impulse.dir, true);
140 fellow->on_impulse(wireImpulse);
146 const char * Stone::collision_sound() {
147 return "stone";
150 /* Move a stone (regardless whether it is_movable() or not) if
151 the destination field is free.
152 Returns: true if stone has been moved.
154 Note: This should be used by on_impulse() to perform a move.
156 bool Stone::move_stone(GridPos newPos, const char *soundevent) {
157 if (isDisplayable()) {
158 GridPos p = get_pos();
160 if (!GetStone(newPos)) {
161 sound_event (soundevent);
163 MoveStone(p, newPos);
164 server::IncMoveCounter();
166 if (on_move(p)) {
167 if (Item *it = GetItem(newPos))
168 it->on_stonehit(this);
171 return true;
173 return false;
175 return false;
177 bool Stone::move_stone(Direction dir) {
178 return move_stone(move(get_pos(), dir), "movesmall");
181 bool Stone::on_move(const GridPos &origin) {
182 if (!is_floating())
183 ShatterActorsInsideField (get_pos());
184 return true;
187 /* Multiplies velocity with the attribute-matrix
188 hit_distortion_[xx,xy,yx,yy] and factor hit_factor
189 If components are not set, use ((1,0),(0,1)) as
190 default matrix, resp. defaultfactor as hit_factor. */
191 ecl::V2 Stone::distortedVelocity (ecl::V2 vel, double defaultfactor = 1.0) {
192 ecl::V2 newvel;
193 double factor = this->getDefaultedAttr("hit_strength", defaultfactor);
194 newvel[0] = (double)(this->getDefaultedAttr("hit_distortion_xx", 1)) * vel[0]
195 + (double)(this->getAttr("hit_distortion_xy")) * vel[1];
196 newvel[1] = (double)(this->getAttr("hit_distortion_yx")) * vel[0]
197 + (double)(this->getDefaultedAttr("hit_distortion_yy", 1)) * vel[1];
198 return newvel * factor;
201 /* -------------------- Cluster routines -------------------- */
202 void Stone::autoJoinCluster() {
203 GridPos p = get_pos();
204 Value myCluster = getAttr("cluster");
205 for (int i = WEST; i <= NORTH; i++) {
206 Direction d = (Direction) i;
207 Stone *neighbour = GetStone(move(p, d));
208 if (isConnectable(neighbour)) {
209 Value neighbourCluster = neighbour->getAttr("cluster");
210 if (myCluster) {
211 if (myCluster == neighbourCluster) {
212 setAttr("$connections", getConnections() | to_bits(d));
213 neighbour->setAttr("$connections", neighbour->getConnections() | to_bits(reverse(d)));
214 } else if (!neighbourCluster && neighbour->getConnections() & to_bits(reverse(d))) {
215 setAttr("$connections", getConnections() | to_bits(d));
216 } else {
217 setAttr("$connections", getConnections() & (ALL_DIRECTIONS ^ to_bits(d))); // clear connection
219 } else if (neighbourCluster) {// I have fixed connections -> adapt neighbour
220 if (getConnections() & to_bits(d))
221 neighbour->setAttr("$connections", neighbour->getConnections() | to_bits(reverse(d)));
222 else
223 neighbour->setAttr("$connections", neighbour->getConnections() & (ALL_DIRECTIONS ^ to_bits(reverse(d))));
225 } else if (myCluster) { // no neighbour -> no connection
226 setAttr("$connections", getConnections() & (ALL_DIRECTIONS ^ to_bits(d))); // clear connection
231 void Stone::autoLeaveCluster() {
232 GridPos p = get_pos();
233 for (int i = WEST; i <= NORTH; i++) {
234 Direction d = (Direction) i;
235 Stone *neighbour = GetStone(move(p, d));
236 if (isConnectable(neighbour) && neighbour->getAttr("cluster")) {
237 neighbour->setAttr("$connections", neighbour->getConnections() & (ALL_DIRECTIONS ^ to_bits(reverse(d))));
242 /* -------------------- Freeze check routines -------------------- */
244 FreezeStatusBits Stone::get_freeze_bits() {
245 if(is_floating())
246 return FREEZEBIT_HOLLOW;
247 switch(get_traits().movable) {
248 case MOVABLE_PERSISTENT: return FREEZEBIT_PERSISTENT;
249 case MOVABLE_BREAKABLE: return FREEZEBIT_NO_STONE;
250 case MOVABLE_STANDARD: return FREEZEBIT_STANDARD;
251 default: return FREEZEBIT_IRREGULAR;
255 FreezeStatusBits Stone::get_freeze_bits(GridPos p) {
256 Stone *st = GetStone(p);
257 if(st == NULL)
258 return FREEZEBIT_NO_STONE;
259 return st->get_freeze_bits();
262 bool Stone::freeze_check() {
263 GridPos this_pos = this->get_pos();
264 // Check if stone and floor ask for freeze_check
265 if (!to_bool(this->getAttr("freeze_check")))
266 return false;
267 if (freeze_check_running)
268 return false;
269 Floor *fl = GetFloor(this_pos);
270 if ((fl == NULL) || (!fl->is_freeze_check()))
271 return false;
272 // Do freeze checks only with standard movables
273 if (this->get_freeze_bits() != FREEZEBIT_STANDARD)
274 return false;
276 // Query persistence status of neighboring stones
277 FreezeStatusBits ms_n = get_freeze_bits(move(this_pos, NORTH));
278 FreezeStatusBits ms_nw = get_freeze_bits(move(move(this_pos, NORTH), WEST));
279 FreezeStatusBits ms_ne = get_freeze_bits(move(move(this_pos, NORTH), EAST));
280 FreezeStatusBits ms_w = get_freeze_bits(move(this_pos, WEST));
281 FreezeStatusBits ms_e = get_freeze_bits(move(this_pos, EAST));
282 FreezeStatusBits ms_s = get_freeze_bits(move(this_pos, SOUTH));
283 FreezeStatusBits ms_sw = get_freeze_bits(move(move(this_pos, SOUTH), WEST));
284 FreezeStatusBits ms_se = get_freeze_bits(move(move(this_pos, SOUTH), EAST));
286 // The following if-construction searches for freeze-patterns.
287 // Each block is one pattern, each line represents one
288 // orientation of this pattern.
290 // First block: # Centered at the box "$", there are four orientations
291 // $# of this pattern.
293 // Second block: $$ Each of the "$" can be movable or persistent.
294 // $$ Centered at one of them, there are again four
295 // different orientation.
297 // Third block: #$ This pattern has eight orientations: Fix one of
298 // $# the boxes. The adjacent persistent stone has four
299 // possibilities, the adjacent movable has two choices
300 // for each position of the persistent. The final
301 // persistent is fixed in its position by the other
302 // two stones.
304 // The variables P and PM are abbreviations for "persistent"
305 // and "persistent or movable". Example: "ms_e & p" checks
306 // if the stone east of THIS is persistent.
307 int p = FREEZEBIT_PERSISTENT;
308 int pm = FREEZEBIT_PERSISTENT | FREEZEBIT_STANDARD;
309 if( ((ms_n & p) && (ms_e & p))
310 || ((ms_n & p) && (ms_w & p))
311 || ((ms_s & p) && (ms_e & p))
312 || ((ms_s & p) && (ms_w & p))
314 || ((ms_n & pm) && (ms_nw & pm) && (ms_w & pm))
315 || ((ms_n & pm) && (ms_ne & pm) && (ms_e & pm))
316 || ((ms_s & pm) && (ms_sw & pm) && (ms_w & pm))
317 || ((ms_s & pm) && (ms_se & pm) && (ms_e & pm))
319 || ((ms_n & pm) && (ms_e & p) && (ms_nw & p))
320 || ((ms_n & pm) && (ms_w & p) && (ms_ne & p))
321 || ((ms_s & pm) && (ms_e & p) && (ms_sw & p))
322 || ((ms_s & pm) && (ms_w & p) && (ms_se & p))
323 || ((ms_w & pm) && (ms_n & p) && (ms_sw & p))
324 || ((ms_w & pm) && (ms_s & p) && (ms_nw & p))
325 || ((ms_e & pm) && (ms_n & p) && (ms_se & p))
326 || ((ms_e & pm) && (ms_s & p) && (ms_ne & p)))
328 ReplaceStone(this_pos, MakeStone("st_death"));
329 // recheck neighboring stones
330 // avoid endless loop with bool freeze_check_running
331 if (Stone *st = GetStone(this_pos))
332 st->freeze_check_running = true;
333 if (Stone *stn = GetStone(move(this_pos, NORTH)))
334 stn->freeze_check();
335 if (Stone *stn = GetStone(move(this_pos, SOUTH)))
336 stn->freeze_check();
337 if (Stone *stn = GetStone(move(this_pos, EAST)))
338 stn->freeze_check();
339 if (Stone *stn = GetStone(move(this_pos, WEST)))
340 stn->freeze_check();
341 if (Stone *st = GetStone(this_pos))
342 st->freeze_check_running = false;
343 return true;
345 return false;
348 } // namespace enigma