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.
22 #include "stones_internal.hh"
26 #include "Inventory.hh"
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)
73 /* Move a stone (by sending an impulse) Called when an actor hits a
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
86 Stone::Stone() : freeze_check_running (false) {
89 Stone::Stone(const char * kind
) : GridObject (kind
), freeze_check_running (false) {
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");
109 StoneResponse
Stone::collision_response(const StoneContact
&) {
110 return STONE_REBOUND
;
114 void Stone::actor_hit(const StoneContact
&sc
)
117 maybe_push_stone (sc
);
120 void Stone::actor_touch(const StoneContact
&sc
) {
123 void Stone::on_impulse(const Impulse
& impulse
) {
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() {
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();
167 if (Item
*it
= GetItem(newPos
))
168 it
->on_stonehit(this);
177 bool Stone::move_stone(Direction dir
) {
178 return move_stone(move(get_pos(), dir
), "movesmall");
181 bool Stone::on_move(const GridPos
&origin
) {
183 ShatterActorsInsideField (get_pos());
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) {
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");
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
));
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
)));
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() {
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
);
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")))
267 if (freeze_check_running
)
269 Floor
*fl
= GetFloor(this_pos
);
270 if ((fl
== NULL
) || (!fl
->is_freeze_check()))
272 // Do freeze checks only with standard movables
273 if (this->get_freeze_bits() != FREEZEBIT_STANDARD
)
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
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
)))
335 if (Stone
*stn
= GetStone(move(this_pos
, SOUTH
)))
337 if (Stone
*stn
= GetStone(move(this_pos
, EAST
)))
339 if (Stone
*stn
= GetStone(move(this_pos
, WEST
)))
341 if (Stone
*st
= GetStone(this_pos
))
342 st
->freeze_check_running
= false;
348 } // namespace enigma