/* * XEvil(TM) Copyright (C) 1994,2000 Steve Hardt and Michael Judge * http://www.xevil.com * satan@xevil.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program, the file "gpl.txt"; if not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA, or visit http://www.gnu.org. */ // "world.cpp" #if X11 #ifndef NO_PRAGMAS #pragma implementation "world.cpp" #endif #endif // Include Files. #include "stdafx.h" #if WIN32 #include "resource.h" #endif // Include Files. #include using namespace std; #include "utils.h" #include "coord.h" #include "xdata.h" #include "area.h" #include "world.h" #include "bitmaps/world/world.bitmaps" // For PhysMover. #include "actual.h" // Defines. #define HANGING_PERCENT 0.40 // The % of the edge hanging off the corner. #define STRAND_PERCENT .50 // Gives number of boundaries between rooms. #define STRAND_LENGTH 8 // Length of strands in Blueprints::fill_random. #define EMPTY_TRIES_MAX 5000 #define WALL_LENGTH 30 #define WALLS_PERCENT .01 #define WALLS_HORIZ_CHANCE 4 // 2 gives equal chance #define LADDER_CHANCE 10 #define MAP_PRINT_DEFAULT False #define UP_DOWN_CHANCE 4 #define OPEN_ITERATIONS 2 #define POSTERS_ACTUAL_PERCENT .0003 // Percentage of dim. #define DOORS_ACTUAL_PERCENT .1 // Pairs of doors as percentage of rooms. // Percentage of rooms that can possibly have movers. // both are multipliers on number of rooms. #define MOVERS_PERCENT .4 // Tries to find a space for the mover. #define MOVER_TRIES 6 // Min number of movers across a horizontal track must be. #define MOVERS_HORIZ_MIN_TRACK 6 // moverwidths #define MOVERS_HORIZ_TRACK_LENGTH 25 // wsquares // Used to determine how much space to leave between walls. // Only the default, actual variable is objectMinDim. #define OBJECT_COL_MAX 2 #define OBJECT_ROW_MAX 2 #define OBJECT_BIG_COL_MAX 2 #define OBJECT_BIG_ROW_MAX 3 // Blueprints class. A helper for World. class Blueprints { public: Blueprints(WorldP world,char map[W_ROW_MAX_MAX][W_COL_MAX_MAX], const Dim &objectDimMax,int specialMap); /* EFFECTS: Create a blueprints object with room boundaries defined. */ int get_middle_row(int down); int get_middle_col(int across); /* NOTES: Offset from edge of room, in WSQUARES. Floor at middleRow. Ladder at [middleCol ... middleCol + objectDimMax.colMax). */ // NOTE: The following three methods replace Blueprints::fill_map. void room_boundries(); /* MODIFIES: map */ /* EFFECTS: Using the defined room boundaries, create a map of the world. */ void extra_walls(); /* MODIFIES: map */ void up_downs(); /* MODIFIES: map */ /* EFFECTS: Change all walls on edge of ladders to up_downs. */ void print(); /* EFFECTS: Print the room boundaries to cout. */ Boolean blocked_right(const RoomIndex &ri); Boolean blocked_bottom(const RoomIndex &ri); Boolean blocked_left(const RoomIndex &ri); Boolean blocked_top(const RoomIndex &ri); /* EFFECTS: Returns whether ri has a blocking wall on the edge of the room. */ private: void fill_random(); /* EFFECTS: Snake algorithm to define boundries between rooms. */ void special_map_adjust(); /* EFFECTS: Adjust map for any requirements imposed by the special map specification. */ Boolean open(const RoomIndex &ri); void has_right(const RoomIndex &ri,const Loc &rl); void missing_right(const RoomIndex &ri,const Loc &rl); void has_bottom(const RoomIndex &ri,const Loc &rl); void missing_bottom(const RoomIndex &ri,const Loc &rl); void has_top(const RoomIndex &ri,const Loc &rl); void missing_top(const RoomIndex &ri,const Loc &rl); void has_left(const RoomIndex &ri,const Loc &rl); void missing_left(const RoomIndex &ri,const Loc &rl); void ladder(const Loc &loc); /* EFFECTS: Draw a ladder going up and down from loc until a wall is hit. */ Boolean wall_below(int rTop,int cTop,int num); /* EFFECTS: Return whether there is a Wwall or WupDown in the vertical column of num blocks whose top is (rTop,cTop). */ WorldP world; Rooms worldRooms; Dim worldDim; char (* map)[W_COL_MAX_MAX]; int middleRows[W_DOWN_MAX_MAX]; int middleCols[W_ACROSS_MAX_MAX]; Boolean horiz[W_DOWN_MAX_MAX + 1][W_ACROSS_MAX_MAX]; Boolean vert[W_DOWN_MAX_MAX][W_ACROSS_MAX_MAX + 1]; WSpecialMap specialMap; Dim objectDimMax; }; Blueprints::Blueprints(WorldP w,char m[W_ROW_MAX_MAX][W_COL_MAX_MAX], const Dim &o_dim_max,WSpecialMap special) { objectDimMax = o_dim_max; world = w; worldRooms = world->get_rooms(); worldDim = world->get_dim(); map = m; specialMap = special; RoomIndex ri; // Boundaries only at the edges of the world. for (ri.across = 0; ri.across < worldRooms.acrossMax + 1; ri.across++) { for (ri.down = 0; ri.down < worldRooms.downMax + 1; ri.down++) { if (ri.across < worldRooms.acrossMax) { if ((ri.down == 0) || (ri.down == worldRooms.downMax)) { horiz[ri.down][ri.across] = True; } else { horiz[ri.down][ri.across] = False; } } if (ri.down < worldRooms.downMax) { //#if 0 if ((ri.across == 0) || (ri.across == worldRooms.acrossMax)) { vert[ri.down][ri.across] = True; } else { //#endif vert[ri.down][ri.across] = False; } } } } // The maze algorithm. if (specialMap != World::ZIG_ZAG) { // ZIG_ZAG takes care of itself in special_map_adjust. fill_random(); } // Any special adjustment for special world maps. special_map_adjust(); assert(W_ROOM_ROW_MAX - 2 * (objectDimMax.rowMax + 1) > 0); assert(W_ROOM_COL_MAX - 2 * (objectDimMax.colMax + 1) > 0); // Compute middles for (int riR = 0; riR < worldRooms.downMax; riR++) { middleRows[riR] = objectDimMax.rowMax + 1 + Utils::choose(W_ROOM_ROW_MAX - 2 * (objectDimMax.rowMax + 1)); } for (int riC = 0; riC < worldRooms.acrossMax; riC++) { middleCols[riC] = objectDimMax.colMax + 1 + Utils::choose(W_ROOM_COL_MAX - 2 * (objectDimMax.colMax + 1)); } } int Blueprints::get_middle_row(int down) { assert (0 <= down && down < worldRooms.downMax); return middleRows[down]; } int Blueprints::get_middle_col(int across) { assert (0 <= across && across < worldRooms.acrossMax); return middleCols[across]; } void Blueprints::room_boundries() { RoomIndex ri; Loc rl; // Walls between rooms. for (ri.across = 0, rl.c = 0; ri.across < worldRooms.acrossMax; ri.across++, rl.c += W_ROOM_COL_MAX) { for (ri.down = 0, rl.r = 0; ri.down < worldRooms.downMax; ri.down++, rl.r += W_ROOM_ROW_MAX) { if (! horiz[ri.down][ri.across]) { missing_top(ri,rl); } else { has_top(ri,rl); } if (! horiz[ri.down + 1][ri.across]) { missing_bottom(ri,rl); } else { has_bottom(ri,rl); } if (! vert[ri.down][ri.across]) { missing_left(ri,rl); } else { has_left(ri,rl); } if (! vert[ri.down][ri.across + 1]) { missing_right(ri,rl); } else { has_right(ri,rl); } } } } void Blueprints::extra_walls() { for (int walls = 0; walls < worldDim.rowMax * worldDim.colMax * WALLS_PERCENT; walls ++) { Boolean ok = True; Loc loc; loc.r = Utils::choose(worldDim.rowMax); loc.c = Utils::choose(worldDim.colMax); int delta = Utils::coin_flip() ? 1 : -1; int horiz = Utils::choose(WALLS_HORIZ_CHANCE); // Special case for SEALS special world, no vertical walls. if (specialMap == World::SEALS) { horiz = 1; } Loc check; for (check.c = loc.c - objectDimMax.colMax; check.c <= loc.c + objectDimMax.colMax; check.c++) { for (check.r = loc.r - objectDimMax.rowMax; check.r <= loc.r + objectDimMax.rowMax; check.r++) { if (!world->open(check,True)) { ok = False; } } } // Horizontal extra wall. if (horiz) { while (ok && Utils::choose(WALL_LENGTH)) { for (check.c = loc.c; check.c != loc.c + delta * (objectDimMax.colMax + 1); check.c += delta) { for (check.r = loc.r - objectDimMax.rowMax; check.r <= loc.r + objectDimMax.rowMax; check.r++) { if (!world->open(check,True)) { ok = False; } } } if (ok) { if (! Utils::choose(LADDER_CHANCE)) { ladder(loc); } else { map[loc.r][loc.c] = Wwall; } loc.c += delta; if (specialMap != World::SEALS && Utils::choose(UP_DOWN_CHANCE) == 0) { loc.r += (Utils::coin_flip() ? 1 : -1); } } } } // Vertical wall, horiz == 0 else { while (ok && Utils::choose(WALL_LENGTH)) { for (check.r = loc.r; check.r != loc.r + delta * (objectDimMax.rowMax + 1); check.r += delta) { for (check.c = loc.c - objectDimMax.colMax; check.c <= loc.c + objectDimMax.colMax; check.c++) { if (!world->open(check,True)) { ok = False; } } } if (ok) { map[loc.r][loc.c] = Wwall; loc.r += delta; } } } } // for walls. } void Blueprints::up_downs() { Loc loc; // Change Wwall to WupDown if a ladder is immediately to left or right. for (loc.c = 0; loc.c < worldDim.colMax; loc.c++) { for (loc.r = 0; loc.r < worldDim.rowMax; loc.r++) { if ((map[loc.r][loc.c] == Wwall) && (((loc.c + 1 < worldDim.colMax) && (map[loc.r][loc.c + 1] == Wladder)) || ((loc.c - 1 >= 0) && (map[loc.r][loc.c - 1] == Wladder)))) { map[loc.r][loc.c] = WupDown; } } } // Change Wwall to WupDown if doesn't have a wall to right AND left. for (loc.c = 0; loc.c < worldDim.colMax; loc.c++) { for (loc.r = 0; loc.r < worldDim.rowMax; loc.r++) { if (map[loc.r][loc.c] == Wwall && !((wall_below(loc.r - 1,loc.c - 1,3) && wall_below(loc.r - 1,loc.c + 1,3)) || wall_below(loc.r - 1,loc.c,1) || wall_below(loc.r + 1,loc.c,1))) { map[loc.r][loc.c] = WupDown; } } } } void Blueprints::print() { RoomIndex ri; for (ri.down = 0; ri.down <= worldRooms.downMax; ri.down++) { // Horizontal boundaries. for (ri.across = 0; ri.across < worldRooms.acrossMax; ri.across++) { cout << ((horiz[ri.down][ri.across]) ? "**" : "*-"); cout << "*" << endl; // Vertical boundaries. if (ri.down < worldRooms.downMax) { for (ri.across = 0; ri.across <= worldRooms.acrossMax; ri.across++) { cout << ((vert[ri.down][ri.across]) ? "* " : "| "); } } cout << endl; } } } Boolean Blueprints::blocked_right(const RoomIndex &ri) { assert(ri.down < worldRooms.downMax && ri.down >= 0); assert(ri.across < worldRooms.acrossMax && ri.across >= 0); return vert[ri.down][ri.across + 1]; } Boolean Blueprints::blocked_bottom(const RoomIndex &ri) { assert(ri.down < worldRooms.downMax && ri.down >= 0); assert(ri.across < worldRooms.acrossMax && ri.across >= 0); return horiz[ri.down + 1][ri.across]; } Boolean Blueprints::blocked_left(const RoomIndex &ri) { assert(ri.down < worldRooms.downMax && ri.down >= 0); assert(ri.across < worldRooms.acrossMax && ri.across >= 0); return vert[ri.down][ri.across]; } Boolean Blueprints::blocked_top(const RoomIndex &ri) { assert(ri.down < worldRooms.downMax && ri.down >= 0); assert(ri.across < worldRooms.acrossMax && ri.across >= 0); return horiz[ri.down][ri.across]; } void Blueprints::fill_random() { // Don't do maze algorithm if the world is too small. if ((worldRooms.acrossMax < 2) || (worldRooms.downMax < 2)) { return; } int strandsNum = (int)(worldRooms.acrossMax * worldRooms.downMax * STRAND_PERCENT); for (int n = 0; n < strandsNum; n++) { // Starting position. RoomIndex ri; ri.across = Utils::choose(worldRooms.acrossMax - 1) + 1; ri.down = Utils::choose(worldRooms.downMax - 1) + 1; Boolean ok = open(ri); // open implies that ri must not be on outer boundaries. while (Utils::choose(STRAND_LENGTH) && ok) { // Move horiz. if (Utils::coin_flip()) { // Increase. if (Utils::coin_flip()) { horiz[ri.down][ri.across] = True; ri.across++; ok = open(ri); } // Decrease. else { horiz[ri.down][ri.across - 1] = True; ri.across--; ok = open(ri); } } // Move vert. else { // Increase. if (Utils::coin_flip()) { vert[ri.down][ri.across] = True; ri.down++; ok = open(ri); } // Decrease. else { vert[ri.down - 1][ri.across] = True; ri.down--; ok = open(ri); } } } } } void Blueprints::special_map_adjust() { switch (specialMap) { case World::SEALS: { // remove all interior vertical walls. RoomIndex ri; for (ri.down = 0; ri.down < worldRooms.downMax; ri.down++) { for (ri.across = 1; ri.across < worldRooms.acrossMax; ri.across++) { vert[ri.down][ri.across] = False; } } } break; case World::ZIG_ZAG: { RoomIndex ri; // Loop over all but top and bottom rows. for (ri.down = worldRooms.downMax - 1; ri.down > 0; ri.down--) { for (ri.across = 0; ri.across < worldRooms.acrossMax; ri.across++) { // Every row alternates having right and left side open // First row on bottom should have right open. Boolean rightOpen = ((worldRooms.downMax - ri.down) % 2 == 1); if ((rightOpen && ri.across != worldRooms.acrossMax - 1) || (!rightOpen && ri.across != 0)) { horiz[ri.down][ri.across] = True; } } } } break; } } Boolean Blueprints::open(const RoomIndex &ri) { assert((ri.across >= 0) && (ri.across <= worldRooms.acrossMax) && (ri.down >= 0) && (ri.down <= worldRooms.downMax)); if ((ri.across > 0) && horiz[ri.down][ri.across - 1]) { return False; } if ((ri.across < worldRooms.acrossMax) && horiz[ri.down][ri.across]) { return False; } if ((ri.down > 0) && vert[ri.down - 1][ri.across]) { return False; } if ((ri.down < worldRooms.downMax) && vert[ri.down][ri.across]) { return False; } return True; } void Blueprints::has_top(const RoomIndex &,const Loc &roomLoc) { for (int c = roomLoc.c; c < roomLoc.c + W_ROOM_COL_MAX; c++) map[roomLoc.r][c] = Wwall; } void Blueprints::missing_top(const RoomIndex &roomIndex, const Loc &roomLoc) { for (int c = roomLoc.c + middleCols[roomIndex.across]; c < roomLoc.c + middleCols[roomIndex.across] + objectDimMax.colMax; c++) { for (int r = roomLoc.r; r < roomLoc.r + W_ROOM_ROW_MAX - 1; r++) { map[r][c] = Wladder; } } } void Blueprints::has_bottom(const RoomIndex &, const Loc &roomLoc) { for (int c = roomLoc.c; c < roomLoc.c + W_ROOM_COL_MAX; c++) { map[roomLoc.r + W_ROOM_ROW_MAX - 1][c] = Wwall; } } void Blueprints::missing_bottom(const RoomIndex &roomIndex, const Loc &roomLoc) { for (int c = roomLoc.c + middleCols[roomIndex.across]; c < roomLoc.c + middleCols[roomIndex.across] + objectDimMax.colMax; c++) { for (int r = roomLoc.r + middleRows[roomIndex.down] - objectDimMax.rowMax; r < roomLoc.r + W_ROOM_ROW_MAX; r++) { map[r][c] = Wladder; } } } void Blueprints::has_left(const RoomIndex &,const Loc &roomLoc) { for (int r = roomLoc.r; r < roomLoc.r + W_ROOM_ROW_MAX; r++) { map[r][roomLoc.c] = Wwall; } } void Blueprints::missing_left(const RoomIndex &roomIndex, const Loc &roomLoc) { for (int c = roomLoc.c; c < roomLoc.c + middleCols[roomIndex.across]; c++) { map[roomLoc.r + middleRows[roomIndex.down]][c] = Wwall; } } void Blueprints::has_right(const RoomIndex &,const Loc &roomLoc) { for (int r = roomLoc.r; r < roomLoc.r + W_ROOM_ROW_MAX; r++) { map[r][roomLoc.c + W_ROOM_COL_MAX - 1] = Wwall; } } void Blueprints::missing_right(const RoomIndex &roomIndex, const Loc &roomLoc) { for (int c = roomLoc.c + middleCols[roomIndex.across] + objectDimMax.colMax; c < roomLoc.c + W_ROOM_COL_MAX; c++) { map[roomLoc.r + middleRows[roomIndex.down]][c] = Wwall; } } void Blueprints::ladder(const Loc &init) { // delta is only -1 and 1. for (int delta = -1; delta <= 1; delta += 2) { Loc loc = init; // To avoid hitting init twice. if (delta == 1) { loc.r++; } // Running directly into wall or ladder. while (world->open(loc,True)) { map[loc.r][loc.c] = Wladder; // Stop when there is a wall to the left or right. Loc left, right; right.r = left.r = loc.r; left.c = loc.c - 1; right.c = loc.c + 1; if ((!world->open(left) || !world->open(right))) { // Extra extension at top. if (delta == -1) { for (int extra = 1; extra <= objectDimMax.rowMax; extra++) { Loc extraLoc; extraLoc.c = loc.c; extraLoc.r = loc.r - extra; if (world->open(extraLoc,True)) { map[extraLoc.r][extraLoc.c] = Wladder; } else { break; } } } break; } loc.r += delta; } // while } // for delta } Boolean Blueprints::wall_below(int rTop,int cTop,int num) { Loc loc; loc.c = cTop; for (loc.r = rTop; loc.r < rTop + num; loc.r++) { if (world->inside(loc) && (map[loc.r][loc.c] == Wwall || map[loc.r][loc.c] == WupDown)) { return True; } } return False; } // Functions for Mover class. Mover::Mover(InStreamP in,WorldP w,LocatorP l) { world = w; locator = l; areaSet = True; timerSet = False; area.read(in); moverId.read(in); physMoverId.read(in); } void Mover::update_from_stream(const Area &a) { assert(areaSet); area = a; } int Mover::get_write_length() { return Area::get_write_length() + // area Identifier::get_write_length() + // moverId Identifier::get_write_length(); // physMoverId } void Mover::write(OutStreamP out) { area.write(out); moverId.write(out); physMoverId.write(out); } void Mover::init(WorldP w,LocatorP l,const Area &a,const Size &v, MoverId mId) { world = w; locator = l; area = a; areaSet = True; vel = v; moverId = mId; timerSet = False; assert(check_area(area)); } void Mover::init_area(const Area &a) { area = a; areaSet = True; } void Mover::init_not_area(WorldP w,LocatorP l,const Size &v,MoverId mId) { world = w; locator = l; vel = v; moverId = mId; timerSet = False; assert(check_area(area)); } void Mover::clock() { // Vel is always valid and gives what the PREVIOUS velocity was. // Should never be off the track. assert(check_area(area)); // not pausing at top or bottom. if (!timerSet) { // Make sure areaNext is still on the track of moverSquares. Area areaNext = area + vel; // Just move along. if (check_area(areaNext)) { area = areaNext; } // change direction. else if (area.wsquare_alligned()) { // Set timer. timer.set(W_MOVER_PAUSE_TIME); timerSet = True; // remember velocity velStored.width = -vel.width; velStored.height = -vel.height; vel.set_zero(); // area doesn't change. } else { areaNext = area.wsquare_allign(vel); // After wsquare_allign(), area shouldn't be touching wsquares it // wasn't already touching. assert(check_area(areaNext) && !(area == areaNext)); assert(areaNext.wsquare_alligned()); vel = areaNext - area; area = areaNext; } } // timerSet // pausing at top or bottom. else { // Reverse direction. if (timer.ready() ) { // Can't use areaNext. // vel = velStored; // hack vel.width = (velStored.width > 0) ? W_MOVER_SPEED : ((velStored.width < 0) ? -W_MOVER_SPEED : 0); vel.height = (velStored.height > 0) ? W_MOVER_SPEED : ((velStored.height < 0) ? -W_MOVER_SPEED : 0); Area areaNext = area + vel; if (check_area(areaNext)) { area = areaNext; } // mover is stuck on a two square wide spot and isn't going anywhere. else { vel.set_zero(); } timerSet = False; } } timer.clock(); // Tell PhysMover that we've been clocked, so can update the Area it // presents to the world. PhysMoverP pM = (PhysMoverP)locator->lookup(physMoverId); if (pM) { pM->mover_clock(); } } // True iff all squares covering area are moverSquares for this Mover. Boolean Mover::check_area(const Area &area) { Loc list[AR_WSQUARES_MAX]; int nItems; area.wsquares(list,nItems); // go through all wsquares on area. for (int m = 0; m < nItems; m++) { const Loc &loc = list[m]; // wsquare outside the world. if (!world->inside(loc)) { return False; } // Mover is friend of World. (might change) UnionSquare *uSquare = world->unionSquares[loc.r][loc.c]; if (!uSquare || uSquare->type != UN_MOVER || uSquare->mSquare.mover != this) return False; } // for return True; } UnionSquare *UnionSquare::read(InStreamP in,WorldP world) { UnionSquare *ret = new UnionSquare; assert(ret); ret->type = (int)in->read_char(); switch (ret->type) { case UN_POSTER: ret->pSquare.poster = in->read_short(); ret->pSquare.loc.read(in); in->read_short(); // padding break; case UN_DOOR: ret->dSquare.topBottom = in->read_short(); ret->dSquare.dest.read(in); in->read_short(); // padding break; case UN_MOVER: { ret->mSquare.orientation = in->read_short(); MoverId mId(in); ret->mSquare.mover = world->lookup(mId); } break; default: cerr << "Received invalid type of UnionSquare." << endl; } return ret; } int UnionSquare::get_write_length() { // Add padding to make Loc the size of Identifier. assert(Identifier::get_write_length() == Loc::get_write_length() + 2); return sizeof(char) + // type sizeof(short) + // poster/topBottom/orientation Identifier::get_write_length(); // loc/dest/moverId } void UnionSquare::write(OutStreamP out) { out->write_char((u_char)type); switch (type) { case UN_POSTER: assert(pSquare.poster <= USHRT_MAX); out->write_short((u_short)pSquare.poster); pSquare.loc.write(out); out->write_short(666); // padding break; case UN_DOOR: assert(dSquare.topBottom <= USHRT_MAX); out->write_short((u_short)dSquare.topBottom); dSquare.dest.write(out); out->write_short(666); // padding break; case UN_MOVER: { assert(mSquare.orientation <= USHRT_MAX); out->write_short((u_short)mSquare.orientation); assert(mSquare.mover); MoverId mId = mSquare.mover->get_mover_id(); mId.write(out); } break; default: assert(0); } } // Functions for World class. World::World() { // Set by World::set_locator(); locator = NULL; // Get pointer to static list of themes. themes = World_themes; objectDimMax.colMax = OBJECT_COL_MAX; objectDimMax.rowMax = OBJECT_ROW_MAX; mapPrint = MAP_PRINT_DEFAULT; rooms.acrossMax = 1; rooms.downMax = 1; roomsNext = rooms; rooms_dim_size_update(); blueprints = NULL; moversNum = 0; uniqueGen = 0; justReset = True; specialMap = specialMapNext = MAP_NONE; worldFile = NULL; xValid = XVARS_VALID_INIT; Loc loc; for (loc.r = 0; loc.r < W_ROW_MAX_MAX; loc.r++) { for (loc.c = 0; loc.c < W_COL_MAX_MAX; loc.c++) { unionSquares[loc.r][loc.c] = NULL; map[loc.r][loc.c] = Wempty; } } // Initialize posterDims for (int n = 0; n < W_ALL_POSTERS_NUM; n++) { assert(posters[n].size.width % WSQUARE_WIDTH == 0 && posters[n].size.width % WSQUARE_HEIGHT == 0); // The scaling factor of 2 is always true, regardless of whether we // are dealing with stretched coordinates or not. posterDims[n].colMax = posters[n].size.width / (WSQUARE_WIDTH * 2); posterDims[n].rowMax = posters[n].size.height / (WSQUARE_HEIGHT * 2); } choose_theme(); // Caller now must explicitly ask for the title_map(). } World::~World() { clear_everything(); Utils::freeif(worldFile); // Generate new map. if (blueprints) { delete blueprints; } } void World::title_map(IViewportInfo* vInfoProvider) { ViewportInfo vInfo = vInfoProvider->get_info(); Dim vDim = vInfo.get_viewport_dim(); // Set to the dimensions of the viewport. clear_everything(); rooms_dim_size_update(&vDim); // Put wall on top and bottom. Loc loc; for (loc.r = 0; loc.r < dim.rowMax; loc.r++) { for (loc.c = 0; loc.c < dim.colMax; loc.c++) { // Top and bottom. if ((loc.r == (dim.rowMax - 1)) || (loc.r == 0)) { map[loc.r][loc.c]= Wwall; } } } // Put title poster in center of screen. // // Title poster is always the last poster in the list. Loc start; start.c = dim.colMax / 2 - posterDims[W_ALL_POSTERS_NUM - 1].colMax / 2; start.r = dim.rowMax / 2 - posterDims[W_ALL_POSTERS_NUM - 1].rowMax / 2; for (loc.c = start.c; loc.c < start.c + posterDims[W_ALL_POSTERS_NUM - 1].colMax; loc.c++) { for (loc.r = start.r; loc.r < start.r + posterDims[W_ALL_POSTERS_NUM - 1].rowMax; loc.r++) { if (! unionSquares[loc.r][loc.c]) { unionSquares[loc.r][loc.c] = new UnionSquare; } assert(unionSquares[loc.r][loc.c]); unionSquares[loc.r][loc.c]->type = UN_POSTER; unionSquares[loc.r][loc.c]->pSquare.poster = W_ALL_POSTERS_NUM - 1; unionSquares[loc.r][loc.c]->pSquare.loc.c = loc.c - start.c; unionSquares[loc.r][loc.c]->pSquare.loc.r = loc.r - start.r; } } } Dim World::get_room_dim() { Dim ret(W_ROOM_ROW_MAX,W_ROOM_COL_MAX); return ret; } Size World::get_room_size() { Size ret; ret.width = W_ROOM_COL_MAX * WSQUARE_WIDTH; ret.height = W_ROOM_ROW_MAX * WSQUARE_HEIGHT; return ret; } Boolean World::check_door(const Loc &loc,Loc &dest) { if (unionSquares[loc.r][loc.c] && unionSquares[loc.r][loc.c]->type == UN_DOOR) { dest = unionSquares[loc.r][loc.c]->dSquare.dest; return True; } return False; } void World::set_rooms_next(const Rooms &r) { Utils::freeif(worldFile); roomsNext.acrossMax = Utils::minimum(W_ACROSS_MAX_MAX,r.acrossMax); roomsNext.downMax = Utils::minimum(W_DOWN_MAX_MAX,r.downMax); } void World::set_special_map_next(WSpecialMap map) { Utils::freeif(worldFile); specialMapNext = map; switch (map) { case SEALS: roomsNext.acrossMax = 4; roomsNext.downMax = 2; break; case ZIG_ZAG: roomsNext.acrossMax = W_ACROSS_MAX_MAX; roomsNext.downMax = 3; // W_DOWN_MAX_MAX; break; case FLAG: roomsNext.acrossMax = 6; roomsNext.downMax = 3; break; case MAP_NONE: break; default: assert(0); } } Boolean World::overlap(const Box &box) { return ((box.loc.c < dim.colMax) && (box.loc.c + box.dim.colMax > 0) && (box.loc.r < dim.rowMax) && (box.loc.r + box.dim.rowMax > 0)); } void World::draw(CMN_DRAWABLE buffer,Xvars &xvars,int dpyNum, const Area &area, Boolean reduceDraw,Boolean background3D) { if (!xvars.is_valid(xValid)) { init_x(xvars,IX_INIT,NULL); } if (!reduceDraw) { draw_background(buffer,xvars,dpyNum,area,background3D); } Pos aPos; Size aSize; area.get_rect(aPos,aSize); int moversFoundNum = 0; MoverP moversFound[MOVERS_MAX]; // Draw squares and create list of movers encountered. Loc loc; for (loc.c = (int)floor((float)aPos.x / (float)WSQUARE_WIDTH); loc.c < (int)ceil((float)(aPos.x + aSize.width) / (float)WSQUARE_WIDTH); loc.c++) { for (loc.r = (int)floor((float)aPos.y / (float)WSQUARE_HEIGHT); loc.r < ceil((float)(aPos.y + aSize.height)/(float)WSQUARE_HEIGHT); loc.r++) { draw_square(buffer,xvars,dpyNum,loc, loc.c * WSQUARE_WIDTH - aPos.x, loc.r * WSQUARE_HEIGHT - aPos.y,reduceDraw); mover_list_add(moversFound,moversFoundNum,loc); } } // Draw movers once. for (int n = 0; n < moversFoundNum; n++) { Pos movPos; Size movSize; const Area &area = moversFound[n]->get_area(); area.get_rect(movPos,movSize); draw_mover(buffer,xvars,dpyNum,moversFound[n], movPos.x - aPos.x,movPos.y - aPos.y); } if (!reduceDraw) { draw_outside(buffer,xvars,dpyNum,area); } } void World::draw_outside(CMN_DRAWABLE buffer,Xvars &xvars,int dpyNum, Area area) { if (!xvars.is_valid(xValid)) { init_x(xvars,IX_INIT,NULL); } // In window coords. area = xvars.stretch_area(area); Pos pos = area.get_pos(); Size size = area.get_size(); Size worldSize = xvars.stretch_size(get_size()); // Yuck, divide by two and stretch. // World::outsides, backgrounds, and posters should // really be in unstretched coordinates. Size imgSize; imgSize.set(xvars.stretch_x(outsides[outsideIndex].size.width >> 1), xvars.stretch_y(outsides[outsideIndex].size.height >> 1)); // Top side, stick out on right and left if (pos.y < 0) { for (int across = Utils::div(pos.x,imgSize.width); across * imgSize.width < pos.x + size.width; across++) { for (int down = Utils::div(pos.y,imgSize.height); down < 0; down++) { // area in window coords Area imgArea(Pos(across * imgSize.width - pos.x,down * imgSize.height - pos.y), imgSize); // Clip to window (0,0,size.width,size.height) Size srcOffset = imgArea.clip(size); draw_outside_offset(buffer,xvars, dpyNum,srcOffset,imgArea); } } } // Left side, stick out on bottom if (pos.x < 0) { for (int down = Utils::maximum(0,Utils::div(pos.y,imgSize.height)); down * imgSize.height < pos.y + size.height; down++) { for (int across = Utils::div(pos.x,imgSize.width); (across * imgSize.width < pos.x + size.width) && (across < 0); across++) { // area in window coords Area imgArea(Pos(across * imgSize.width - pos.x, down * imgSize.height - pos.y),imgSize); // Clip to window (0,0,size.width,size.height) Size srcOffset = imgArea.clip(size); draw_outside_offset(buffer,xvars, dpyNum,srcOffset,imgArea); } } } // Bottom side, stick out on right if (pos.y + size.height > worldSize.height) { for (int across = Utils::maximum(0,Utils::div(pos.x,imgSize.width)); across * imgSize.width < pos.x + size.width; across++) { for (int down = Utils::maximum(Utils::div(pos.y,imgSize.height), worldSize.height / imgSize.height); down * imgSize.height < pos.y + size.height; down++) { // area in window coords Area imgArea(Pos(across * imgSize.width - pos.x,down * imgSize.height - pos.y), imgSize); // Clip to window (0,0,size.width,size.height) Size srcOffset = imgArea.clip(size); // Clip to world. srcOffset += imgArea.clip_top(worldSize.height - pos.y); draw_outside_offset(buffer,xvars, dpyNum,srcOffset,imgArea); } } } // Right side, if (pos.x + size.width > worldSize.width) { for (int down = Utils::maximum(0,Utils::div(pos.y,imgSize.height)); (down * imgSize.height < pos.y + size.height) && (down * imgSize.height < worldSize.height); down++) { for (int across = Utils::maximum(Utils::div(pos.x,imgSize.width), worldSize.width / imgSize.width); across * imgSize.width < pos.x + size.width; across++) { // area in window coords Area imgArea(Pos(across * imgSize.width - pos.x,down * imgSize.height - pos.y), imgSize); // Clip to window (0,0,size.width,size.height) Size srcOffset = imgArea.clip(size); // Clip to world. srcOffset += imgArea.clip_left(worldSize.width - pos.x); draw_outside_offset(buffer,xvars, dpyNum,srcOffset,imgArea); } } } } Boolean World::open(const Loc &loc,Boolean laddersClosed,Boolean postersClosed, Boolean doorsClosed,Boolean outsideClosed) { if (!inside(loc)) { return !outsideClosed; } // a poster isn't closed at loc Boolean posterOpen = (!postersClosed || !(unionSquares[loc.r][loc.c] && unionSquares[loc.r][loc.c]->type == UN_POSTER)); // a door isn't "closed" at loc Boolean doorOpen = (!doorsClosed || !(unionSquares[loc.r][loc.c] && unionSquares[loc.r][loc.c]->type == UN_DOOR)); // a moverSquare isn't "closed" at loc (uses laddersClosed for now) Boolean moverSquareOpen = (!laddersClosed || !(unionSquares[loc.r][loc.c] && unionSquares[loc.r][loc.c]->type == UN_MOVER)); Boolean ret = // map contains an open square at loc (map[loc.r][loc.c] == Wempty || map[loc.r][loc.c] == WtextSquare || map[loc.r][loc.c] == Wsquanch || (map[loc.r][loc.c] == Wladder && !laddersClosed)) && posterOpen && doorOpen && moverSquareOpen; // quick sanity check. if (unionSquares[loc.r][loc.c]) { assert(map[loc.r][loc.c] == Wempty || map[loc.r][loc.c] == Wwall); } return ret; } // The only World::open() that takes movers into account. Boolean World::open(const Area &area,Boolean laddersClosed, Boolean postersClosed,Boolean doorsClosed, Boolean outsideClosed) { /* Avoid a certain type of crash when area is far out of the world. I.e. when area.pos is near overflow. */ if (!inside(area.middle_wsquare())) { // Fix this to deal with outsideClosed. return False; } // Try all wsquares on top of area. Loc list[AR_WSQUARES_MAX]; int nsquares; area.wsquares(list,nsquares); for (int n = 0; n < nsquares; n++) { if (!open(list[n],laddersClosed,postersClosed, doorsClosed,outsideClosed)) { return False; } // Have to add this inside() check because if outsideClosed is False, // we can still get here. if (inside(list[n])) { // Look at movers corresponding to moverSquares that are covered by area. UnionSquare *uSquare = unionSquares[list[n].r][list[n].c]; if (uSquare && uSquare->type == UN_MOVER) { const Area &moverArea = uSquare->mSquare.mover->get_area(); if (moverArea.overlap(area)) { return False; } } } } // for return True; } Boolean World::open(const Box &box,Boolean laddersClosed,Boolean postersClosed, Boolean doorsClosed) { Loc loc; for (loc.c = box.loc.c; loc.c < box.loc.c + box.dim.colMax; loc.c++) { for (loc.r = box.loc.r; loc.r < box.loc.r + box.dim.rowMax; loc.r++) { if (!open(loc,laddersClosed,postersClosed,doorsClosed)) { return False; } } } return True; } Wsig World::open_offset(Size &offset,const Area &areaNew,const Vel &vel) { if (open(areaNew)) { return W_NO_SIG; } // wrap-around disabled. #if 0 // Check for player wrapping around the world. if (check_wrap_around(offset,areaNew)) { return W_CLOSE; } #endif Vel opp = -1.0f * vel; Dir best[4]; Dir others[4]; int bestNum,othersNum; opp.get_dirs_4(best,others,bestNum,othersNum); if (open_try_dirs(offset,areaNew,best,bestNum)) { return W_CLOSE; } if (open_try_dirs(offset,areaNew,others,othersNum)) { return W_CLOSE; } // Special "Hero stuck in corner" case. if (open_try_corners(offset,areaNew)) { return W_CLOSE; } // Big jump. if (open_try_diagonals(offset,areaNew)) { return W_CLOSE_BAD; } return W_FAILURE; } void World::compute_touching_hanging(Touching &touching, Hanging &hanging, MoverP &touchingMover,const Area &area) { Pos apos; Size asize; area.get_rect(apos,asize); touching = CO_air; hanging.corner = CO_air; TouchingList touchingList; area.touching_wsquares(touchingList); TouchingList edgeList; area.edge_wsquares(edgeList); // Set to NULL if not touching a Mover. touchingMover = NULL; // Last one takes priority. Down is highest priority, then // Up, then right, left. th_helper(touching,hanging,touchingMover,touchingList.l,edgeList.l, True,area,apos.y,asize.height, WSQUARE_HEIGHT,CO_l,CO_l_UP,CO_l_DN); th_helper(touching,hanging,touchingMover,touchingList.r,edgeList.r, True,area,apos.y,asize.height, WSQUARE_HEIGHT,CO_r,CO_r_UP,CO_r_DN); th_helper(touching,hanging,touchingMover,touchingList.up,edgeList.up, False,area,apos.x,asize.width, WSQUARE_WIDTH,CO_up,CO_up_L,CO_up_R); th_helper(touching,hanging,touchingMover,touchingList.dn,edgeList.dn, False,area,apos.x,asize.width, WSQUARE_WIDTH,CO_dn,CO_dn_L,CO_dn_R); } void World::demo_reset() { // Don't reuse the nonce because may have old packets // uniqueGen = 0; justReset = True; choose_theme(); } void World::clear_everything() { // Delete old unionSquares. Loc loc; for (loc.r = 0; loc.r < dim.rowMax; loc.r++) { for (loc.c = 0; loc.c < dim.colMax; loc.c++) { if (unionSquares[loc.r][loc.c]) { delete unionSquares[loc.r][loc.c]; unionSquares[loc.r][loc.c] = NULL; } } } // Change rooms,dim,size of world. rooms = roomsNext; specialMap = specialMapNext; rooms_dim_size_update(); // Clear map. for (loc.r = 0; loc.r < dim.rowMax; loc.r++) { for (loc.c = 0; loc.c < dim.colMax; loc.c++) { map[loc.r][loc.c] = Wempty; } } delete_movers(); } void World::close_horiz_mover(const Loc &loc,Boolean &inHorizMover) { if (inHorizMover) { // initialize the mover. MoverId moverId; moverId.index = moversNum; moverId.unique = uniqueGen++; Size vel; vel.set(Utils::coin_flip() ? W_MOVER_SPEED : -W_MOVER_SPEED,0); // Use specified initial position if (movers[moversNum]->is_area_set()) { movers[moversNum]->init_not_area(this,locator,vel,moverId); } // Start at right end of the track. else { // Doesn't check that we have at least two wsquares to the right. Pos pos((loc.c - 2) * WSQUARE_WIDTH,loc.r * WSQUARE_HEIGHT); Area area(AR_RECT,pos,moverSize); movers[moversNum]->init(this,locator,area,vel,moverId); } // Dummy mover object, so mover gets redrawn. PhysMoverP p = new PhysMover(this,locator,movers[moversNum]); assert(p); locator->add(p); movers[moversNum]->set_phys_mover_id(p->get_id()); moversNum++; inHorizMover = False; } } void World::close_vert_movers(const Dim &dim) { Loc l; // Traverse all squares. for (l.c = 0; l.c < (dim.colMax - 1); l.c++) { for (l.r = 0; l.r < dim.rowMax; l.r++) { Loc loc = l; Boolean inVertMover = False; // Go down, filling out mover. // (MoverP)-1 hack is described in read_from_file(). while (loc.r < dim.rowMax && unionSquares[loc.r][loc.c] && unionSquares[loc.r][loc.c]->type == UN_MOVER && (unionSquares[loc.r][loc.c]->mSquare.mover == NULL || unionSquares[loc.r][loc.c]->mSquare.mover == (MoverP)-1) && unionSquares[loc.r][loc.c]->mSquare.orientation == OR_VERT && unionSquares[loc.r][loc.c+1] && unionSquares[loc.r][loc.c+1]->type == UN_MOVER && (unionSquares[loc.r][loc.c+1]->mSquare.mover == NULL || unionSquares[loc.r][loc.c+1]->mSquare.mover == (MoverP)-1) && unionSquares[loc.r][loc.c+1]->mSquare.orientation == OR_VERT) { if (!inVertMover) { // create the mover. inVertMover = True; assert(moversNum < MOVERS_MAX); movers[moversNum] = new Mover; assert(movers[moversNum]); } // If starting position of Mover is given by user. if (unionSquares[loc.r][loc.c]->mSquare.mover == (MoverP)-1 && !movers[moversNum]->is_area_set()) { Pos pos(loc.c * WSQUARE_WIDTH,loc.r * WSQUARE_HEIGHT); Area area(AR_RECT,pos,moverSize); movers[moversNum]->init_area(area); } unionSquares[loc.r][loc.c]->mSquare.mover = movers[moversNum]; unionSquares[loc.r][loc.c+1]->mSquare.mover = movers[moversNum]; loc.r++; } // Close out mover. if (inVertMover) { // initialize the mover. MoverId moverId; moverId.index = moversNum; moverId.unique = uniqueGen++; Size vel; vel.set(0,Utils::coin_flip() ? W_MOVER_SPEED : -W_MOVER_SPEED); // Use specified initial position if (movers[moversNum]->is_area_set()) { movers[moversNum]->init_not_area(this,locator,vel,moverId); } // Start at top. else { Pos pos(l.c * WSQUARE_WIDTH,l.r * WSQUARE_HEIGHT); Area area(AR_RECT,pos,moverSize); movers[moversNum]->init(this,locator,area,vel,moverId); } // Dummy mover object, so mover gets redrawn. PhysMoverP p = new PhysMover(this,locator,movers[moversNum]); assert(p); locator->add(p); movers[moversNum]->set_phys_mover_id(p->get_id()); moversNum++; } } // for } // for } Boolean World::read_from_file(char *filename) { FILE *fp = fopen(filename,"r"); if (!fp) { cerr << "Could not open " << filename << endl; return False; } if (EOF == fscanf(fp,"XEvil World 1.0\n")) { cerr << "Invalid header in world file." << endl; fclose(fp); return False; } Dim dim; if (2 != fscanf(fp,"%dx%d\n",&dim.colMax,&dim.rowMax)) { cerr << "Could not read size of world." << endl; fclose(fp); return False; } if (dim.colMax > W_COL_MAX_MAX) { cerr << "Across size is larger than maximum. Truncating to " << W_COL_MAX_MAX << endl; dim.colMax = W_COL_MAX_MAX; } if (dim.rowMax > W_ROW_MAX_MAX) { cerr << "Down size is larger than maximum. Truncating to " << W_ROW_MAX_MAX << endl; dim.rowMax = W_ROW_MAX_MAX; } Dim halfRoom(W_ROOM_ROW_MAX / 2,W_ROOM_COL_MAX / 2); if (dim.rowMax % halfRoom.rowMax != 0 || dim.colMax % halfRoom.colMax != 0) { cerr << "Dimensions of world must be multiples of " << halfRoom.colMax << " across and " << halfRoom.rowMax << " down." << endl; fclose(fp); return False; } if (dim.rowMax <= 0 || dim.colMax <= 0) { cerr << "Invalid dimension, negative or zero." << endl; fclose(fp); return False; } // No longer need to pad with extra Wwall. this->dim = dim; rooms_dim_size_update(&dim); char buffer[W_COL_MAX_MAX]; Loc loc; for (loc.r = 0; loc.r < this->dim.rowMax; loc.r++) { // dim is the size of the data on file, this.dim is // the size alligned to room boundries, fill rest with Wwall if (loc.r < dim.rowMax) { Boolean badRow = False; if (!fgets(buffer,W_COL_MAX_MAX,fp)) { badRow = True; } if (buffer[0] == ';') { // Hit a comment, ignore the line. loc.r--; continue; } if ((int)strlen(buffer) < dim.colMax) { badRow = True; } if (badRow) { cerr << "Could not read World data." << endl; fclose(fp); return False; } Boolean inHorizMover = False; for (loc.c = 0; loc.c < this->dim.colMax; loc.c++) { if (loc.c < dim.colMax) { char val; switch (buffer[loc.c]) { case '.': val = Wempty; close_horiz_mover(loc,inHorizMover); break; case '#': val = Wwall; close_horiz_mover(loc,inHorizMover); break; case 'H': val = Wladder; close_horiz_mover(loc,inHorizMover); break; case '*': val = WupDown; close_horiz_mover(loc,inHorizMover); break; case '^': case '|': case '&': // sticking in floor. val = Wempty; close_horiz_mover(loc,inHorizMover); if (loc.c + 1 < dim.colMax && (buffer[loc.c+1] == '^' || buffer[loc.c+1] == '|' || buffer[loc.c+1] == '&')) { Boolean markStartPos = (buffer[loc.c] == '^' && buffer[loc.c + 1] == '^'); Boolean stickInFloor = (buffer[loc.c] == '&' && buffer[loc.c + 1] == '&'); // Do two squares. for (int x = 0; x < 2; x++) { unionSquares[loc.r][loc.c] = new UnionSquare; assert(unionSquares[loc.r][loc.c]); unionSquares[loc.r][loc.c]->type = UN_MOVER; // Big hack here. We don't create mover here since we don't know which // mover goes with which wsquare, but we still need to remember where // the initial position is. Set mover to be (MoverP)(-1) for wsquares // that the user specified as initial positions. if (markStartPos) { unionSquares[loc.r][loc.c]->mSquare.mover = (MoverP)-1; } else { unionSquares[loc.r][loc.c]->mSquare.mover = NULL; } unionSquares[loc.r][loc.c]->mSquare.orientation = OR_VERT; if (x == 0) { map[loc.r][loc.c] = stickInFloor ? Wwall : Wempty; loc.c++; } else { assert(x == 1); val = stickInFloor ? Wwall : Wempty; } } } break; case '~': case '-': case '%': if (buffer[loc.c] == '%') { val = Wwall; } else { val = Wempty; } // Make sure at least two next to each other. if ((loc.c + 1 < dim.colMax && (buffer[loc.c+1] == '~' || buffer[loc.c+1] == '-' || buffer[loc.c+1] == '%')) || (loc.c - 1 >= 0 && (buffer[loc.c-1] == '~' || buffer[loc.c-1] == '-' || buffer[loc.c-1] == '%'))) { if (!inHorizMover) { assert(moversNum < MOVERS_MAX); movers[moversNum] = new Mover; assert(movers[moversNum]); } inHorizMover = True; unionSquares[loc.r][loc.c] = new UnionSquare; assert(unionSquares[loc.r][loc.c]); unionSquares[loc.r][loc.c]->type = UN_MOVER; unionSquares[loc.r][loc.c]->mSquare.mover = movers[moversNum]; unionSquares[loc.r][loc.c]->mSquare.orientation = OR_HORIZ; // If starting position of Mover is given by user. if (buffer[loc.c] == '~' && // Need two '~' in a row. loc.c + 1 < dim.colMax && buffer[loc.c + 1] == '~' && // pos not already set. !movers[moversNum]->is_area_set()) { Pos pos(loc.c * WSQUARE_WIDTH,loc.r * WSQUARE_HEIGHT); Area area(AR_RECT,pos,moverSize); movers[moversNum]->init_area(area); } } break; default: cerr << "Error reading map, unknown character " << buffer[loc.c] << endl; val = Wempty; close_horiz_mover(loc,inHorizMover); } map[loc.r][loc.c] = val; } else { // Close out mover. close_horiz_mover(loc,inHorizMover); // Fill rest of row with Wwall map[loc.r][loc.c] = Wwall; } } // for loc.c } else { // Fill remaining rows with Wwall for (loc.c = 0; loc.c < this->dim.colMax; loc.c++) { map[loc.r][loc.c] = Wwall; } } } close_vert_movers(dim); fclose(fp); return True; } void World::read_from_stream(InStreamP in) { Dim dim(in); themeIndex = in->read_int(); themeIndex = Utils::minimum(W_THEME_NUM - 1,themeIndex); backgroundIndex = in->read_int(); #if WIN32 backgroundIndex = Utils::minimum(W_ALL_BACKGROUNDS_NUM - 1,backgroundIndex); #endif outsideIndex = in->read_int(); #if WIN32 outsideIndex = Utils::minimum(W_ALL_OUTSIDES_NUM - 1,outsideIndex); #endif // Take minimum with the max number, just in case the server gives an invalid // number. Only for windows, UNIX client doesn't give a shit about these numbers. if (!in->alive()) { // failure. return; } rooms_dim_size_update(&dim); Loc l; for (l.c = 0; l.c < dim.colMax; l.c++) { for (l.r = 0; l.r < dim.rowMax; l.r++) { map[l.r][l.c] = in->read_char(); } } assert(moversNum == 0); moversNum = in->read_int(); int n; for (n = 0; n < moversNum; n++) { movers[n] = new Mover(in,this,locator); assert(movers[n]); } int uCount = in->read_int(); for (n = 0; n < uCount; n++) { Loc loc; loc.read(in); if (unionSquares[loc.r][loc.c]) { delete unionSquares[loc.r][loc.c]; } unionSquares[loc.r][loc.c] = UnionSquare::read(in,this); assert(unionSquares[loc.r][loc.c]); // Hack to deal with different poster sizes on UNIX vs. Windows. if (unionSquares[loc.r][loc.c]->type == UN_POSTER) { // Weed out posters that aren't there if (unionSquares[loc.r][loc.c]->pSquare.poster >= W_ALL_POSTERS_NUM) { delete unionSquares[loc.r][loc.c]; unionSquares[loc.r][loc.c] = NULL; } else { // Special check to weed out posters that are too big. Loc &pLoc = unionSquares[loc.r][loc.c]->pSquare.loc; int p = unionSquares[loc.r][loc.c]->pSquare.poster; if (pLoc.c >= posterDims[p].colMax || pLoc.r >= posterDims[p].rowMax) { delete unionSquares[loc.r][loc.c]; unionSquares[loc.r][loc.c] = NULL; } } } } } int World::get_write_length() { // Count number of union squares. int uCount = 0; Loc l; for (l.c = 0; l.c < dim.colMax; l.c++) { for (l.r = 0; l.r < dim.rowMax; l.r++) { if (unionSquares[l.r][l.c]) { uCount++; } } } return Dim::get_write_length() + // dim sizeof(int) + // themeIndex sizeof(int) + // backgroundIndex sizeof(int) + // outsideIndex dim.rowMax * dim.colMax * sizeof(char) + // wsquares sizeof(int) + // moversNum moversNum * Mover::get_write_length() + // movers[] sizeof(int) + // uCount uCount * (Loc::get_write_length() + UnionSquare::get_write_length()); // unionSquares[] } void World::write(OutStreamP out) { dim.write(out); out->write_int(themeIndex); out->write_int(backgroundIndex); out->write_int(outsideIndex); if (!out->alive()) { return; } Loc l; for (l.c = 0; l.c < dim.colMax; l.c++) { for (l.r = 0; l.r < dim.rowMax; l.r++) { out->write_char(map[l.r][l.c]); } } // Write movers before union squares so that MoverSquares can refer to // the movers. out->write_int(moversNum); int n; for (n = 0; n < moversNum; n++) { movers[n]->write(out); } // Count number of union squares. int uCount = 0; for (l.c = 0; l.c < dim.colMax; l.c++) { for (l.r = 0; l.r < dim.rowMax; l.r++) { if (unionSquares[l.r][l.c]) { uCount++; } } } out->write_int(uCount); for (l.c = 0; l.c < dim.colMax; l.c++) { for (l.r = 0; l.r < dim.rowMax; l.r++) { if (unionSquares[l.r][l.c]) { l.write(out); unionSquares[l.r][l.c]->write(out); } } } } // For one room. int World::get_write_length(const RoomIndex &) { return W_ROOM_ROW_MAX * W_ROOM_COL_MAX * sizeof(char); } // Write one room. void World::write(OutStreamP outStream,const RoomIndex &idx) { if (!outStream->alive()) { return; } Loc l; for (l.r = idx.down * W_ROOM_ROW_MAX; l.r < (idx.down + 1) * W_ROOM_ROW_MAX; l.r++) { for (l.c = idx.across * W_ROOM_COL_MAX; l.c < (idx.across + 1) * W_ROOM_COL_MAX; l.c++) { if (inside(l)) { outStream->write_char(map[l.r][l.c]); } else { // There's not really any point in writing this. outStream->write_char(Woutside); } } } } // Read one room void World::read(InStreamP in,const RoomIndex &idx) { if (!in->alive()) { return; } Loc l; for (l.r = idx.down * W_ROOM_ROW_MAX; l.r < (idx.down + 1) * W_ROOM_ROW_MAX; l.r++) { for (l.c = idx.across * W_ROOM_COL_MAX; l.c < (idx.across + 1) * W_ROOM_COL_MAX; l.c++) { if (inside(l)) { map[l.r][l.c] = in->read_char(); } else { // just throw it away. in->read_char(); } } } } void World::reset(const Dim *clearDim,InStreamP inStream) { // Don't reuse the nonce because may have old packets // uniqueGen = 0; justReset = True; assert(locator); clear_everything(); // Create empty world of given dimensions. if (clearDim) { rooms_dim_size_update(clearDim); return; } // Read from stream if supplied if (inStream) { read_from_stream(inStream); return; } // Read from file if set. if (specialMap == MAP_NONE && worldFile) { if (!read_from_file(worldFile)) { // Failed to read, go back to default. clear_everything(); // Don't bother to try again. Utils::freeif(worldFile); } } // Create regular world if worldFile is NULL or if world can't be read // in. if (!worldFile) { // Before blueprints if (specialMap == FLAG) { // hack so FireDemon can get flags. objectDimMax.colMax = OBJECT_BIG_COL_MAX; objectDimMax.rowMax = OBJECT_BIG_ROW_MAX; } else { // back to default. objectDimMax.colMax = OBJECT_COL_MAX; objectDimMax.rowMax = OBJECT_ROW_MAX; } // Generate new map. if (blueprints) { delete blueprints; } blueprints = new Blueprints(this,map,objectDimMax,specialMap); assert(blueprints); if (mapPrint) { blueprints->print(); } blueprints->room_boundries(); if (useMovers) { add_movers(); } blueprints->extra_walls(); blueprints->up_downs(); if (specialMap != ZIG_ZAG) {// Can't have people teleporting around, now. add_doors(); } add_posters(); // add_vert_movers(); } choose_theme(); } void World::choose_theme() { // Weigh choice of themes by number of backgrounds in each theme. int themeWeights[W_THEME_NUM]; for (int n = 0; n < W_THEME_NUM; n++) { themeWeights[n] = themes[n].backgroundsNum; } // X11 has no themes, but still maintains these indices if it is acting as // a server. themeIndex = Utils::weighted_choose(W_THEME_NUM,themeWeights); backgroundIndex = Utils::choose(themes[themeIndex].backgroundsNum); // Want index into xdata.backgroundSurfaces backgroundIndex = themes[themeIndex].backgroundIndices[backgroundIndex]; outsideIndex = Utils::choose(themes[themeIndex].outsidesNum); outsideIndex = themes[themeIndex].outsideIndices[outsideIndex]; } MoverP World::lookup(const MoverId &id) { if (id.index != MoverId::INVALID && id.index >= 0 && id.index < moversNum && movers[id.index]->get_mover_id() == id) { return movers[id.index]; } return NULL; } void World::clock() { justReset = False; for (int n = 0; n < moversNum; n++) { movers[n]->clock(); } // for n } Pos World::empty_rect(const Size &s) { int count = 0; // Guard against infinite loop. while (True) { // Bail out, semi-gracefully. if (count >= EMPTY_TRIES_MAX) { cerr << "World::empty_rect failed after " << EMPTY_TRIES_MAX << " iterations" << endl; Pos ret; // (0,0) return ret; } count++; Pos ret; ret.x = Utils::choose(size.width); ret.y = Utils::choose(size.height); Area area(AR_RECT,ret,s); if (open(area,True,False,True)) { return ret; } } } Pos World::empty_touching_rect(const Size &s) { int count = 0; // Guard against infinite loop. while (True) { // Bail out, semi-gracefully. if (count >= EMPTY_TRIES_MAX) { cerr << "World::empty_touching_rect failed after " << EMPTY_TRIES_MAX << " iterations" << endl; Pos ret; // (0,0) return ret; } count++; Pos ret; ret.x = Utils::choose(size.width); ret.y = WSQUARE_HEIGHT * Utils::choose(dim.rowMax) - s.height; Area area(AR_RECT,ret,s); Touching touching; Hanging hanging; MoverP dummy; compute_touching_hanging(touching,hanging,dummy,area); if (open(area,True,False,True) && (touching == CO_dn)) { return ret; } } } Pos World::empty_accessible_rect(const Size &s) { int count = 0; // Guard against infinite loop. while (True) { // Bail out, semi-gracefully. if (count >= EMPTY_TRIES_MAX) { cerr << "World::empty_accessible_rect failed after " << EMPTY_TRIES_MAX << " iterations" << endl; Pos ret; // (0,0) return ret; } count++; // Choose the room. RoomIndex r(Utils::choose(rooms.downMax), Utils::choose(rooms.acrossMax)); Pos ret; if (empty_accessible_rect_one(ret,s,r)) { return ret; } } } Pos World::empty_accessible_rect(const Size &s,const RoomIndex &r) { int count = 0; // Guard against infinite loop. while (True) { // Bail out, semi-gracefully. if (count >= EMPTY_TRIES_MAX) { cerr << "World::empty_accessible_rect<2> failed after " << EMPTY_TRIES_MAX << " iterations" << endl; Pos ret; // (0,0) return ret; } count++; Pos ret; // Use supplied room. if (empty_accessible_rect_one(ret,s,r)) { return ret; } } } Boolean World::empty_accessible_rect_one(Pos &ret,const Size &s, const RoomIndex &r) { Blueprints *bl = blueprints; assert(bl); // Start at right edge of room. ret.x = r.across * W_ROOM_COL_MAX * WSQUARE_WIDTH; // Choose x coord so that on right or left side of ladder, whereever // there is a floor. if (bl->blocked_right(r)) { if (bl->blocked_left(r)) { // Bail out, no suitable rect. return False; } else { // Left half of room. ret.x += Utils::choose(WSQUARE_WIDTH * bl->get_middle_col(r.across)); } } else { if (bl->blocked_left(r)) { // Right half of room. ret.x += WSQUARE_WIDTH * (bl->get_middle_col(r.across) + objectDimMax.colMax) + Utils::choose(WSQUARE_WIDTH * (W_ROOM_COL_MAX - bl->get_middle_col(r.across) - objectDimMax.colMax)); } else { if (Utils::coin_flip()) { // Left half of room. ret.x += Utils::choose(WSQUARE_WIDTH * bl->get_middle_col(r.across)); } else { // Right half of room. ret.x += WSQUARE_WIDTH * (bl->get_middle_col(r.across) + objectDimMax.colMax) + Utils::choose(WSQUARE_WIDTH * (W_ROOM_COL_MAX - bl->get_middle_col(r.across) - objectDimMax.colMax)); } } } // y coord so that area is just touching middle floor. ret.y = (r.down * W_ROOM_ROW_MAX + bl->get_middle_row(r.down)) * WSQUARE_HEIGHT - s.height; Area area(AR_RECT,ret,s); if (open(area,True,False,True)) { return True; } else { return False; } } Boolean World::empty_box(Loc &loc,const Dim &d,Boolean laddersClosed, Boolean postersClosed,Boolean doorsClosed) { int count = 0; // Guard against infinite loop. while (True) { if (count >= EMPTY_TRIES_MAX) { return False; } count++; loc.c = Utils::choose(dim.colMax); loc.r = Utils::choose(dim.rowMax); Box box(loc,d); if (open(box,laddersClosed,postersClosed,doorsClosed)) { return True; } } } Boolean World::empty_touching_box(Loc &loc,const Dim &d, Boolean laddersClosed, Boolean postersClosed, Boolean doorsClosed) { int count = 0; // Guard against infinite loop. while (True) { if (count >= EMPTY_TRIES_MAX) { return False; } count++; loc.c = Utils::choose(dim.colMax); loc.r = Utils::choose(dim.rowMax); Area area(AR_RECT,loc,d); Touching touching; Hanging hanging; MoverP dummy; compute_touching_hanging(touching,hanging,dummy,area); if (open(area,laddersClosed,postersClosed,doorsClosed) && touching == CO_dn) { return True; } } } void World::rooms_dim_size_update(const Dim *newDim) { if (newDim) { // Use newDim to set dim, rooms, and size. dim = *newDim; // Round rooms up. rooms.downMax = (int)ceil((float)dim.rowMax / (float)W_ROOM_ROW_MAX); rooms.acrossMax = (int)ceil((float)dim.colMax / (float)W_ROOM_COL_MAX); } else { // Use this.rooms to set dim and size. dim.colMax = rooms.acrossMax * W_ROOM_COL_MAX; dim.rowMax = rooms.downMax * W_ROOM_ROW_MAX; } size.width = dim.colMax * WSQUARE_WIDTH; size.height = dim.rowMax * WSQUARE_HEIGHT; } void World::th_helper( // Return values. Touching &touching, Hanging &hanging, MoverP &touchingMover, const TouchingListItem &tItem, const TouchingListItem &eItem, // For getting potential Movers. Boolean r_c, // row/column const Area &area, int coord,int length, int wsquare_length, Touching iftouching, Corner ifsmall, Corner iflarge) { // Compute touching. Boolean touchingOk = False; // Get list of all movers touching area on the given side. MoverP touchingMovers[MOVERS_MAX]; int touchingMoversNum = 0; int n; for (n = 0; n < eItem.num; n++) { const Loc &loc = eItem.list[n]; if (inside(loc) && unionSquares[loc.r][loc.c] && unionSquares[loc.r][loc.c]->type == UN_MOVER && area.touches(unionSquares[loc.r][loc.c]->mSquare.mover->get_area(), iftouching)) { Boolean alreadyFound = False; for (int m = 0; m < touchingMoversNum; m++) { if (touchingMovers[m] == unionSquares[loc.r][loc.c]->mSquare.mover) { alreadyFound = True; continue; } } if (!alreadyFound) { touchingMovers[touchingMoversNum] = unionSquares[loc.r][loc.c]->mSquare.mover; touching = iftouching; // May be set again in hanging. touchingMover = touchingMovers[touchingMoversNum]; touchingOk = True; touchingMoversNum++; } } } // Is a mover touching. if (!touchingOk) { // Check all Locs. for (n = 0; n < tItem.num; n++) { if (!open(tItem.list[n])) { touching = iftouching; touchingMover = NULL; touchingOk = True; continue; } } } // Only compute hanging if touching. if (touchingOk) { int hangCutoff = (int)floor(HANGING_PERCENT * length); int n; for (n = 0; n < tItem.num; n++) { if (!open(tItem.list[n])) { break; } } // tItem.list[n] is first blocked wsquare. if ((n < tItem.num) && ((r_c ? tItem.list[n].r:tItem.list[n].c) * wsquare_length - coord >= hangCutoff)) { hanging.corner = ifsmall; hanging.loc = tItem.list[n]; hanging.type = Hanging::LOC; } for (n = tItem.num - 1; n >= 0; n--) { if (! open(tItem.list[n])) { break; } } // tItem.list[n] is last blocked wsquare. if ((n >= 0) && (coord + length - (r_c ? tItem.list[n].r + 1: tItem.list[n].c + 1) * wsquare_length >= hangCutoff)) { hanging.corner = iflarge; hanging.loc = tItem.list[n]; hanging.type = Hanging::LOC; } // Really should sort movers by their areas and then treat them similar // to the Locs, but fuck it. for (n = 0; n < touchingMoversNum; n++) { Pos moverPos; Size moverSize; const Area &moverArea = touchingMovers[n]->get_area(); moverArea.get_rect(moverPos,moverSize); if ((r_c ? moverPos.y : moverPos.x) - coord >= hangCutoff) { hanging.corner = ifsmall; hanging.moverId = touchingMovers[n]->get_mover_id(); hanging.type = Hanging::MOVER; // touchingMover = touchingMovers[n]; } if (coord + length - (r_c ? moverPos.y + moverSize.height : moverPos.x + moverSize.width) >= hangCutoff) { hanging.corner = iflarge; hanging.moverId = touchingMovers[n]->get_mover_id(); hanging.type = Hanging::MOVER; // touchingMover = touchingMovers[n]; } } } // touchingOk } Boolean World::check_wrap_around(Size &offset,const Area &area) { // Only consider wrapping-around if the slot is completely open // except for being partially outside the world. So, pass in False // for the outsideClosed argument of World::open(). */ if (!open(area,False,False,False,False)) { return False; } Pos arPos; Size arSize; area.get_rect(arPos,arSize); // Can't have any objects that are as big as the world. assert(arSize.width < size.width); // Go off left side of screen, appear flush against right wall. if (arPos.x < 0) { offset.width = size.width - arSize.width - arPos.x; offset.height = 0; // Only do wrap-around if the new position is open. Area newArea = area + offset; return open(newArea); } // Go off right side of screen, appear flush against left wall. if (arPos.x + arSize.width > size.width) { offset.width = -arPos.x; offset.height = 0; // Only do wrap-around if the new position is open. Area newArea = area + offset; return open(newArea); } return False; } Boolean World::open_iter(Area &area,int &changed,Dir dir) { int offOne = 0; Loc list[AR_WSQUARES_MAX]; int nsquares; area.wsquares(list,nsquares); for (int n = 0; n < nsquares; n++) { // Support for up_downs. if (inside(list[n]) && (map[list[n].r][list[n].c] == WupDown) && ((dir == CO_R) || (dir == CO_L))) { return False; } // Avoid closed block. if (! open(list[n])) { int temp = area.avoid_wsquare_dir(list[n],dir); offOne = Utils::maximum(offOne,temp); } // Look at mover corresponding to moverSquare at list[n]. if (inside(list[n])) { UnionSquare *uSquare = unionSquares[list[n].r][list[n].c]; if (uSquare && uSquare->type == UN_MOVER) { const Area &moverArea = uSquare->mSquare.mover->get_area(); if (moverArea.overlap(area)) { // Movers always shift you up. if (dir != CO_UP) { return False; } int temp = area.avoid_area_dir(uSquare->mSquare.mover->get_area(),dir); offOne = max(offOne,temp); } } } } if (offOne == 0) { return True; } changed += offOne; area.shift(open_size(offOne,dir)); return False; } Size World::open_size(int offset,Dir dir) { Size ret; ret.width = ret.height = 0; switch (dir) { case CO_R: ret.width = offset; break; case CO_DN: ret.height = offset; break; case CO_L: ret.width = -offset; break; case CO_UP: ret.height = -offset; break; }; return ret; } Size World::open_size(int offset1,Dir dir1,int offset2,Dir dir2) { Size ret; ret.width = ret.height = 0; switch (dir1) { case CO_R: ret.width = offset1; break; case CO_DN: ret.height = offset1; break; case CO_L: ret.width = -offset1; break; case CO_UP: ret.height = -offset1; break; }; switch (dir2) { case CO_R: ret.width = offset2; break; case CO_DN: ret.height = offset2; break; case CO_L: ret.width = -offset2; break; case CO_UP: ret.height = -offset2; break; }; return ret; } Boolean World::open_try_dirs(Size &offset,const Area &area,const Dir dirs[4], int dirsNum) { if (!dirsNum) { return False; } // Trying several directions simultaneously. Area areas[4]; int offsets[4]; for (int n = 0; n < dirsNum; n++) { areas[n] = area; offsets[n] = 0; } for (int iter = 0; iter < OPEN_ITERATIONS; iter++) { Boolean oks[4]; int n; for (n = 0; n < dirsNum; n++) { oks[n] = open_iter(areas[n],offsets[n],dirs[n]); } int offsetOkMin = Utils::minimum(offsets,oks,dirsNum); // Return after first iteration that gives an open area in one of the directions. for (n = 0; n < dirsNum; n++) { if (oks[n] && (offsets[n] == offsetOkMin)) { offset = open_size(offsets[n],dirs[n]); return True; } } } return False; } Boolean World::open_try_corners(Size &offset,const Area &area) { Dir corners[4]; corners[0] = CO_DN_R; corners[1] = CO_DN_L; corners[2] = CO_UP_L; corners[3] = CO_UP_R; // Try all four corners. for (int cNum = 0; cNum < 4; cNum++) { Loc outer[3]; Loc inner; if (area.corner_offset(offset,outer,inner,corners[cNum])) { // Check that all 3 outer wsquares are closed. Boolean validCorner = True; for (int n = 0; n < 3; n++) { if (open(outer[n])) { validCorner = False; break; } } // Check that the inner wsquare of the corner is open. if (!open(inner)) { validCorner = False; } if (validCorner) { // offset is already set. return True; } } } return False; } Boolean World::open_try_diagonals(Size &offset,const Area &area) { Dir dirs[4]; dirs[0] = CO_R; dirs[1] = CO_DN; dirs[2] = CO_L; dirs[3] = CO_UP; int offsets[4]; int n; for (n = 0; n < 4; n++) { offsets[n] = 0; } Loc list[AR_WSQUARES_MAX]; int nsquares; area.wsquares(list,nsquares); for (n = 0; n < nsquares; n++) { if (! open(list[n])) { for (int dIndex = 0; dIndex < 4; dIndex++) { int dOffset = area.avoid_wsquare_dir(list[n],dirs[dIndex]); offsets[dIndex] = Utils::maximum(dOffset,offsets[dIndex]); } } // Look at mover corresponding to moverSquare at list[n]. if (inside(list[n])) { UnionSquare *uSquare = unionSquares[list[n].r][list[n].c]; if (uSquare && uSquare->type == UN_MOVER) { const Area &moverArea = uSquare->mSquare.mover->get_area(); if (moverArea.overlap(area)) { for (int dIndex = 0; dIndex < 4; dIndex++) { int dOffset = area.avoid_area_dir(uSquare->mSquare.mover->get_area(), dirs[dIndex]); offsets[dIndex] = Utils::maximum(dOffset,offsets[dIndex]); } } } } } // if for (int dIndex = 0; dIndex < 4; dIndex++) { Area areaTest = area; offset = open_size(offsets[dIndex],dirs[dIndex], offsets[(dIndex + 1) % 4],dirs[(dIndex + 1) % 4]); areaTest.shift(offset); if (open(areaTest)) { return True; } } return False; } void World::add_posters() { // Put in posters. for (int n = 0; n < POSTERS_ACTUAL_PERCENT * dim.rowMax * dim.colMax; n++) { // Choose poster appropriate for the current theme. int p = Utils::choose(themes[themeIndex].postersNum); p = themes[themeIndex].posterIndices[p]; Loc pLoc; if (!empty_box(pLoc,posterDims[p],True,True,True)) { break; } Loc loc; for (loc.c = pLoc.c; loc.c < pLoc.c + posterDims[p].colMax; loc.c++) { for (loc.r = pLoc.r; loc.r < pLoc.r + posterDims[p].rowMax; loc.r++) { if (! unionSquares[loc.r][loc.c]) { unionSquares[loc.r][loc.c] = new UnionSquare; } assert(unionSquares[loc.r][loc.c]); unionSquares[loc.r][loc.c]->type = UN_POSTER; unionSquares[loc.r][loc.c]->pSquare.poster = p; unionSquares[loc.r][loc.c]->pSquare.loc.c = loc.c - pLoc.c; unionSquares[loc.r][loc.c]->pSquare.loc.r = loc.r - pLoc.r; } } } } void World::add_doors() { // Put in doors. for (int n = 0; n < DOORS_ACTUAL_PERCENT * rooms.downMax * rooms.acrossMax; n++) { Loc loc1; Loc loc2; Dim doorDim(2,1); // Hardwired size of doors. if (!empty_touching_box(loc1,doorDim,True,True,True)) { break; } if (!empty_touching_box(loc2,doorDim,True,True,True)) { break; } // Don't want doors on top of each other. Area area1(AR_RECT,loc1,doorDim); Area area2(AR_RECT,loc2,doorDim); if (area1.overlap(area2)) { break; } // Lower half of doors. Loc loc1d = loc1.move(1,0); Loc loc2d = loc2.move(1,0); // We actually create 4 doors, two pairs. Each apparent door is // actually two DoorSquares. // Cross connect two new doors at loc1 and loc2. unionSquares[loc1.r][loc1.c] = new UnionSquare; assert(unionSquares[loc1.r][loc1.c]); unionSquares[loc1.r][loc1.c]->type = UN_DOOR; unionSquares[loc1.r][loc1.c]->dSquare.dest = loc2; unionSquares[loc1.r][loc1.c]->dSquare.topBottom = W_DOOR_TOP; unionSquares[loc2.r][loc2.c] = new UnionSquare; assert(unionSquares[loc2.r][loc2.c]); unionSquares[loc2.r][loc2.c]->type = UN_DOOR; unionSquares[loc2.r][loc2.c]->dSquare.dest = loc1; unionSquares[loc2.r][loc2.c]->dSquare.topBottom = W_DOOR_TOP; // Cross connect two new doors at loc1d and loc2d. unionSquares[loc1d.r][loc1d.c] = new UnionSquare; assert(unionSquares[loc1d.r][loc1d.c]); unionSquares[loc1d.r][loc1d.c]->type = UN_DOOR; unionSquares[loc1d.r][loc1d.c]->dSquare.dest = loc2d; unionSquares[loc1d.r][loc1d.c]->dSquare.topBottom = W_DOOR_BOTTOM; unionSquares[loc2d.r][loc2d.c] = new UnionSquare; assert(unionSquares[loc2d.r][loc2d.c]); unionSquares[loc2d.r][loc2d.c]->type = UN_DOOR; unionSquares[loc2d.r][loc2d.c]->dSquare.dest = loc1d; unionSquares[loc2d.r][loc2d.c]->dSquare.topBottom = W_DOOR_BOTTOM; } // for } void World::add_movers() { assert(moversNum <= MOVERS_MAX && blueprints); // Movers shouldn't be too big. assert(objectDimMax.colMax * WSQUARE_WIDTH >= moverSize.width); // Number of movers to create. const int moversActual = Utils::choose((int)(rooms.acrossMax * rooms.downMax * MOVERS_PERCENT) + 1); for (int n = 0; n < moversActual && moversNum < MOVERS_MAX; n++) { Boolean which = Utils::coin_flip(); Boolean ok = False; int tries = 0; while (!ok && tries < MOVER_TRIES) { // Decide between horizontal and vertical mover. if (which) { ok = add_vert_mover(); } else { ok = add_horiz_mover(); } tries++; } } } Boolean World::add_horiz_mover() { assert(moversNum <= MOVERS_MAX && blueprints); // Movers shouldn't be too big. assert(objectDimMax.colMax * WSQUARE_WIDTH >= moverSize.width); // loc will walk right or left, filling in track. Loc loc; loc.r = Utils::choose(dim.rowMax); loc.c = Utils::choose(dim.colMax); // Check space surrounding initial choice for minimum track length. int delta = Utils::coin_flip() ? 1 : -1; // right or left. Loc check; for (check.c = loc.c - (delta == -1 ? MOVERS_HORIZ_MIN_TRACK : 1) * objectDimMax.colMax; check.c < loc.c + (delta == 1 ? MOVERS_HORIZ_MIN_TRACK + 1 : 2) * objectDimMax.colMax; // mover track plus blank space check.c++) { for (check.r = loc.r - objectDimMax.rowMax; check.r <= loc.r + objectDimMax.rowMax; check.r++) { if (!open(check,True,True,True)) { return False; } } } // Create mover movers[moversNum] = new Mover; assert(movers[moversNum]); // Start at the right edge of the mover and go left if (delta == -1) { loc.c += objectDimMax.colMax - 1; } // else we are at the left edge going right. // minimum and maximum values of left side of mover, inclusive int mLeft = loc.c; int mRight = loc.c; // Go right/left until hit a wall or decide to stop. // Must do at least MOVERS_HORIZ_MIN_TRACK multiples of the mover width. int n = 0; Boolean ok = True; while (ok && (n <= MOVERS_HORIZ_MIN_TRACK * objectDimMax.colMax || Utils::choose(MOVERS_HORIZ_TRACK_LENGTH))) { // Check if we can put a mover at loc. for (check.c = loc.c; check.c != loc.c + delta * objectDimMax.colMax; check.c += delta) { for (check.r = loc.r - objectDimMax.rowMax; check.r <= loc.r + objectDimMax.rowMax; check.r++) { if (!open(check,True,True,True)) { ok = False; // Our above check of the surrounding space was not correct. assert(n >= MOVERS_HORIZ_MIN_TRACK * objectDimMax.colMax); } if (!ok) { break; } } if (!ok) { break; } } // Add mover track at loc. if (ok) { // Add a mover square. assert(inside(loc) && (map[loc.r][loc.c] == Wempty ) && !unionSquares[loc.r][loc.c]); unionSquares[loc.r][loc.c] = new UnionSquare; assert(unionSquares[loc.r][loc.c]); unionSquares[loc.r][loc.c]->type = UN_MOVER; unionSquares[loc.r][loc.c]->mSquare.mover = movers[moversNum]; unionSquares[loc.r][loc.c]->mSquare.orientation = OR_HORIZ; mLeft = Utils::minimum(mLeft,loc.c); mRight = Utils::maximum(mRight,loc.c); n++; loc.c += delta; } } // while ok // Check again that we got enough track. assert(mRight - mLeft + 1 >= MOVERS_HORIZ_MIN_TRACK * objectDimMax.colMax); // initialize mover. // Choose X position somewhere in range of mover squares. int rangeX = (mRight - mLeft + 1 - objectDimMax.colMax) * WSQUARE_WIDTH + 1; assert(rangeX > 0); // Or not enough room. int startX = Utils::choose(rangeX) + mLeft * WSQUARE_WIDTH; Pos pos(startX,loc.r * WSQUARE_HEIGHT); Area area(AR_RECT,pos,moverSize); Size vel; vel.set(Utils::coin_flip() ? W_MOVER_SPEED : -W_MOVER_SPEED,0); // initialize the mover. MoverId moverId; moverId.index = moversNum; moverId.unique = uniqueGen++; movers[moversNum]->init(this,locator,area,vel,moverId); // Dummy mover object, so mover gets redrawn. PhysMoverP p = new PhysMover(this,locator,movers[moversNum]); assert(p); locator->add(p); movers[moversNum]->set_phys_mover_id(p->get_id()); moversNum++; return True; } // What an ugly function. I should redo this. Boolean World::add_vert_mover() { assert(moversNum <= MOVERS_MAX && blueprints); // Movers shouldn't be too big. assert(objectDimMax.colMax * WSQUARE_WIDTH >= moverSize.width); // Choose a random room, check if its middle is a ladder, if so, change // the ladder to a bunch of mover squares and create a new mover. // Else continue. Loc init; int m; // holder for the down/across value of the room chosen. m = Utils::choose(rooms.acrossMax); init.c = m * W_ROOM_COL_MAX + blueprints->get_middle_col(m); m = Utils::choose(rooms.downMax); init.r = m * W_ROOM_ROW_MAX + blueprints->get_middle_row(m); // Found a ladder, create a new mover and set all the ladder wsquares to be // MoverSquares pointing to the new mover. if (!(map[init.r][init.c] == Wladder && map[init.r][init.c+1] == Wladder)) { return False; } assert(!unionSquares[init.r][init.c]); // All wsquares between mTop and mBottom, exclusive, are part of the // mover. int mTop = init.r; int mBottom = init.r; movers[moversNum] = new Mover; assert(movers[moversNum]); // delta is -1, then 1. Go up, then down.. for (int delta = -1; delta <= 1; delta += 2) { Loc loc = init; // To avoid hitting init twice, skip it when going down. if (delta == 1) { loc.r++; } // Scan up/down depending on value of delta. Boolean bothInside = inside(loc) && inside(loc.r,loc.c+1); Boolean bothLadders = bothInside && (map[loc.r][loc.c] == Wladder && map[loc.r][loc.c+1] == Wladder); Boolean aboveInside = inside(loc.r-1,loc.c) && inside(loc.r-1,loc.c+1); Boolean aboveMoverSq = aboveInside && map[loc.r-1][loc.c] == Wempty && map[loc.r-1][loc.c+1] == Wempty && unionSquares[loc.r-1][loc.c] && unionSquares[loc.r-1][loc.c+1] && unionSquares[loc.r-1][loc.c]->type == UN_MOVER && unionSquares[loc.r-1][loc.c+1]->type == UN_MOVER; Boolean bothWalls = bothInside && (map[loc.r][loc.c] == Wwall && map[loc.r][loc.c+1] == Wwall); // This shit is all so that movers can stick down one square into floor. // CAREFUL!! Crappy code duplicated below. while (bothLadders || // normal case (delta == 1 && aboveMoverSq && bothWalls) ) { // sticking down one // Scan to right. for (; loc.c < init.c + objectDimMax.colMax; loc.c++) { // Add a mover square. assert(inside(loc) && (map[loc.r][loc.c] == Wladder || map[loc.r][loc.c] == Wwall) && !unionSquares[loc.r][loc.c]); unionSquares[loc.r][loc.c] = new UnionSquare; assert(unionSquares[loc.r][loc.c]); unionSquares[loc.r][loc.c]->type = UN_MOVER; unionSquares[loc.r][loc.c]->mSquare.mover = movers[moversNum]; unionSquares[loc.r][loc.c]->mSquare.orientation = OR_VERT; // Still leave overlap of wall and moversquare. if (map[loc.r][loc.c] != Wwall) { map[loc.r][loc.c] = Wempty; } mTop = Utils::minimum(mTop,loc.r); mBottom = Utils::maximum(mBottom,loc.r + 1); } loc.c = init.c; // When up at the top. // Remove annoying ladder sticking up. if (delta == -1 && map[loc.r - objectDimMax.rowMax - 1][loc.c] != Wladder && // Just to be sure we don't kill of some unionSquares. !unionSquares[loc.r - objectDimMax.rowMax - 1][loc.c]) { int locTop = loc.r - objectDimMax.rowMax; for (loc.r--; loc.r >= locTop; loc.r--) { for (loc.c = init.c; loc.c < init.c + objectDimMax.colMax; loc.c++) { map[loc.r][loc.c] = Wempty; } } break; } // annoying ladder sticking up. loc.c = init.c; loc.r += delta; bothInside = inside(loc) && inside(loc.r,loc.c+1); bothLadders = bothInside && (map[loc.r][loc.c] == Wladder && map[loc.r][loc.c+1] == Wladder); aboveInside = inside(loc.r-1,loc.c) && inside(loc.r-1,loc.c+1); aboveMoverSq = aboveInside && map[loc.r-1][loc.c] == Wempty && map[loc.r-1][loc.c+1] == Wempty && unionSquares[loc.r-1][loc.c] && unionSquares[loc.r-1][loc.c+1] && unionSquares[loc.r-1][loc.c]->type == UN_MOVER && unionSquares[loc.r-1][loc.c+1]->type == UN_MOVER; bothWalls = bothInside && (map[loc.r][loc.c] == Wwall && map[loc.r][loc.c+1] == Wwall); // This shit is all so that movers can stick down one square into // the floor. } // while } // for delta // Note: loc is now at the left side of the former ladder, // one square beneath it. assert(mTop < mBottom); // Or no squares were added. // Choose Y position somewhere in range of mover squares. int rangeY = (mBottom - mTop) * WSQUARE_HEIGHT - moverSize.height + 1; assert(rangeY > 0); // or movers are more than one square high. int startY = Utils::choose(rangeY) + mTop * WSQUARE_HEIGHT; Pos pos(init.c * WSQUARE_WIDTH,startY); Area area(AR_RECT,pos,moverSize); Size vel; vel.set(0,Utils::coin_flip() ? W_MOVER_SPEED : -W_MOVER_SPEED); // initialize the mover. MoverId moverId; moverId.index = moversNum; moverId.unique = uniqueGen++; movers[moversNum]->init(this,locator,area,vel,moverId); // Dummy mover object, so mover gets redrawn. PhysMoverP p = new PhysMover(this,locator,movers[moversNum]); assert(p); locator->add(p); movers[moversNum]->set_phys_mover_id(p->get_id()); moversNum++; return True; } void World::delete_movers() { // The locator should take care of killing off the PhysMovers // in Locator::reset(). int n; for (n = 0; n < moversNum; n++) { delete movers[n]; movers[n] = NULL; } moversNum = 0; } void World::mover_list_add(MoverP list[],int &nItems,const Loc &loc) { if (!inside(loc)) { return; } UnionSquare *uSquare = unionSquares[loc.r][loc.c]; if (uSquare && uSquare->type == UN_MOVER) { MoverP mover = uSquare->mSquare.mover; // Only add movers that overlap loc. const Area &area = mover->get_area(); if (!area.overlap(loc)) { return; } // Check for duplicates. for (int n = 0; n < nItems; n++) { if (list[n] == mover) { return; } } // Add to list. assert(nItems < MOVERS_MAX); list[nItems] = mover; nItems++; } } Boolean World::useMovers = True; // On by default.