789 |
or columnwise. Groups of oxyd-compatible puzzle stones are shuffled |
or columnwise. Groups of oxyd-compatible puzzle stones are shuffled |
790 |
randomly at level startup. |
randomly at level startup. |
791 |
|
|
|
|
|
792 |
\subsection puzzlee Example |
\subsection puzzlee Example |
793 |
<table> |
<table> |
794 |
<tr> |
<tr> |
816 |
*/ |
*/ |
817 |
namespace |
namespace |
818 |
{ |
{ |
819 |
class PuzzleStone : public ConnectiveStone, public TimeHandler { |
class PuzzleStone : public ConnectiveStone, public TimeHandler, public laser::PhotoCell { |
820 |
INSTANCELISTOBJ(PuzzleStone); |
INSTANCELISTOBJ(PuzzleStone); |
821 |
public: |
public: |
822 |
PuzzleStone(int connections, bool oxyd1_compatible_) |
PuzzleStone(int connections, bool oxyd1_compatible_) |
823 |
: ConnectiveStone("st-puzzle", connections) |
: ConnectiveStone("st-puzzle", connections) |
824 |
, state(IDLE) |
, state(IDLE) |
825 |
|
, illumination(NODIRBIT) |
826 |
{ |
{ |
827 |
set_attrib("oxyd", int(oxyd1_compatible_)); |
set_attrib("oxyd", int(oxyd1_compatible_)); |
828 |
} |
} |
842 |
Direction dir, int wanted_oxyd_attrib); |
Direction dir, int wanted_oxyd_attrib); |
843 |
|
|
844 |
bool cluster_complete(); |
bool cluster_complete(); |
845 |
void maybe_move_cluster(Cluster &c, bool is_complete, Direction dir); |
void maybe_move_cluster(Cluster &c, bool is_complete, bool actor_with_wand, Direction dir); |
846 |
void rotate_cluster(const Cluster &c); |
void rotate_cluster(const Cluster &c); |
847 |
|
void maybe_rotate_cluster(Direction dir); |
848 |
|
|
849 |
void shuffle(int count); |
void shuffle(int count); |
850 |
|
|
851 |
void message(const string& msg, const Value &val); |
void message(const string& msg, const Value &val); |
852 |
|
|
853 |
|
void on_creation(); |
854 |
|
void on_removal(); |
855 |
|
|
856 |
bool on_laserhit(Direction); |
bool on_laserhit(Direction); |
857 |
void on_impulse(const Impulse& impulse); |
void on_impulse(const Impulse& impulse); |
858 |
|
|
872 |
|
|
873 |
void alarm(); |
void alarm(); |
874 |
|
|
875 |
|
// PhotoCell interface. |
876 |
|
void on_recalc_start(); |
877 |
|
void on_recalc_finish(); |
878 |
|
|
879 |
|
DirectionBits calc_illumination(); |
880 |
|
|
881 |
// Variables |
// Variables |
882 |
bool visited; // flag for DFS |
bool visited; // flag for DFS |
|
|
|
883 |
enum { IDLE, EXPLODING } state; |
enum { IDLE, EXPLODING } state; |
884 |
|
DirectionBits illumination; // last state of surrounding laser beams |
885 |
}; |
}; |
886 |
} |
} |
887 |
|
|
1011 |
} |
} |
1012 |
|
|
1013 |
void |
void |
1014 |
PuzzleStone::maybe_move_cluster(Cluster &c, bool is_complete, Direction dir) |
PuzzleStone::maybe_move_cluster(Cluster &c, bool is_complete, bool actor_with_wand, Direction dir) |
1015 |
{ |
{ |
1016 |
sort(c.begin(), c.end()); |
sort(c.begin(), c.end()); |
1017 |
Cluster mc(c); // Moved cluster |
Cluster mc(c); // Moved cluster |
1033 |
if (!move_ok) |
if (!move_ok) |
1034 |
return; |
return; |
1035 |
|
|
1036 |
// If the floor at the cluster's new position consists exclusively |
// If the floor at a complete cluster's new position consists |
1037 |
// of abyss or water, create a bridge instead of moving the |
// exclusively of abyss or water, create a bridge instead of |
1038 |
// cluster. |
// moving the cluster. |
1039 |
bool create_bridge = false; |
// |
1040 |
|
// For partial clusters build bridges only on water and if |
1041 |
if (is_complete) { |
// the wielded item is not the magic wand. |
1042 |
create_bridge = true; |
|
1043 |
for (unsigned i=0; i<mc.size(); ++i) { |
bool create_bridge = true; |
1044 |
if (Floor *fl = GetFloor(mc[i])) { |
|
1045 |
if (!fl->is_kind("fl-abyss") && !fl->is_kind("fl-water")) { |
for (unsigned i=0; create_bridge && i<mc.size(); ++i) { |
1046 |
create_bridge = false; |
if (Floor *fl = GetFloor(mc[i])) { |
1047 |
break; |
if (fl->is_kind("fl-abyss")) { |
1048 |
} |
if (!is_complete) create_bridge = false; |
1049 |
|
} |
1050 |
|
else if (fl->is_kind("fl-water")) { |
1051 |
|
if (!is_complete && actor_with_wand) create_bridge = false; |
1052 |
|
} |
1053 |
|
else { |
1054 |
|
create_bridge = false; |
1055 |
} |
} |
1056 |
} |
} |
1057 |
} |
} |
1116 |
void |
void |
1117 |
PuzzleStone::shuffle(int count) |
PuzzleStone::shuffle(int count) |
1118 |
{ |
{ |
1119 |
if (oxyd1_compatible()) { |
assert(oxyd1_compatible()); |
1120 |
Direction move_dir = static_cast<Direction>(IntegerRand(0, 3)); |
|
1121 |
Cluster c; |
Direction move_dir = static_cast<Direction>(IntegerRand(0, 3)); |
1122 |
find_row_or_column_cluster(c, get_pos(), reverse(move_dir), 1); |
Cluster c; |
1123 |
if (!c.empty()) { |
find_row_or_column_cluster(c, get_pos(), reverse(move_dir), 1); |
1124 |
GridPos last = c.back(); |
if (!c.empty()) { |
1125 |
|
GridPos last = c.back(); |
1126 |
|
c.clear(); |
1127 |
|
find_row_or_column_cluster(c, last, move_dir, 1); |
1128 |
|
|
1129 |
|
// if cluster too small to rotate -> turn direction by 90 deg |
1130 |
|
if (c.size() <= 1) { |
1131 |
|
move_dir = rotate(move_dir, true); |
1132 |
c.clear(); |
c.clear(); |
1133 |
find_row_or_column_cluster(c, last, move_dir, 1); |
find_row_or_column_cluster(c, last, move_dir, 1); |
1134 |
|
} |
1135 |
|
|
1136 |
// if cluster too small to rotate -> turn direction by 90 deg |
if (c.size() > 1) { |
|
if (c.size() <= 1) { |
|
|
move_dir = rotate(move_dir, true); |
|
|
c.clear(); |
|
|
find_row_or_column_cluster(c, last, move_dir, 1); |
|
|
} |
|
|
|
|
|
if (c.size() > 1) { |
|
1137 |
|
|
1138 |
// @@@ FIXME: check whether rotation is impossible |
// @@@ FIXME: check whether rotation is impossible |
1139 |
// (e.g. blocked by unmoveable stone at both sides) |
// (e.g. blocked by unmoveable stone at both sides) |
1140 |
|
|
1141 |
while (count--) |
while (count--) |
1142 |
rotate_cluster(c); |
rotate_cluster(c); |
|
} |
|
1143 |
} |
} |
1144 |
} |
} |
1145 |
} |
} |
1181 |
|
|
1182 |
// @@@ FIXME: At the moment it's possible to push partial puzzle stones |
// @@@ FIXME: At the moment it's possible to push partial puzzle stones |
1183 |
// next to an already exploding cluster. Then the part will explode as well. |
// next to an already exploding cluster. Then the part will explode as well. |
1184 |
// I've no idea what oxyd does, but to fix this we have to change the way |
// Possible fix : mark whole cluster as "EXPLODING_SOON" when explosion is initiated |
|
// how explosions are forwarded. [ralf] |
|
1185 |
|
|
1186 |
// ignite adjacent fields |
// ignite adjacent fields |
1187 |
SendExplosionEffect(p, DYNAMITE); |
SendExplosionEffect(p, DYNAMITE); |
1193 |
} |
} |
1194 |
|
|
1195 |
void |
void |
1196 |
PuzzleStone::message(const string& msg, const Value &) { |
PuzzleStone::message(const string& msg, const Value &val) { |
1197 |
if (msg == "init") { // shuffle at level startup |
if (oxyd1_compatible()) { |
1198 |
int count = options::Difficulty == DIFFICULTY_EASY |
if (msg == "init") { |
1199 |
? 1 |
// shuffle oxyd1 style puzzle pieces at level startup. |
1200 |
: IntegerRand(0, 4); |
// only done this way if gametype is GAMET_ENIGMA. |
1201 |
|
if (enigma::GameCompatibility == GAMET_ENIGMA) { |
1202 |
|
int count = options::Difficulty == DIFFICULTY_EASY |
1203 |
|
? 1 |
1204 |
|
: IntegerRand(0, 4); |
1205 |
|
|
1206 |
shuffle(count); |
shuffle(count); |
1207 |
|
} |
1208 |
|
} |
1209 |
|
else if (msg == "scramble") { |
1210 |
|
// oxyd levels contain explicit information on how to |
1211 |
|
// shuffle puzzle stones. According to that information |
1212 |
|
// a "scramble" message is send to specific puzzle stones |
1213 |
|
// together with information about the direction. |
1214 |
|
// only done this way if gametype is not GAMET_ENIGMA. |
1215 |
|
|
1216 |
|
Direction dir = static_cast<Direction>(val.get_double()); |
1217 |
|
Cluster c; |
1218 |
|
find_row_or_column_cluster(c, get_pos(), dir, 1); |
1219 |
|
|
1220 |
|
int size = c.size(); |
1221 |
|
if (size >= 2) { |
1222 |
|
int count = IntegerRand(1, size-1); |
1223 |
|
while (count--) |
1224 |
|
rotate_cluster(c); |
1225 |
|
} |
1226 |
|
} |
1227 |
} |
} |
1228 |
} |
} |
1229 |
|
|
1232 |
{ |
{ |
1233 |
if (!oxyd1_compatible() && state == IDLE) { |
if (!oxyd1_compatible() && state == IDLE) { |
1234 |
Cluster c; |
Cluster c; |
1235 |
bool is_complete = find_cluster(c); |
bool is_complete = find_cluster(c); |
1236 |
maybe_move_cluster(c, is_complete, impulse.dir); |
bool actor_with_wand = false; |
1237 |
|
|
1238 |
|
if (Actor *ac = dynamic_cast<Actor*>(impulse.sender)) { |
1239 |
|
actor_with_wand = wielded_item_is(ac, "it-magicwand"); |
1240 |
|
} |
1241 |
|
|
1242 |
|
maybe_move_cluster(c, is_complete, actor_with_wand, impulse.dir); |
1243 |
} |
} |
1244 |
} |
} |
1245 |
|
|
1246 |
bool |
bool |
1247 |
PuzzleStone::explode_complete_cluster() |
PuzzleStone::explode_complete_cluster() |
1248 |
{ |
{ |
1249 |
|
// @@@ FIXME: explode_complete_cluster should mark the whole cluster |
1250 |
|
// as "EXPLODING_SOON" (otherwise it may be changed before it explodes completely) |
1251 |
|
|
1252 |
assert(state == IDLE); |
assert(state == IDLE); |
1253 |
bool exploded = false; |
bool exploded = false; |
1254 |
|
|
1299 |
} |
} |
1300 |
|
|
1301 |
if (exploded) { |
if (exploded) { |
1302 |
|
warning("exploding complete cluster"); |
1303 |
explode(); |
explode(); |
1304 |
} |
} |
1305 |
} |
} |
1310 |
} |
} |
1311 |
|
|
1312 |
bool |
bool |
1313 |
PuzzleStone::on_laserhit(Direction) |
PuzzleStone::on_laserhit(Direction ) |
1314 |
{ |
{ |
|
if (state == IDLE && explode_complete_cluster()) |
|
|
return false; |
|
|
|
|
1315 |
return get_connections() == 0; |
return get_connections() == 0; |
1316 |
} |
} |
1317 |
|
|
1318 |
void |
void |
1319 |
|
PuzzleStone::maybe_rotate_cluster(Direction dir) |
1320 |
|
{ |
1321 |
|
assert(oxyd1_compatible()); |
1322 |
|
warning("maybe_rotate_cluster dir=%i", int(dir)); |
1323 |
|
|
1324 |
|
Cluster c; |
1325 |
|
find_row_or_column_cluster(c, get_pos(), dir, 1); |
1326 |
|
if (c.size() >= 2) { |
1327 |
|
warning("ok -> rotate"); |
1328 |
|
rotate_cluster(c); |
1329 |
|
} |
1330 |
|
} |
1331 |
|
|
1332 |
|
void PuzzleStone::on_creation() |
1333 |
|
{ |
1334 |
|
photo_activate(); |
1335 |
|
ConnectiveStone::on_creation(); |
1336 |
|
illumination = DirectionBits(ALL_DIRECTIONS+1); // invalid |
1337 |
|
} |
1338 |
|
|
1339 |
|
void PuzzleStone::on_removal() |
1340 |
|
{ |
1341 |
|
photo_deactivate(); |
1342 |
|
ConnectiveStone::on_removal(); |
1343 |
|
} |
1344 |
|
|
1345 |
|
void |
1346 |
|
PuzzleStone::on_recalc_start() |
1347 |
|
{ |
1348 |
|
} |
1349 |
|
|
1350 |
|
DirectionBits |
1351 |
|
PuzzleStone::calc_illumination() |
1352 |
|
{ |
1353 |
|
DirectionBits illu = NODIRBIT; |
1354 |
|
GridPos p = get_pos(); |
1355 |
|
|
1356 |
|
if (laser::LightFrom(p, NORTH)) illu = DirectionBits(illu|NORTHBIT); |
1357 |
|
if (laser::LightFrom(p, SOUTH)) illu = DirectionBits(illu|SOUTHBIT); |
1358 |
|
if (laser::LightFrom(p, EAST)) illu = DirectionBits(illu|EASTBIT); |
1359 |
|
if (laser::LightFrom(p, WEST)) illu = DirectionBits(illu|WESTBIT); |
1360 |
|
|
1361 |
|
return illu; |
1362 |
|
} |
1363 |
|
|
1364 |
|
void |
1365 |
|
PuzzleStone::on_recalc_finish() |
1366 |
|
{ |
1367 |
|
DirectionBits illu = calc_illumination(); |
1368 |
|
|
1369 |
|
if (illu != illumination) { |
1370 |
|
if (illumination == (ALL_DIRECTIONS+1)) { |
1371 |
|
warning("initializing illumination on new stone"); |
1372 |
|
} |
1373 |
|
else { |
1374 |
|
warning("illumination changed (old=%i new=%i)", illumination, illu); |
1375 |
|
|
1376 |
|
DirectionBits newBeams = DirectionBits(illu ^ (illu & illumination)); |
1377 |
|
|
1378 |
|
if (state == IDLE && newBeams != NODIRBIT) { |
1379 |
|
if (!explode_complete_cluster()) { |
1380 |
|
if (oxyd1_compatible()) { |
1381 |
|
if (newBeams & NORTHBIT) maybe_rotate_cluster(SOUTH); |
1382 |
|
if (newBeams & SOUTHBIT) maybe_rotate_cluster(NORTH); |
1383 |
|
if (newBeams & EASTBIT) maybe_rotate_cluster(WEST); |
1384 |
|
if (newBeams & WESTBIT) maybe_rotate_cluster(EAST); |
1385 |
|
} |
1386 |
|
} |
1387 |
|
} |
1388 |
|
} |
1389 |
|
illumination = illu; |
1390 |
|
} |
1391 |
|
} |
1392 |
|
|
1393 |
|
|
1394 |
|
void |
1395 |
PuzzleStone::actor_hit(const StoneContact &sc) |
PuzzleStone::actor_hit(const StoneContact &sc) |
1396 |
{ |
{ |
1397 |
if (get_connections() == NODIRBIT) |
if (get_connections() == NODIRBIT) |