entities without definitions in script will be removed from game; it is now possible...
[k8-i-v-a-n.git] / src / game / level.cpp
blob06ad3dfcb7f98d40d5c66bbbf8e6d4c27be18db7
1 /*
3 * Iter Vehemens ad Necem (IVAN)
4 * Copyright (C) Timo Kiviluoto
5 * Released under the GNU General
6 * Public License
8 * See LICENSING which should be included
9 * along with this file for more details
13 /* Compiled through levelset.cpp */
15 #define FORBIDDEN (1)
16 #define ON_POSSIBLE_ROUTE (2)
17 #define STILL_ON_POSSIBLE_ROUTE (4)
18 #define PREFERRED (8)
19 #define ICE_TERRAIN (16)
20 #define STONE_TERRAIN (32)
23 feuLong level::NextExplosionID = 1;
25 node*** node::NodeMap;
26 int node::RequiredWalkability;
27 ccharacter* node::SpecialMover;
28 v2 node::To;
29 uChar** node::WalkabilityMap;
30 int node::XSize, node::YSize;
31 nodequeue* node::NodeQueue;
34 // ////////////////////////////////////////////////////////////////////////// //
35 void maze::CreateMaze () {
36 InitializeMaze();
37 CarveMaze(2, 2);
38 StripMazeHusk();
42 void maze::InitializeMaze () {
43 std::fill(MazeVector.begin(), MazeVector.end(), false);
44 for (unsigned x = 0; x < MazeXSize; ++x) {
45 MazeVector[x] = true;
46 MazeVector[(MazeYSize-1)*MazeXSize+x] = true;
48 for (unsigned y = 0; y < MazeYSize; ++y) {
49 MazeVector[y*MazeXSize] = true;
50 MazeVector[y*MazeXSize+MazeXSize-1] = true;
55 void maze::CarveMaze (int x, int y) {
56 MazeVector[y*MazeXSize+x] = true;
57 const unsigned d = RAND();
58 for (unsigned i = 0; i < 4; ++i) {
59 const int dirs[] = { 1, -1, 0, 0 };
60 const int dx = dirs[(i+d+0)%4];
61 const int dy = dirs[(i+d+2)%4];
62 const int x1 = x+dx, y1 = y+dy;
63 const int x2 = x1+dx, y2 = y1+dy;
64 if(!MazeVector[y1*MazeXSize+x1] && !MazeVector[y2*MazeXSize+x2]) {
65 MazeVector[y1*MazeXSize+x1] = true;
66 CarveMaze(x2, y2);
72 void maze::StripMazeHusk () {
73 for (unsigned y1 = 2; y1 < MazeYSize-2; ++y1) {
74 for (unsigned x1 = 2; x1 < MazeXSize-2; ++x1) {
75 MazeKernel.push_back(MazeVector[y1*MazeXSize+x1]);
81 // ////////////////////////////////////////////////////////////////////////// //
82 level::level() : Room(1, static_cast<room*>(0)), GlobalRainLiquid(0), SunLightEmitation(0), AmbientLuminance(0), SquareStack(0), NightAmbientLuminance(0) {}
83 void level::SetRoom (int I, room *What) { Room[I] = What; }
84 void level::AddToAttachQueue (v2 Pos) { AttachQueue.push_back(Pos); }
87 level::~level () {
88 for (feuLong c = 0; c < XSizeTimesYSize; ++c) delete NodeMap[0][c];
89 for (feuLong c = 0; c < Room.size(); ++c) delete Room[c];
90 delete [] NodeMap;
91 delete [] WalkabilityMap;
92 delete GlobalRainLiquid;
93 delete [] SquareStack;
94 game::SetGlobalRainLiquid(0);
98 #define CHECK(x, y) (!(FlagMap[x][y]&(ON_POSSIBLE_ROUTE|FORBIDDEN)))
99 #define CALL_EXPAND(x, y) do { \
100 ExpandPossibleRoute(x, y, TargetX, TargetY, XMode); \
101 if (FlagMap[TargetX][TargetY]&ON_POSSIBLE_ROUTE) return; \
102 } while(0)
104 void level::ExpandPossibleRoute (int OrigoX, int OrigoY, int TargetX, int TargetY, truth XMode) {
105 FlagMap[OrigoX][OrigoY] |= ON_POSSIBLE_ROUTE;
106 if (XMode) {
107 if (TargetX < OrigoX && CHECK(OrigoX-1, OrigoY)) CALL_EXPAND(OrigoX-1, OrigoY);
108 if (TargetX > OrigoX && CHECK(OrigoX+1, OrigoY)) CALL_EXPAND(OrigoX+1, OrigoY);
109 if (TargetY < OrigoY && CHECK(OrigoX, OrigoY-1)) CALL_EXPAND(OrigoX, OrigoY-1);
110 if (TargetY > OrigoY && CHECK(OrigoX, OrigoY+1)) CALL_EXPAND(OrigoX, OrigoY+1);
111 if (TargetX <= OrigoX && OrigoX < XSize-2 && CHECK(OrigoX+1, OrigoY)) CALL_EXPAND(OrigoX+1, OrigoY);
112 if (TargetX >= OrigoX && OrigoX > 1 && CHECK(OrigoX-1, OrigoY)) CALL_EXPAND(OrigoX-1, OrigoY);
113 if (TargetY <= OrigoY && OrigoY < YSize-2 && CHECK(OrigoX, OrigoY+1)) CALL_EXPAND(OrigoX, OrigoY+1);
114 if (TargetY >= OrigoY && OrigoY > 1 && CHECK(OrigoX, OrigoY-1)) CALL_EXPAND(OrigoX, OrigoY-1);
115 } else {
116 if (TargetY < OrigoY && CHECK(OrigoX, OrigoY-1)) CALL_EXPAND(OrigoX, OrigoY-1);
117 if (TargetY > OrigoY && CHECK(OrigoX, OrigoY+1)) CALL_EXPAND(OrigoX, OrigoY+1);
118 if (TargetX < OrigoX && CHECK(OrigoX-1, OrigoY)) CALL_EXPAND(OrigoX-1, OrigoY);
119 if (TargetX > OrigoX && CHECK(OrigoX+1, OrigoY)) CALL_EXPAND(OrigoX+1, OrigoY);
120 if (TargetY <= OrigoY && OrigoY < YSize-2 && CHECK(OrigoX, OrigoY+1)) CALL_EXPAND(OrigoX, OrigoY+1);
121 if (TargetY >= OrigoY && OrigoY > 1 && CHECK(OrigoX, OrigoY-1)) CALL_EXPAND(OrigoX, OrigoY-1);
122 if (TargetX <= OrigoX && OrigoX < XSize-2 && CHECK(OrigoX+1, OrigoY)) CALL_EXPAND(OrigoX+1, OrigoY);
123 if (TargetX >= OrigoX && OrigoX > 1 && CHECK(OrigoX-1, OrigoY)) CALL_EXPAND(OrigoX-1, OrigoY);
126 #undef CHECK
127 #undef CALL_EXPAND
130 #define CHECK(x, y) ((FlagMap[x][y]&(STILL_ON_POSSIBLE_ROUTE|ON_POSSIBLE_ROUTE)) == ON_POSSIBLE_ROUTE)
131 #define CALL_EXPAND(x, y) do { \
132 ExpandStillPossibleRoute(x, y, TargetX, TargetY, XMode); \
133 if (FlagMap[TargetX][TargetY]&STILL_ON_POSSIBLE_ROUTE) return; \
134 } while(0)
136 void level::ExpandStillPossibleRoute (int OrigoX, int OrigoY, int TargetX, int TargetY, truth XMode) {
137 FlagMap[OrigoX][OrigoY] |= STILL_ON_POSSIBLE_ROUTE;
138 if (XMode) {
139 if (TargetX < OrigoX && CHECK(OrigoX-1, OrigoY)) CALL_EXPAND(OrigoX-1, OrigoY);
140 if (TargetX > OrigoX && CHECK(OrigoX+1, OrigoY)) CALL_EXPAND(OrigoX+1, OrigoY);
141 if (TargetY < OrigoY && CHECK(OrigoX, OrigoY-1)) CALL_EXPAND(OrigoX, OrigoY-1);
142 if (TargetY > OrigoY && CHECK(OrigoX, OrigoY+1)) CALL_EXPAND(OrigoX, OrigoY+1);
143 if (TargetX <= OrigoX && OrigoX < XSize-2 && CHECK(OrigoX+1, OrigoY)) CALL_EXPAND(OrigoX+1, OrigoY);
144 if (TargetX >= OrigoX && OrigoX > 1 && CHECK(OrigoX-1, OrigoY)) CALL_EXPAND(OrigoX-1, OrigoY);
145 if (TargetY <= OrigoY && OrigoY < YSize-2 && CHECK(OrigoX, OrigoY+1)) CALL_EXPAND(OrigoX, OrigoY+1);
146 if (TargetY >= OrigoY && OrigoY > 1 && CHECK(OrigoX, OrigoY-1)) CALL_EXPAND(OrigoX, OrigoY-1);
147 } else {
148 if (TargetY < OrigoY && CHECK(OrigoX, OrigoY-1)) CALL_EXPAND(OrigoX, OrigoY-1);
149 if (TargetY > OrigoY && CHECK(OrigoX, OrigoY+1)) CALL_EXPAND(OrigoX, OrigoY+1);
150 if (TargetX < OrigoX && CHECK(OrigoX-1, OrigoY)) CALL_EXPAND(OrigoX-1, OrigoY);
151 if (TargetX > OrigoX && CHECK(OrigoX+1, OrigoY)) CALL_EXPAND(OrigoX+1, OrigoY);
152 if (TargetY <= OrigoY && OrigoY < YSize-2 && CHECK(OrigoX, OrigoY+1)) CALL_EXPAND(OrigoX, OrigoY+1);
153 if (TargetY >= OrigoY && OrigoY > 1 && CHECK(OrigoX, OrigoY-1)) CALL_EXPAND(OrigoX, OrigoY-1);
154 if (TargetX <= OrigoX && OrigoX < XSize-2 && CHECK(OrigoX+1, OrigoY)) CALL_EXPAND(OrigoX+1, OrigoY);
155 if (TargetX >= OrigoX && OrigoX > 1 && CHECK(OrigoX-1, OrigoY)) CALL_EXPAND(OrigoX-1, OrigoY);
158 #undef CHECK
159 #undef CALL_EXPAND
162 void level::GenerateTunnel (int FromX, int FromY, int TargetX, int TargetY, truth XMode) {
163 FlagMap[FromX][FromY] |= ON_POSSIBLE_ROUTE;
164 ExpandPossibleRoute(FromX, FromY, TargetX, TargetY, XMode);
166 const contentscript<glterrain> *GTerrain = LevelScript->GetTunnelSquare()->GetGTerrain();
167 const contentscript<olterrain> *OTerrain = LevelScript->GetTunnelSquare()->GetOTerrain();
169 if (FlagMap[TargetX][TargetY]&ON_POSSIBLE_ROUTE) {
170 for (int x = 0; x < XSize; ++x) {
171 for (int y = 0; y < YSize; ++y) {
172 if ((FlagMap[x][y]&(ON_POSSIBLE_ROUTE|PREFERRED)) == ON_POSSIBLE_ROUTE &&
173 !(x == FromX && y == FromY) && !(x == TargetX && y == TargetY)) {
174 FlagMap[x][y] &= ~ON_POSSIBLE_ROUTE;
175 FlagMap[FromX][FromY] |= STILL_ON_POSSIBLE_ROUTE;
176 ExpandStillPossibleRoute(FromX, FromY, TargetX, TargetY, XMode);
178 if (!(FlagMap[TargetX][TargetY]&STILL_ON_POSSIBLE_ROUTE)) {
179 FlagMap[x][y] |= ON_POSSIBLE_ROUTE|PREFERRED;
180 Map[x][y]->ChangeGLTerrain(GTerrain->Instantiate());
181 Map[x][y]->ChangeOLTerrain(OTerrain->Instantiate());
184 for (int X = 0; X < XSize; ++X) {
185 for (int Y = 0; Y < YSize; ++Y) {
186 FlagMap[X][Y] &= ~STILL_ON_POSSIBLE_ROUTE;
193 for (int x = 1; x < XSize-1; ++x) {
194 for (int y = 1; y < YSize-1; ++y) {
195 FlagMap[x][y] &= ~ON_POSSIBLE_ROUTE;
201 truth level::Generate (int Index) {
202 game::BusyAnimation();
203 Initialize(LevelScript->GetSize()->X, LevelScript->GetSize()->Y);
204 game::SetCurrentArea(this);
205 game::SetCurrentLevel(this);
206 Alloc2D(NodeMap, XSize, YSize);
207 Alloc2D(WalkabilityMap, XSize, YSize);
208 Map = reinterpret_cast<lsquare***>(area::Map);
209 SquareStack = new lsquare * [XSizeTimesYSize];
211 if ((Index == 0 && GetDungeon()->GetIndex() == NEW_ATTNAM) ||
212 (Index == 0 && GetDungeon()->GetIndex() == ATTNAM) ||
213 (Index == 0 && GetDungeon()->GetIndex() == MUNTUO)) {
214 NightAmbientLuminance = MakeRGB24(95, 95, 95);
217 for (int x = 0; x < XSize; ++x) {
218 for (int y = 0; y < YSize; ++y) {
219 Map[x][y] = new lsquare(this, v2(x, y));
220 NodeMap[x][y] = new node(x, y, Map[x][y]);
224 for (int x = 0; x < XSize; ++x) {
225 for (int y = 0; y < YSize; ++y) {
226 Map[x][y]->CalculateNeighbourLSquares();
230 int Type = LevelScript->GetType() ? *LevelScript->GetType() : 0;
232 if (Type == 0) return GenerateDungeon(Index);
233 if (Type == DESERT) return GenerateDesert();
234 if (Type == JUNGLE) return GenerateJungle();
235 if (Type == STEPPE) return GenerateSteppe();
236 if (Type == LEAFY_FOREST) return GenerateLeafyForest();
237 if (Type == EVERGREEN_FOREST) return GenerateEvergreenForest();
238 if (Type == TUNDRA) return GenerateTundra();
239 if (Type == GLACIER) return GenerateGlacier();
241 ABORT("You are a terrorist. Please stop creating wterrains that are stupid.");
242 return false;
246 void level::ApplyLSquareScript (const squarescript *Script) {
247 const interval *ScriptTimes = Script->GetTimes();
248 int Times = ScriptTimes ? ScriptTimes->Randomize() : 1;
250 for (int c = 0; c < Times; ++c) {
251 v2 Pos;
253 if (Script->GetPosition()->GetRandom()) {
254 Pos = GetRandomSquare(0, Script->GetPosition()->GetFlags(), Script->GetPosition()->GetBorders());
255 } else {
256 Pos = Script->GetPosition()->GetVector();
258 Map[Pos.X][Pos.Y]->ApplyScript(Script, 0);
263 void level::AttachPos (int WhatX, int WhatY) {
264 int PosX = 1+RAND()%(XSize-2);
265 int PosY = 1+RAND()%(YSize-2);
267 while(!(FlagMap[PosX][PosY]&PREFERRED)) {
268 PosX = 1+RAND()%(XSize-2);
269 PosY = 1+RAND()%(YSize-2);
271 FlagMap[WhatX][WhatY] &= ~FORBIDDEN;
272 FlagMap[WhatX][WhatY] |= PREFERRED;
273 GenerateTunnel(WhatX, WhatY, PosX, PosY, RAND()&1);
274 FlagMap[WhatX][WhatY] |= FORBIDDEN;
275 FlagMap[WhatX][WhatY] &= ~PREFERRED;
279 void level::CreateItems (int Amount) {
280 if (Amount) {
281 sLong MinPrice = *LevelScript->GetItemMinPriceBase() + *LevelScript->GetItemMinPriceDelta()*Index;
283 for (int x = 0; x < Amount; ++x) {
284 v2 Pos = GetRandomSquare();
285 item *Item = protosystem::BalancedCreateItem(this, MinPrice, MAX_PRICE, ANY_CATEGORY, 0, IGNORE_BROKEN_PRICE);
287 Item->CalculateEnchantment();
288 Map[Pos.X][Pos.Y]->Stack->AddItem(Item);
289 Item->SpecialGenerationHandler();
295 truth level::MakeRoom (const roomscript *RoomScript) {
296 game::BusyAnimation();
298 v2 Pos = RoomScript->GetPos()->Randomize();
299 v2 Size = RoomScript->GetSize()->Randomize();
301 if (Pos.X+Size.X > XSize-2) return false;
302 if (Pos.Y+Size.Y > YSize-2) return false;
304 for (int x = Pos.X-1; x <= Pos.X+Size.X; ++x) {
305 for (int y = Pos.Y-1; y <= Pos.Y+Size.Y; ++y) {
306 if (FlagMap[x][y]&FORBIDDEN || FlagMap[x][y]&PREFERRED) return false;
310 auto roomProto = protocontainer<room>::GetProto(*RoomScript->GetType());
311 if (!roomProto) ABORT("No prototype #%d for room!", *RoomScript->GetType());
312 room *RoomClass = roomProto->Spawn();
314 RoomClass->SetScript(RoomScript);
315 RoomClass->SetPos(Pos);
316 RoomClass->SetSize(Size);
317 RoomClass->SetFlags(*RoomScript->GetFlags());
318 AddRoom(RoomClass);
319 RoomClass->SetDivineMaster(*RoomScript->GetDivineMaster());
320 game::BusyAnimation();
322 std::vector<v2> OKForDoor, Inside, Border;
324 GenerateRectangularRoom(OKForDoor, Inside, Border, RoomScript, RoomClass, Pos, Size);
325 game::BusyAnimation();
327 if (*RoomScript->GenerateFountains() && !(RAND()%10)) {
328 GetLSquare(Inside[RAND()%Inside.size()])->ChangeOLTerrain(fountain::Spawn());
331 // Ward, which gets generated as per fountain activation
332 if (*RoomScript->GenerateWards() && !(RAND()%5)) {
333 GetLSquare(Inside[RAND()%Inside.size()])->ChangeOLTerrain(ward::Spawn());
336 if (*RoomScript->AltarPossible() && !(RAND()%5)) {
337 int Owner;
338 const fearray<int> *am = RoomScript->GetAllowedDivineMasters();
340 if (!am || am->Size == 0) {
341 Owner = 1+RAND()%GODS;
342 } else {
343 Owner = am->GetRandomElement();
344 if (Owner < 1 || Owner > GODS) ABORT("Your god is a bad god!");
347 GetLSquare(Inside[RAND()%Inside.size()])->ChangeOLTerrain(altar::Spawn(Owner));
348 game::GetGod(Owner)->SignalRandomAltarGeneration(Inside);
349 RoomClass->SetDivineMaster(Owner);
352 if (*RoomScript->GenerateTunnel() && !Door.empty()) {
353 game::BusyAnimation();
354 v2 OutsideDoorPos = Door[RAND()%Door.size()]; // An other room
356 if (OKForDoor.empty()) ABORT("The Doors - You are strange.");
358 v2 InsideDoorPos = OKForDoor[RAND()%OKForDoor.size()]; // this door
359 olterrain *Door = RoomScript->GetDoorSquare()->GetOTerrain()->Instantiate(); //Bug! Wrong room!
361 if (Door && !(RAND()%5) && *RoomScript->AllowLockedDoors()) {
362 if (*RoomScript->AllowBoobyTrappedDoors() && !(RAND()%5)) Door->CreateBoobyTrap();
363 Door->Lock();
366 Map[OutsideDoorPos.X][OutsideDoorPos.Y]->ChangeLTerrain(RoomScript->GetDoorSquare()->GetGTerrain()->Instantiate(), Door);
367 Map[OutsideDoorPos.X][OutsideDoorPos.Y]->Clean();
368 FlagMap[OutsideDoorPos.X][OutsideDoorPos.Y] &= ~FORBIDDEN;
369 FlagMap[OutsideDoorPos.X][OutsideDoorPos.Y] |= PREFERRED;
370 FlagMap[InsideDoorPos.X][InsideDoorPos.Y] &= ~FORBIDDEN;
371 FlagMap[InsideDoorPos.X][InsideDoorPos.Y] |= PREFERRED;
372 Door = RoomScript->GetDoorSquare()->GetOTerrain()->Instantiate();
374 if (Door && !(RAND()%5) && *RoomScript->AllowLockedDoors()) {
375 if (*RoomScript->AllowBoobyTrappedDoors() && !(RAND()%5)) Door->CreateBoobyTrap();
376 Door->Lock();
379 Map[InsideDoorPos.X][InsideDoorPos.Y]->ChangeLTerrain(RoomScript->GetDoorSquare()->GetGTerrain()->Instantiate(), Door);
380 Map[InsideDoorPos.X][InsideDoorPos.Y]->Clean();
381 GenerateTunnel(InsideDoorPos.X, InsideDoorPos.Y, OutsideDoorPos.X, OutsideDoorPos.Y, RAND()&1);
382 FlagMap[OutsideDoorPos.X][OutsideDoorPos.Y] |= FORBIDDEN;
383 FlagMap[OutsideDoorPos.X][OutsideDoorPos.Y] &= ~PREFERRED;
384 FlagMap[InsideDoorPos.X][InsideDoorPos.Y] |= FORBIDDEN;
385 FlagMap[InsideDoorPos.X][InsideDoorPos.Y] &= ~PREFERRED;
388 if (*RoomScript->GenerateDoor()) {
389 game::BusyAnimation();
390 v2 DoorPos;
392 if (OKForDoor.empty()) ABORT("The Doors - This thing has been broken.");
394 DoorPos = OKForDoor[RAND()%OKForDoor.size()];
395 Door.push_back(DoorPos);
397 if (!*RoomScript->GenerateTunnel()) {
398 Map[DoorPos.X][DoorPos.Y]->ChangeLTerrain(RoomScript->GetDoorSquare()->GetGTerrain()->Instantiate(),
399 RoomScript->GetDoorSquare()->GetOTerrain()->Instantiate());
400 Map[DoorPos.X][DoorPos.Y]->Clean();
404 // Make second door for a maze room. If the room has even-numbered dimension, then it will be a rectangular room with two doors...
405 if (*RoomScript->GenerateDoor() && (*RoomScript->GetShape() == MAZE_ROOM)) {
406 game::BusyAnimation();
407 v2 DoorPos;
409 if (!OKForDoor.empty()) {
410 DoorPos = OKForDoor[OKForDoor.size()-1];
411 Door.push_back(DoorPos);
413 if (!*RoomScript->GenerateTunnel()) {
414 Map[DoorPos.X][DoorPos.Y]->ChangeLTerrain(RoomScript->GetDoorSquare()->GetGTerrain()->Instantiate(),
415 RoomScript->GetDoorSquare()->GetOTerrain()->Instantiate());
416 Map[DoorPos.X][DoorPos.Y]->Clean();
421 const charactercontentmap *CharacterMap = RoomScript->GetCharacterMap();
423 if (CharacterMap) {
424 v2 CharPos(Pos + *CharacterMap->GetPos());
425 const contentscript<character> *CharacterScript;
427 for (int x = 0; x < CharacterMap->GetSize()->X; ++x) {
428 game::BusyAnimation();
429 for (int y = 0; y < CharacterMap->GetSize()->Y; ++y) {
430 if (IsValidScript(CharacterScript = CharacterMap->GetContentScript(x, y))) {
431 character *Char = CharacterScript->Instantiate();
433 if (Char) {
434 Char->SetGenerationDanger(Difficulty);
435 if (!Char->GetTeam()) Char->SetTeam(game::GetTeam(*LevelScript->GetTeamDefault()));
436 if (CharacterScript->GetFlags()&IS_LEADER) Char->GetTeam()->SetLeader(Char);
437 Char->PutTo(CharPos+v2(x, y));
438 Char->CreateHomeData();
439 if (CharacterScript->GetFlags()&IS_MASTER) RoomClass->SetMasterID(Char->GetID());
446 const itemcontentmap *ItemMap = RoomScript->GetItemMap();
448 if (ItemMap) {
449 v2 ItemPos(Pos + *ItemMap->GetPos());
450 const fearray<contentscript<item> >* ItemScript;
452 for (int x = 0; x < ItemMap->GetSize()->X; ++x) {
453 game::BusyAnimation();
454 for (int y = 0; y < ItemMap->GetSize()->Y; ++y) {
455 if (IsValidScript(ItemScript = ItemMap->GetContentScript(x, y))) {
456 for (uInt c1 = 0; c1 < ItemScript->Size; ++c1) {
457 const interval* TimesPtr = ItemScript->Data[c1].GetTimes();
458 int Times = TimesPtr ? TimesPtr->Randomize() : 1;
460 for (int c2 = 0; c2 < Times; ++c2) {
461 item *Item = ItemScript->Data[c1].Instantiate();
463 if (Item) {
464 int SquarePosition = ItemScript->Data[c1].GetSquarePosition();
466 if (SquarePosition != CENTER) Item->SignalSquarePositionChange(SquarePosition);
467 Map[ItemPos.X+x][ItemPos.Y+y]->GetStack()->AddItem(Item);
468 Item->SpecialGenerationHandler();
477 const glterraincontentmap *GTerrainMap = RoomScript->GetGTerrainMap();
479 if (GTerrainMap) {
480 v2 GTerrainPos(Pos + *GTerrainMap->GetPos());
481 const contentscript<glterrain> *GTerrainScript;
483 for (int x = 0; x < GTerrainMap->GetSize()->X; ++x) {
484 game::BusyAnimation();
485 for (int y = 0; y < GTerrainMap->GetSize()->Y; ++y) {
486 if (IsValidScript(GTerrainScript = GTerrainMap->GetContentScript(x, y))) {
487 lsquare *Square = Map[GTerrainPos.X+x][GTerrainPos.Y+y];
489 Square->ChangeGLTerrain(GTerrainScript->Instantiate());
490 if (GTerrainScript->IsInside()) {
491 if (*GTerrainScript->IsInside()) Square->Flags |= INSIDE; else Square->Flags &= ~INSIDE;
498 const olterraincontentmap *OTerrainMap = RoomScript->GetOTerrainMap();
500 if (OTerrainMap) {
501 v2 OTerrainPos(Pos + *OTerrainMap->GetPos());
502 const contentscript<olterrain> *OTerrainScript;
504 for (int x = 0; x < OTerrainMap->GetSize()->X; ++x) {
505 game::BusyAnimation();
506 for (int y = 0; y < OTerrainMap->GetSize()->Y; ++y) {
507 if (IsValidScript(OTerrainScript = OTerrainMap->GetContentScript(x, y))) {
508 olterrain *Terrain = OTerrainScript->Instantiate();
510 if (Terrain->AcceptsOffers()) {
511 //FIXME: make IsAltar()? for now only altars can accept offers
512 if (RoomClass->GetDivineMaster()) {
513 //if (Terrain->GetConfig() != RoomClass->GetDivineMaster()) ABORT("Random altar in room with DivineMaster!");
514 if (Terrain->GetConfig() != RoomClass->GetDivineMaster()) {
515 // force altar type
516 fprintf(stderr, "forced altar!\n");
517 delete Terrain;
518 Terrain = altar::Spawn(RoomClass->GetDivineMaster());
520 } else {
521 // no DivineMaster yet, assign it
522 fprintf(stderr, "spawned altar in room w/o divine master, assigning %d\n", Terrain->GetConfig());
523 RoomClass->SetDivineMaster(Terrain->GetConfig());
526 Map[OTerrainPos.X+x][OTerrainPos.Y+y]->ChangeOLTerrain(Terrain);
532 const std::list<squarescript> Square = RoomScript->GetSquare();
534 for (std::list<squarescript>::const_iterator i = Square.begin(); i != Square.end(); ++i) {
535 game::BusyAnimation();
536 const squarescript *Script = &*i;
537 const interval *ScriptTimes = Script->GetTimes();
538 int Times = ScriptTimes ? ScriptTimes->Randomize() : 1;
540 for (int t = 0; t < Times; ++t) {
541 v2 SquarePos;
543 if (Script->GetPosition()->GetRandom()) {
544 const rect *ScriptBorders = Script->GetPosition()->GetBorders();
545 rect Borders = ScriptBorders ? *ScriptBorders+Pos : rect(Pos, Pos+Size-v2(1, 1));
547 SquarePos = GetRandomSquare(0, Script->GetPosition()->GetFlags(), &Borders);
548 } else {
549 SquarePos = Pos+Script->GetPosition()->GetVector();
551 Map[SquarePos.X][SquarePos.Y]->ApplyScript(Script, RoomClass);
555 return true;
559 truth level::GenerateLanterns (int X, int Y, int SquarePos) const {
560 if (!(RAND()%7)) {
561 lantern *Lantern = lantern::Spawn();
562 Lantern->SignalSquarePositionChange(SquarePos);
563 Map[X][Y]->GetStack()->AddItem(Lantern);
564 return true;
566 return false;
570 void level::CreateRoomSquare (glterrain *GLTerrain, olterrain* OLTerrain, int X, int Y, int Room, int Flags) const {
571 Map[X][Y]->ChangeLTerrain(GLTerrain, OLTerrain);
572 FlagMap[X][Y] |= FORBIDDEN;
573 Map[X][Y]->SetRoomIndex(Room);
574 Map[X][Y]->AddFlags(Flags);
578 void level::GenerateMonsters () {
579 if (*LevelScript->GenerateMonsters() &&
580 game::GetTeam(MONSTER_TEAM)->GetEnabledMembers() < IdealPopulation &&
581 (MonsterGenerationInterval <= 1 || !RAND_N(MonsterGenerationInterval))) {
582 GenerateNewMonsters(1);
583 ++MonsterGenerationInterval;
588 void level::Save (outputfile &SaveFile) const {
589 area::Save(SaveFile);
590 SaveFile << Room << GlobalRainLiquid << GlobalRainSpeed;
592 for (int x = 0; x < XSize; ++x) {
593 for (int y = 0; y < YSize; ++y) {
594 Map[x][y]->Save(SaveFile);
598 SaveFile << Door << LevelMessage << IdealPopulation << MonsterGenerationInterval << Difficulty;
599 SaveFile << SunLightEmitation << SunLightDirection << AmbientLuminance << NightAmbientLuminance;
603 void level::Load (inputfile &SaveFile) {
604 game::SetIsGenerating(true);
605 game::SetIsLoading(true);
606 area::Load(SaveFile);
607 Map = reinterpret_cast<lsquare ***>(area::Map);
608 SaveFile >> Room;
609 GlobalRainLiquid = static_cast<liquid *>(ReadType(material *, SaveFile));
610 SaveFile >> GlobalRainSpeed;
612 if (GlobalRainLiquid) GlobalRainLiquid->SetVolumeNoSignals(0);
614 game::SetGlobalRainLiquid(GlobalRainLiquid);
615 game::SetGlobalRainSpeed(GlobalRainSpeed);
617 for (int x = 0; x < XSize; ++x) {
618 for (int y = 0; y < YSize; ++y) {
619 Map[x][y] = new lsquare(this, v2(x, y));
623 for (int x = 0; x < XSize; ++x) {
624 for (int y = 0; y < YSize; ++y) {
625 game::SetSquareInLoad(Map[x][y]);
626 Map[x][y]->Load(SaveFile);
627 Map[x][y]->CalculateNeighbourLSquares();
631 SaveFile >> Door >> LevelMessage >> IdealPopulation >> MonsterGenerationInterval >> Difficulty;
632 SaveFile >> SunLightEmitation >> SunLightDirection >> AmbientLuminance >> NightAmbientLuminance;
633 Alloc2D(NodeMap, XSize, YSize);
634 Alloc2D(WalkabilityMap, XSize, YSize);
636 for (int x = 0; x < XSize; ++x) {
637 for (int y = 0; y < YSize; ++y) {
638 if (!Map[x][y]->IsInside()) Map[x][y]->AmbientLuminance = AmbientLuminance;
639 NodeMap[x][y] = new node(x, y, Map[x][y]);
640 WalkabilityMap[x][y] = Map[x][y]->GetTheoreticalWalkability();
641 Map[x][y]->CalculateGroundBorderPartners();
642 Map[x][y]->CalculateOverBorderPartners();
646 SquareStack = new lsquare * [XSizeTimesYSize];
647 game::SetIsLoading(false);
648 game::SetIsGenerating(false);
652 void level::FiatLux () {
653 for (int x = 0; x < XSize; ++x) {
654 for (int y = 0; y < YSize; ++y) {
655 Map[x][y]->CalculateEmitation();
656 Map[x][y]->Emitate();
657 Map[x][y]->CalculateLuminance();
660 CheckSunLight();
664 void level::GenerateNewMonsters (int HowMany, truth ConsiderPlayer) {
665 for (int c1 = 0; c1 < HowMany; ++c1) {
666 v2 Pos;
667 character *Char = protosystem::BalancedCreateMonster(this);
668 // gum solution
669 Char->CalculateEnchantments();
670 for (int c2 = 0; c2 < 30; ++c2) {
671 Pos = GetRandomSquare(Char);
672 if (Pos == ERROR_V2) break;
673 lsquare *Square = GetLSquare(Pos);
674 if ((!Square->GetRoomIndex() || !Square->GetRoom()->DontGenerateMonsters()) &&
675 (!ConsiderPlayer || (Pos-PLAYER->GetPos()).GetManhattanLength() > 6)) break;
677 if (Pos != ERROR_V2) {
678 Char->PutTo(Pos);
679 Char->SetGenerationDanger(Difficulty);
680 Char->SignalGeneration();
681 Char->SignalNaturalGeneration();
682 ivantime Time;
683 game::GetTime(Time);
684 int Modifier = Time.Day-EDIT_ATTRIBUTE_DAY_MIN;
685 if (Modifier > 0) Char->EditAllAttributes(Modifier>>EDIT_ATTRIBUTE_DAY_SHIFT);
686 } else {
687 delete Char;
688 //k8:delete Char;
689 //Char->SendToHell(); // equipment
695 /* Example of the usage: GetRandomSquare() gives out a random walkable square */
696 v2 level::GetRandomSquare (ccharacter* Char, int Flags, const rect* Borders) const {
697 rect LocalBorder;
698 lsquare *LSquare;
700 if (Borders) {
701 LocalBorder = *Borders;
702 Borders = &LocalBorder;
703 LimitRef(LocalBorder.X1, 0, XSize-1);
704 LimitRef(LocalBorder.X2, 0, XSize-1);
705 LimitRef(LocalBorder.Y1, 0, YSize-1);
706 LimitRef(LocalBorder.Y2, 0, YSize-1);
708 for (int c = 0;; ++c) {
709 v2 Pos;
711 if (c == 50) Char = 0;
712 if (c == 500) return ERROR_V2;
713 if (Borders) {
714 Pos.X = Borders->X1+RAND()%(Borders->X2-Borders->X1+1);
715 Pos.Y = Borders->Y1+RAND()%(Borders->Y2-Borders->Y1+1);
716 } else {
717 Pos.X = 1+RAND()%(XSize-2);
718 Pos.Y = 1+RAND()%(YSize-2);
720 LSquare = Map[Pos.X][Pos.Y];
721 if (((Char ? Char->CanMoveOn(LSquare) : (LSquare->GetWalkability()&WALK)) != !(Flags&NOT_WALKABLE)) ||
722 ((Char ? Char->IsFreeForMe(LSquare) : !LSquare->GetCharacter()) != !(Flags&HAS_CHARACTER)) ||
723 (Flags&ATTACHABLE && FlagMap[Pos.X][Pos.Y]&FORBIDDEN) ||
724 (Flags&HAS_NO_OTERRAIN && LSquare->GetOTerrain()))
726 continue;
728 int RoomFlags = Flags&(IN_ROOM|NOT_IN_ROOM);
729 if ((RoomFlags == IN_ROOM && !LSquare->GetRoomIndex()) || (RoomFlags == NOT_IN_ROOM && LSquare->GetRoomIndex())) continue;
730 return Pos;
735 void level::ParticleTrail (v2 StartPos, v2 EndPos) {
736 if (StartPos.X != EndPos.X && StartPos.Y != EndPos.Y) {
737 ABORT("666th rule of thermodynamics - Particles don't move the way you want them to move.");
742 truth level::IsOnGround () const {
743 return *LevelScript->IsOnGround();
747 truth level::EarthquakesAffectTunnels () const {
748 return *LevelScript->EarthquakesAffectTunnels();
752 int level::GetLOSModifier () const {
753 return *LevelScript->GetLOSModifier();
757 void level::AddRoom (room *NewRoom) {
758 NewRoom->SetIndex(Room.size());
759 Room.push_back(NewRoom);
763 room *level::GetRoom (int I) const {
764 if (!I) ABORT("Access to room zero denied!");
765 return Room[I];
769 void level::Explosion (character *Terrorist, cfestring &DeathMsg, v2 Pos, int Strength, truth HurtNeutrals) {
770 static int StrengthLimit[6] = { 500, 250, 100, 50, 25, 10 };
771 int Size = 6;
773 for (int c = 0; c < 6; ++c) if (Strength >= StrengthLimit[c]) { Size = c; break; }
774 PlayerHurt.resize(PlayerHurt.size()+1);
775 explosion *Exp = new explosion;
776 Exp->Terrorist = Terrorist;
777 Exp->DeathMsg = DeathMsg;
778 Exp->Pos = Pos;
779 Exp->ID = NextExplosionID++;
780 Exp->Strength = Strength;
781 Exp->RadiusSquare = (8-Size)*(8-Size);
782 Exp->Size = Size;
783 Exp->HurtNeutrals = HurtNeutrals;
784 ExplosionQueue.push_back(Exp);
786 if (ExplosionQueue.size() == 1) {
787 unsigned int Explosions = 0;
789 while (Explosions != ExplosionQueue.size()) {
790 unsigned int c;
792 for (c = Explosions; c != ExplosionQueue.size(); c = TriggerExplosions(c)) ;
793 unsigned int NewExplosions = c;
794 for (c = Explosions; c < NewExplosions; ++c) {
795 if (PlayerHurt[c] && PLAYER->IsEnabled()) {
796 PLAYER->GetHitByExplosion(ExplosionQueue[c], ExplosionQueue[c]->Strength/((PLAYER->GetPos()-ExplosionQueue[c]->Pos).GetLengthSquare()+1));
799 Explosions = NewExplosions;
801 for (unsigned int c = 0; c < ExplosionQueue.size(); ++c) delete ExplosionQueue[c];
802 ExplosionQueue.clear();
803 PlayerHurt.clear();
804 NextExplosionID = 1;
805 for (int x = 0; x < XSize; ++x) {
806 for (int y = 0; y < YSize; ++y) {
807 Map[x][y]->LastExplosionID = 0;
814 truth level::DrawExplosion (const explosion *Explosion) const {
815 static v2 StrengthPicPos[7] = { v2(176, 176), v2(0, 144), v2(256, 32), v2(144, 32), v2(64, 32), v2(16, 32),v2(0, 32) };
816 v2 BPos = game::CalculateScreenCoordinates(Explosion->Pos)-v2((6-Explosion->Size)<<4, (6-Explosion->Size)<<4);
817 v2 SizeVect(16+((6-Explosion->Size)<<5), 16+((6-Explosion->Size)<<5));
818 v2 OldSizeVect = SizeVect;
819 v2 PicPos = StrengthPicPos[Explosion->Size];
821 if (BPos.X < 0) {
822 if (BPos.X+SizeVect.X <= 0) return false;
823 PicPos.X -= BPos.X;
824 SizeVect.X += BPos.X;
825 BPos.X = 0;
828 if (BPos.Y < 0) {
829 if (BPos.Y+SizeVect.Y <= 0) return false;
830 PicPos.Y -= BPos.Y;
831 SizeVect.Y += BPos.Y;
832 BPos.Y = 0;
835 if (BPos.X >= RES.X || BPos.Y >= RES.Y) return false;
836 if (BPos.X+SizeVect.X > RES.X) SizeVect.X = RES.X-BPos.X;
837 if (BPos.Y+SizeVect.Y > RES.Y) SizeVect.Y = RES.Y-BPos.Y;
839 int Flags = RAND()&7;
840 blitdata BlitData = {
842 { PicPos.X, PicPos.Y },
843 { 0, 0 },
844 { SizeVect.X, SizeVect.Y },
845 { 0 },
846 TRANSPARENT_COLOR,
850 if (!Flags || SizeVect != OldSizeVect) {
851 BlitData.Bitmap = DOUBLE_BUFFER;
852 BlitData.Dest = BPos;
853 BlitData.Luminance = ivanconfig::GetContrastLuminance();
854 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
855 } else {
856 /* Cache these */
857 bitmap ExplosionPic(SizeVect);
858 ExplosionPic.ActivateFastFlag();
859 BlitData.Bitmap = &ExplosionPic;
860 BlitData.Flags = Flags;
861 igraph::GetSymbolGraphic()->NormalBlit(BlitData);
862 BlitData.Bitmap = DOUBLE_BUFFER;
863 BlitData.Dest = BPos;
864 BlitData.Src.X = BlitData.Src.Y = 0;
865 BlitData.Luminance = ivanconfig::GetContrastLuminance();
866 ExplosionPic.LuminanceMaskedBlit(BlitData);
869 return true;
873 struct explosioncontroller {
874 static truth Handler (int x, int y) {
875 lsquare *Square = Map[x][y];
876 Square->GetHitByExplosion(CurrentExplosion);
877 return Square->IsFlyable();
879 static lsquare ***Map;
880 static explosion *CurrentExplosion;
884 lsquare ***explosioncontroller::Map;
885 explosion *explosioncontroller::CurrentExplosion;
888 int level::TriggerExplosions (int MinIndex) {
889 int LastExplosion = ExplosionQueue.size();
890 int NotSeen = 0;
891 int c;
893 for (c = MinIndex; c < LastExplosion; ++c) {
894 int EmitChange = Min(50+ExplosionQueue[c]->Strength, 255);
895 GetLSquare(ExplosionQueue[c]->Pos)->SetTemporaryEmitation(MakeRGB24(EmitChange, EmitChange, EmitChange));
896 if (!GetSquare(ExplosionQueue[c]->Pos)->CanBeSeenByPlayer(true)) ++NotSeen;
898 if (NotSeen) {
899 if (NotSeen == 1) ADD_MESSAGE("You hear an explosion."); else ADD_MESSAGE("You hear explosions.");
901 game::DrawEverythingNoBlit();
902 truth Drawn = false;
903 for (c = MinIndex; c < LastExplosion; ++c) if (DrawExplosion(ExplosionQueue[c])) Drawn = true;
904 if (Drawn) {
905 graphics::BlitDBToScreen();
906 game::GetCurrentArea()->SendNewDrawRequest();
907 clock_t StartTime = clock();
908 while(clock()-StartTime < 0.3*CLOCKS_PER_SEC);
910 for (c = MinIndex; c < LastExplosion; ++c) {
911 explosion *Explosion = ExplosionQueue[c];
912 int Radius = 8-Explosion->Size;
913 game::SetPlayerWasHurtByExplosion(false);
914 explosioncontroller::Map = Map;
915 explosioncontroller::CurrentExplosion = Explosion;
916 rect Rect;
917 femath::CalculateEnvironmentRectangle(Rect, GetBorder(), Explosion->Pos, Radius);
918 for (int x = Rect.X1; x <= Rect.X2; ++x) {
919 mapmath<explosioncontroller>::DoLine(Explosion->Pos.X, Explosion->Pos.Y, x, Rect.Y1);
920 mapmath<explosioncontroller>::DoLine(Explosion->Pos.X, Explosion->Pos.Y, x, Rect.Y2);
922 for (int y = Rect.Y1+1; y < Rect.Y2; ++y) {
923 mapmath<explosioncontroller>::DoLine(Explosion->Pos.X, Explosion->Pos.Y, Rect.X1, y);
924 mapmath<explosioncontroller>::DoLine(Explosion->Pos.X, Explosion->Pos.Y, Rect.X2, y);
926 PlayerHurt[c] = game::PlayerWasHurtByExplosion();
927 if (GetLSquare(Explosion->Pos)->IsFlyable()) GetLSquare(Explosion->Pos)->AddSmoke(gas::Spawn(SMOKE, 1000));
929 for (c = MinIndex; c < LastExplosion; ++c) GetLSquare(ExplosionQueue[c]->Pos)->SetTemporaryEmitation(0);
930 return LastExplosion;
934 truth level::CollectCreatures (charactervector &CharacterArray, character* Leader, truth AllowHostiles) {
935 if (!AllowHostiles) {
936 for (int c = 0; c < game::GetTeams(); ++c) {
937 if (Leader->GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) {
938 for (std::list<character*>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
939 if ((*i)->IsEnabled() && Leader->CanBeSeenBy(*i) && Leader->SquareUnderCanBeSeenBy(*i, true) && (*i)->CanFollow()) {
940 ADD_MESSAGE("You can't escape when there are hostile creatures nearby.");
941 return false;
948 truth TakeAll = true;
950 for (int c = 0; c < game::GetTeams(); ++c) {
951 if (game::GetTeam(c)->GetEnabledMembers() && Leader->GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) {
952 TakeAll = false;
953 break;
957 for (int c = 0; c < game::GetTeams(); ++c) {
958 if (game::GetTeam(c) == Leader->GetTeam() || Leader->GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) {
959 for (std::list<character*>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
960 if ((*i)->IsEnabled() && *i != Leader &&
961 (TakeAll || (Leader->CanBeSeenBy(*i) && Leader->SquareUnderCanBeSeenBy(*i, true))) &&
962 (*i)->CanFollow() && (*i)->GetCommandFlags()&FOLLOW_LEADER)
964 if ((*i)->GetAction() && (*i)->GetAction()->IsVoluntary()) (*i)->GetAction()->Terminate(false);
965 if (!(*i)->GetAction()) {
966 ADD_MESSAGE("%s follows you.", (*i)->CHAR_NAME(DEFINITE));
967 CharacterArray.push_back(*i);
968 (*i)->Remove();
975 return true;
979 void level::Draw (truth AnimationDraw) const {
980 cint XMin = Max(game::GetCamera().X, 0);
981 cint YMin = Max(game::GetCamera().Y, 0);
982 cint XMax = Min(XSize, game::GetCamera().X+game::GetScreenXSize());
983 cint YMax = Min(YSize, game::GetCamera().Y+game::GetScreenYSize());
984 culong LOSTick = game::GetLOSTick();
985 blitdata BlitData = {
986 DOUBLE_BUFFER,
987 { 0, 0 },
988 { 0, 0 },
989 { TILE_SIZE, TILE_SIZE },
990 { 0 },
991 TRANSPARENT_COLOR,
992 ALLOW_ANIMATE|ALLOW_ALPHA
995 if (!game::GetSeeWholeMapCheatMode()) {
996 if (!AnimationDraw) {
997 for (int x = XMin; x < XMax; ++x) {
998 BlitData.Dest = game::CalculateScreenCoordinates(v2(x, YMin));
999 lsquare **SquarePtr = &Map[x][YMin];
1000 for (int y = YMin; y < YMax; ++y, ++SquarePtr, BlitData.Dest.Y += TILE_SIZE) {
1001 const lsquare* Square = *SquarePtr;
1002 culong LastSeen = Square->LastSeen;
1003 if (LastSeen == LOSTick) Square->Draw(BlitData);
1004 else if ((Square->Flags&STRONG_BIT) || LastSeen == LOSTick-2) Square->DrawMemorized(BlitData);
1007 } else {
1008 for (int x = XMin; x < XMax; ++x) {
1009 BlitData.Dest = game::CalculateScreenCoordinates(v2(x, YMin));
1010 lsquare **SquarePtr = &Map[x][YMin];
1011 for (int y = YMin; y < YMax; ++y, ++SquarePtr, BlitData.Dest.Y += TILE_SIZE) {
1012 const lsquare *Square = *SquarePtr;
1013 if (Square->LastSeen == LOSTick) Square->Draw(BlitData);
1014 else if (Square->Flags&STRONG_BIT) Square->DrawMemorized(BlitData);
1015 else {
1016 ccharacter *C = Square->Character;
1017 if (C) {
1018 if (C->CanBeSeenByPlayer()) Square->DrawMemorizedCharacter(BlitData); else Square->DrawMemorized(BlitData);
1024 } else {
1025 for (int x = XMin; x < XMax; ++x) {
1026 BlitData.Dest = game::CalculateScreenCoordinates(v2(x, YMin));
1027 lsquare **SquarePtr = &Map[x][YMin];
1028 for (int y = YMin; y < YMax; ++y, ++SquarePtr, BlitData.Dest.Y += TILE_SIZE) (*SquarePtr)->Draw(BlitData);
1034 v2 level::GetEntryPos (ccharacter *Char, int I) const {
1035 if (I == FOUNTAIN) {
1036 std::vector<v2> Fountains;
1037 for (int x = 0; x < XSize; ++x) {
1038 for (int y = 0; y < YSize; ++y) {
1039 if (GetLSquare(x,y)->GetOLTerrain() && GetLSquare(x,y)->GetOLTerrain()->IsFountainWithWater()) Fountains.push_back(v2(x,y));
1042 if (Fountains.empty()) return GetRandomSquare();
1043 return Fountains[RAND_N(Fountains.size())];
1045 std::map<int, v2>::const_iterator i = EntryMap.find(I);
1046 return (i == EntryMap.end() ? GetRandomSquare(Char) : i->second);
1050 void level::GenerateRectangularRoom (std::vector<v2> &OKForDoor, std::vector<v2> &Inside,
1051 std::vector<v2> &Border, const roomscript *RoomScript, room *RoomClass, v2 Pos, v2 Size)
1053 const contentscript<glterrain> *GTerrain;
1054 const contentscript<olterrain> *OTerrain;
1056 if (*RoomScript->UseFillSquareWalls()) {
1057 GTerrain = LevelScript->GetFillSquare()->GetGTerrain();
1058 OTerrain = LevelScript->GetFillSquare()->GetOTerrain();
1059 } else {
1060 GTerrain = RoomScript->GetWallSquare()->GetGTerrain();
1061 OTerrain = RoomScript->GetWallSquare()->GetOTerrain();
1064 int Room = RoomClass->GetIndex();
1065 sLong Counter = 0;
1066 truth AllowLanterns = *RoomScript->GenerateLanterns();
1067 truth AllowWindows = *RoomScript->GenerateWindows();
1068 int x, y;
1069 int Shape = *RoomScript->GetShape();
1070 int Flags = ((GTerrain->IsInside() ? *GTerrain->IsInside() : *RoomScript->IsInside()) ? INSIDE : 0);
1073 if ((Shape == ROUND_CORNERS || Shape == MAZE_ROOM) && (Size.X < 5 || Size.Y < 5)) Shape = RECTANGLE; /* No weird shapes this way. */
1074 if ((Shape == MAZE_ROOM) && (!(Size.X % 2) || !(Size.Y % 2))) Shape = RECTANGLE; /* Alas, no even numbered maze rooms allowed. */
1076 maze MazeRoom(Size.X, Size.Y);
1078 if (Shape == MAZE_ROOM) {
1079 MazeRoom.CreateMaze();
1082 for (x = Pos.X; x < Pos.X+Size.X; ++x, Counter += 2) {
1083 if (Shape == ROUND_CORNERS) {
1084 if (x == Pos.X) {
1085 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x+1, Pos.Y+1, Room, Flags);
1086 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x+1, Pos.Y+Size.Y-2, Room, Flags);
1087 Border.push_back(v2(x+1, Pos.Y+1));
1088 Border.push_back(v2(x+1, Pos.Y+Size.Y-2));
1089 continue;
1090 } else if (x == Pos.X+Size.X-1) {
1091 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x-1, Pos.Y+1, Room, Flags);
1092 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x-1, Pos.Y+Size.Y-2, Room, Flags);
1093 Border.push_back(v2(x-1, Pos.Y+1));
1094 Border.push_back(v2(x-1, Pos.Y+Size.Y-2));
1095 continue;
1098 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x, Pos.Y, Room, Flags);
1099 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x, Pos.Y+Size.Y-1, Room, Flags);
1100 if ((Shape == RECTANGLE && x != Pos.X && x != Pos.X+Size.X-1) ||
1101 (Shape == ROUND_CORNERS && x > Pos.X+1 && x < Pos.X+Size.X-2))
1103 OKForDoor.push_back(v2(x, Pos.Y));
1104 OKForDoor.push_back(v2(x, Pos.Y+Size.Y-1));
1105 if ((!AllowLanterns || !GenerateLanterns(x, Pos.Y, DOWN)) && AllowWindows) GenerateWindows(x, Pos.Y);
1106 if ((!AllowLanterns || !GenerateLanterns(x, Pos.Y+Size.Y-1, UP)) && AllowWindows) GenerateWindows(x, Pos.Y+Size.Y-1);
1108 Border.push_back(v2(x, Pos.Y));
1109 Border.push_back(v2(x, Pos.Y+Size.Y-1));
1112 game::BusyAnimation();
1114 for (y = Pos.Y+1; y < Pos.Y+Size.Y-1; ++y, Counter += 2) {
1115 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), Pos.X, y, Room, Flags);
1116 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), Pos.X+Size.X-1, y, Room, Flags);
1117 if (Shape == RECTANGLE || (Shape == ROUND_CORNERS && y != Pos.Y+1 && y != Pos.Y+Size.Y-2)) {
1118 OKForDoor.push_back(v2(Pos.X, y));
1119 OKForDoor.push_back(v2(Pos.X+Size.X-1, y));
1120 if ((!AllowLanterns || !GenerateLanterns(Pos.X, y, RIGHT)) && AllowWindows) GenerateWindows(Pos.X, y);
1121 if ((!AllowLanterns || !GenerateLanterns(Pos.X+Size.X-1, y, LEFT)) && AllowWindows) GenerateWindows(Pos.X+Size.X-1, y);
1123 Border.push_back(v2(Pos.X, y));
1124 Border.push_back(v2(Pos.X+Size.X-1, y));
1127 // Maze rooms only: put in the doors, in the corners.
1128 if (Shape == MAZE_ROOM) {
1129 int MazeDoors = RAND() % 4;
1130 switch (MazeDoors) {
1131 case 0:
1132 OKForDoor.push_back(v2(Pos.X, Pos.Y + 1));
1133 OKForDoor.push_back(v2(Pos.X + Size.X - 1, Pos.Y + Size.Y - 2));
1134 break;
1135 case 1:
1136 OKForDoor.push_back(v2(Pos.X + 1, Pos.Y));
1137 OKForDoor.push_back(v2(Pos.X + Size.X - 2, Pos.Y + Size.Y - 1));
1138 break;
1139 case 2:
1140 OKForDoor.push_back(v2(Pos.X + 1, Pos.Y + Size.Y - 1));
1141 OKForDoor.push_back(v2(Pos.X + Size.X - 2, Pos.Y));
1142 break;
1143 case 3:
1144 OKForDoor.push_back(v2(Pos.X, Pos.Y + Size.Y - 2));
1145 OKForDoor.push_back(v2(Pos.X + Size.X - 1, Pos.Y + 1));
1146 break;
1147 default:
1148 break;
1152 GTerrain = RoomScript->GetFloorSquare()->GetGTerrain();
1153 OTerrain = RoomScript->GetFloorSquare()->GetOTerrain();
1154 Counter = 0;
1155 Flags = ((GTerrain->IsInside() ? *GTerrain->IsInside() : *RoomScript->IsInside()) ? INSIDE : 0);
1157 for (x = Pos.X+1; x < Pos.X+Size.X-1; ++x) {
1158 for (y = Pos.Y+1; y < Pos.Y+Size.Y-1; ++y, ++Counter) {
1159 /* if not in the corner */
1160 if (!(Shape == MAZE_ROOM) && !(Shape == ROUND_CORNERS && (x == Pos.X+1 || x == Pos.X+Size.X-2) && (y == Pos.Y+1 || y == Pos.Y+Size.Y-2))) {
1161 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x, y, Room, Flags);
1162 Inside.push_back(v2(x,y));
1167 // Maze rooms only: put in the internal walls.
1168 if (Shape == MAZE_ROOM) {
1169 for (y = Pos.Y + 1; y < Pos.Y + Size.Y - 1; ++y) {
1170 for (x = Pos.X + 1; x < Pos.X + Size.X - 1; ++x, ++Counter) {
1171 // If there is a wall here, then put a wall here. Don't the square "inside" (forbids oterrains like fountains from generating in the wall(?)).
1172 if (!MazeRoom.MazeKernel[(y-Pos.Y-1)*(Size.X-2)+(x-Pos.X-1)]) {
1173 if (*RoomScript->UseFillSquareWalls()) {
1174 GTerrain = LevelScript->GetFillSquare()->GetGTerrain();
1175 OTerrain = LevelScript->GetFillSquare()->GetOTerrain();
1176 } else {
1177 GTerrain = RoomScript->GetWallSquare()->GetGTerrain();
1178 OTerrain = RoomScript->GetWallSquare()->GetOTerrain();
1180 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x, y, Room, Flags);
1181 } else if (MazeRoom.MazeKernel[(y-Pos.Y-1)*(Size.X-2)+(x-Pos.X-1)]) {
1182 // Put in a floor
1183 GTerrain = RoomScript->GetFloorSquare()->GetGTerrain();
1184 OTerrain = RoomScript->GetFloorSquare()->GetOTerrain();
1185 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x, y, Room, Flags);
1186 Inside.push_back(v2(x, y));
1194 void level::Reveal () {
1195 feuLong Tick = game::GetLOSTick();
1196 for (int x = 0; x < XSize; ++x) {
1197 for (int y = 0; y < YSize; ++y) {
1198 Map[x][y]->Reveal(Tick);
1204 void level::ParticleBeam (beamdata &Beam) {
1205 v2 CurrentPos = Beam.StartPos;
1206 if (Beam.Direction != YOURSELF) {
1207 for (int Length = 0; Length < Beam.Range; ++Length) {
1208 CurrentPos += game::GetMoveVector(Beam.Direction);
1209 if (!IsValidPos(CurrentPos)) break;
1210 lsquare *CurrentSquare = GetLSquare(CurrentPos);
1211 if (!CurrentSquare->IsFlyable()) {
1212 (CurrentSquare->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam);
1213 break;
1214 } else {
1215 CurrentSquare->DrawParticles(Beam.BeamColor);
1216 if ((CurrentSquare->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam)) break;
1219 } else {
1220 lsquare *Where = GetLSquare(CurrentPos);
1221 Where->DrawParticles(Beam.BeamColor);
1222 (Where->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam);
1227 /* Note: You will most likely need some help from supernatural entities to comprehend this code. Sorry. */
1228 void level::LightningBeam (beamdata &Beam) {
1229 v2 CurrentPos = Beam.StartPos;
1231 if (Beam.Direction == YOURSELF) {
1232 lsquare *Where = GetLSquare(CurrentPos);
1233 for (int c = 0; c < 4; ++c) Where->DrawLightning(v2(8, 8), Beam.BeamColor, YOURSELF);
1234 (Where->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam);
1235 return;
1238 v2 StartPos;
1240 switch (Beam.Direction) {
1241 case 0: StartPos = v2(15, 15); break;
1242 case 1: StartPos = v2(RAND()&15, 15); break;
1243 case 2: StartPos = v2(0, 15); break;
1244 case 3: StartPos = v2(15, RAND()&15); break;
1245 case 4: StartPos = v2(0, RAND()&15); break;
1246 case 5: StartPos = v2(15, 0); break;
1247 case 6: StartPos = v2(RAND()&15, 0); break;
1248 case 7: StartPos = v2(0, 0); break;
1249 default: StartPos = v2(0, 0); break;
1252 for (int Length = 0; Length < Beam.Range; ++Length) {
1253 CurrentPos += game::GetMoveVector(Beam.Direction);
1254 if (!IsValidPos(CurrentPos)) break;
1255 lsquare *CurrentSquare = GetLSquare(CurrentPos);
1256 if (!CurrentSquare->IsFlyable()) {
1257 if ((CurrentSquare->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam)) break;
1258 truth W1, W2;
1259 switch (Beam.Direction) {
1260 case 0:
1261 W1 = GetLSquare(CurrentPos+v2(1, 0))->IsFlyable();
1262 W2 = GetLSquare(CurrentPos+v2(0, 1))->IsFlyable();
1263 if (W1 == W2) {
1264 Beam.Direction = 7;
1265 } else if (W1) {
1266 ++CurrentPos.Y;
1267 Beam.Direction = 2;
1268 } else {
1269 ++CurrentPos.X;
1270 Beam.Direction = 5;
1272 break;
1273 case 1: Beam.Direction = 6; StartPos.Y = 0; break;
1274 case 2:
1275 W1 = GetLSquare(CurrentPos+v2(-1, 0))->IsFlyable();
1276 W2 = GetLSquare(CurrentPos+v2(0, 1))->IsFlyable();
1277 if (W1 == W2) {
1278 Beam.Direction = 5;
1279 } else if (W1) {
1280 ++CurrentPos.Y;
1281 Beam.Direction = 0;
1282 } else {
1283 --CurrentPos.X;
1284 Beam.Direction = 7;
1286 break;
1287 case 3: Beam.Direction = 4; StartPos.X = 0; break;
1288 case 4: Beam.Direction = 3; StartPos.X = 15; break;
1289 case 5:
1290 W1 = GetLSquare(CurrentPos+v2(1, 0))->IsFlyable();
1291 W2 = GetLSquare(CurrentPos+v2(0, -1))->IsFlyable();
1292 if (W1 == W2) {
1293 Beam.Direction = 2;
1294 } else if (W1) {
1295 --CurrentPos.Y;
1296 Beam.Direction = 7;
1297 } else {
1298 ++CurrentPos.X;
1299 Beam.Direction = 0;
1301 break;
1302 case 6: Beam.Direction = 1; StartPos.Y = 15; break;
1303 case 7:
1304 W1 = GetLSquare(CurrentPos+v2(-1, 0))->IsFlyable();
1305 W2 = GetLSquare(CurrentPos+v2(0, -1))->IsFlyable();
1306 if (W1 == W2) {
1307 Beam.Direction = 0;
1308 } else if (W1) {
1309 --CurrentPos.Y;
1310 Beam.Direction = 5;
1311 } else {
1312 --CurrentPos.X;
1313 Beam.Direction = 2;
1315 break;
1317 switch (Beam.Direction) {
1318 case 0: StartPos = v2(15, 15); break;
1319 case 2: StartPos = v2(0, 15); break;
1320 case 5: StartPos = v2(15, 0); break;
1321 case 7: StartPos = v2(0, 0); break;
1323 } else {
1324 StartPos = CurrentSquare->DrawLightning(StartPos, Beam.BeamColor, Beam.Direction);
1325 if ((CurrentSquare->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam)) break;
1331 void level::ShieldBeam (beamdata &Beam) {
1332 v2 Pos[3];
1333 switch (Beam.Direction) {
1334 case 0:
1335 Pos[0] = v2(-1, 0);
1336 Pos[1] = v2(-1, -1);
1337 Pos[2] = v2(0, -1);
1338 break;
1339 case 1:
1340 Pos[0] = v2(-1, -1);
1341 Pos[1] = v2(0, -1);
1342 Pos[2] = v2(1, -1);
1343 break;
1344 case 2:
1345 Pos[0] = v2(0, -1);
1346 Pos[1] = v2(1, -1);
1347 Pos[2] = v2(1, 0);
1348 break;
1349 case 3:
1350 Pos[0] = v2(-1, 1);
1351 Pos[1] = v2(-1, 0);
1352 Pos[2] = v2(-1, -1);
1353 break;
1354 case 4:
1355 Pos[0] = v2(1, -1);
1356 Pos[1] = v2(1, 0);
1357 Pos[2] = v2(1, 1);
1358 break;
1359 case 5:
1360 Pos[0] = v2(0, 1);
1361 Pos[1] = v2(-1, 1);
1362 Pos[2] = v2(-1, 0);
1363 break;
1364 case 6:
1365 Pos[0] = v2(1, 1);
1366 Pos[1] = v2(0, 1);
1367 Pos[2] = v2(-1, 1);
1368 break;
1369 case 7:
1370 Pos[0] = v2(1, 0);
1371 Pos[1] = v2(1, 1);
1372 Pos[2] = v2(0, 1);
1373 break;
1374 case 8:
1375 GetLSquare(Beam.StartPos)->DrawParticles(Beam.BeamColor);
1376 (GetLSquare(Beam.StartPos)->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam);
1377 return;
1378 default: return;
1380 for (int c = 0; c < 3; ++c) {
1381 if (IsValidPos(Beam.StartPos+Pos[c])) {
1382 GetLSquare(Beam.StartPos+Pos[c])->DrawParticles(Beam.BeamColor);
1383 (GetLSquare(Beam.StartPos+Pos[c])->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam);
1389 outputfile &operator << (outputfile &SaveFile, const level *Level) {
1390 Level->Save(SaveFile);
1391 return SaveFile;
1395 inputfile &operator >> (inputfile &SaveFile, level *&Level) {
1396 Level = new level;
1397 Level->Load(SaveFile);
1398 return SaveFile;
1402 void (level::*Beam[BEAM_STYLES]) (beamdata&) = {
1403 &level::ParticleBeam,
1404 &level::LightningBeam,
1405 &level::ShieldBeam
1409 void (level::*level::GetBeam(int I)) (beamdata &) {
1410 return Beam[I];
1414 v2 level::FreeSquareSeeker (ccharacter *Char, v2 StartPos, v2 Prohibited, int MaxDistance, truth AllowStartPos) const {
1415 int c;
1417 for (c = 0; c < 8; ++c) {
1418 v2 Pos = StartPos+game::GetMoveVector(c);
1419 if (IsValidPos(Pos) && Char->CanMoveOn(GetLSquare(Pos)) && Char->IsFreeForMe(GetLSquare(Pos)) && Pos != Prohibited && (AllowStartPos || !Char->PlaceIsIllegal(Pos, Prohibited))) {
1420 return Pos;
1424 if (MaxDistance) {
1425 for (c = 0; c < 8; ++c) {
1426 v2 Pos = StartPos+game::GetMoveVector(c);
1427 if (IsValidPos(Pos)) {
1428 if (Char->CanMoveOn(GetLSquare(Pos)) && Pos != Prohibited) {
1429 Pos = FreeSquareSeeker(Char, Pos, Prohibited, MaxDistance-1, AllowStartPos);
1430 if (Pos != ERROR_V2) return Pos;
1436 return ERROR_V2;
1440 /* Returns ERROR_V2 if no free square was found */
1441 v2 level::GetNearestFreeSquare (ccharacter *Char, v2 StartPos, truth AllowStartPos) const {
1442 if (AllowStartPos && Char->CanMoveOn(GetLSquare(StartPos)) && Char->IsFreeForMe(GetLSquare(StartPos))) return StartPos;
1444 int c;
1446 for (c = 0; c < 8; ++c) {
1447 v2 Pos = StartPos+game::GetMoveVector(c);
1448 if (IsValidPos(Pos) && Char->CanMoveOn(GetLSquare(Pos)) && Char->IsFreeForMe(GetLSquare(Pos)) && (AllowStartPos || !Char->PlaceIsIllegal(Pos, StartPos))) {
1449 return Pos;
1453 for (int Dist = 0; Dist < 5; ++Dist) {
1454 for (c = 0; c < 8; ++c) {
1455 v2 Pos = StartPos+game::GetMoveVector(c);
1456 if (IsValidPos(Pos) && Char->CanMoveOn(GetLSquare(Pos))) {
1457 Pos = FreeSquareSeeker(Char, Pos, StartPos, Dist, AllowStartPos);
1458 if (Pos != ERROR_V2) return Pos;
1463 return ERROR_V2;
1467 v2 level::GetFreeAdjacentSquare (ccharacter *Char, v2 StartPos, truth AllowCharacter) const {
1468 int PossibleDir[8];
1469 int Index = 0;
1470 lsquare *Origo = GetLSquare(StartPos);
1471 for (int d = 0; d < 8; ++d) {
1472 lsquare *Square = Origo->GetNeighbourLSquare(d);
1473 if (Square && Char->CanMoveOn(Square) && (AllowCharacter || Char->IsFreeForMe(Square))) PossibleDir[Index++] = d;
1475 return (Index ? StartPos+game::GetMoveVector(PossibleDir[RAND()%Index]) : ERROR_V2);
1479 void (level::*level::GetBeamEffectVisualizer(int I)) (const fearray<lsquare*>&, col16) const {
1480 static void (level::*Visualizer[BEAM_STYLES])(const fearray<lsquare*>&, col16) const = { &level::ParticleVisualizer, &level::LightningVisualizer, &level::ParticleVisualizer };
1481 return Visualizer[I];
1485 void level::ParticleVisualizer (const fearray<lsquare *> &Stack, col16 BeamColor) const {
1486 clock_t StartTime = clock();
1487 game::DrawEverythingNoBlit();
1488 for (fearray<lsquare*>::sizetype c = 0; c < Stack.Size; ++c) Stack[c]->DrawParticles(BeamColor, false);
1489 graphics::BlitDBToScreen();
1490 while (clock()-StartTime < 0.05*CLOCKS_PER_SEC) {}
1494 void level::LightningVisualizer (const fearray<lsquare *> &Stack, col16 BeamColor) const {
1495 clock_t StartTime = clock();
1496 game::DrawEverythingNoBlit();
1497 for (fearray<lsquare*>::sizetype c = 0; c < Stack.Size; ++c) Stack[c]->DrawLightning(v2(8, 8), BeamColor, YOURSELF, false);
1498 graphics::BlitDBToScreen();
1499 while (clock()-StartTime < 0.05*CLOCKS_PER_SEC) {}
1503 truth level::PreProcessForBone () {
1504 if (!*LevelScript->CanGenerateBone()) return false;
1505 /* Gum solution */
1506 game::SetQuestMonstersFound(0);
1507 for (int x = 0; x < XSize; ++x) {
1508 for (int y = 0; y < YSize; ++y) {
1509 Map[x][y]->PreProcessForBone();
1512 //int DungeonIndex = GetDungeon()->GetIndex();
1513 /*k8: this logic allows to generate bones on special levels; i don't quite understand it, but... */
1514 /* ok, let's use level tags for special levels, instead of hardcoding the numbers */
1516 return
1517 !(DungeonIndex == ELPURI_CAVE && Index == IVAN_LEVEL && game::GetQuestMonstersFound() < 5) &&
1518 (game::GetQuestMonstersFound() ||
1519 ((DungeonIndex != UNDER_WATER_TUNNEL || Index != VESANA_LEVEL) &&
1520 (DungeonIndex != ELPURI_CAVE || (Index != ENNER_BEAST_LEVEL && Index != DARK_LEVEL)) &&
1521 (DungeonIndex != ALIEN_VESSEL || Index != ALIENQUEEN_LEVEL)));
1523 // Ivan the Communist level?
1524 if (IsGCIvanLevel()) return (game::GetQuestMonstersFound() >= 5); // why 5?
1525 // other special level conditions are in effect only if no quest mosters are found yet
1526 if (game::GetQuestMonstersFound()) return true; // see above
1527 // other special levels
1528 cfestring *tag = GetLevelScript()->GetTag();
1529 if (!tag) return true;
1530 return (tag->Find("(!)") == festring::NPos);
1533 // check for special levels
1534 truth level::IsGCIvanLevel () const { return (GetLevelScript()->GetTag() ? *(GetLevelScript()->GetTag()) == "GCIvanLevel(!)" : false); }
1536 truth level::IsUTVesanaLevel () const { return (GetTag() ? *GetTag() == "UTVesanaLevel(!)" : false); }
1537 truth level::IsGCEnnerLevel () const { return (GetTag() ? *GetTag() == "GCEnnerBeastLevel(!)" : false); }
1538 truth level::IsGCElpuriLevel () const { return (GetTag() ? *GetTag() == "GCElpuriLevel(!)" : false); }
1539 truth level::IsGCOreeLevel () const { return (GetTag() ? *GetTag() == "GCOreeLair(!)" : false); }
1540 truth level::IsSolicitusLevel () const { return (GetTag() ? *GetTag() == "SolicitusLevel(!)" : false); }
1541 truth level::IsAlienQueenLevel () const { return (GetTag() ? *GetTag() == "AlienQueenLevel(!)" : false); }
1545 truth level::PostProcessForBone () {
1546 game::SetTooGreatDangerFound(false);
1547 double DangerSum = 0;
1548 int Enemies = 0;
1549 for (int x = 0; x < XSize; ++x) {
1550 for (int y = 0; y < YSize; ++y) {
1551 Map[x][y]->PostProcessForBone(DangerSum, Enemies);
1554 return !(game::TooGreatDangerFound() || (Enemies && DangerSum/Enemies > Difficulty*10));
1558 void level::FinalProcessForBone () {
1559 for (int x = 0; x < XSize; ++x) {
1560 for (int y = 0; y < YSize; ++y) {
1561 Map[x][y]->FinalProcessForBone();
1564 for (uInt c = 1; c < Room.size(); ++c) Room[c]->FinalProcessForBone();
1568 truth level::GenerateDungeon (int Index) {
1569 cfestring *Msg = LevelScript->GetLevelMessage();
1570 if (Msg) LevelMessage = *Msg;
1572 if (*LevelScript->GenerateMonsters()) {
1573 MonsterGenerationInterval = *LevelScript->GetMonsterGenerationIntervalBase() + *LevelScript->GetMonsterGenerationIntervalDelta()*Index;
1574 IdealPopulation = *LevelScript->GetMonsterAmountBase() + *LevelScript->GetMonsterAmountDelta()*Index;
1577 Difficulty = 0.001*(*LevelScript->GetDifficultyBase() + *LevelScript->GetDifficultyDelta()*Index);
1578 EnchantmentMinusChance = *LevelScript->GetEnchantmentMinusChanceBase() + *LevelScript->GetEnchantmentMinusChanceDelta()*Index;
1579 EnchantmentPlusChance = *LevelScript->GetEnchantmentPlusChanceBase() + *LevelScript->GetEnchantmentPlusChanceDelta()*Index;
1580 const contentscript<glterrain> *GTerrain = LevelScript->GetFillSquare()->GetGTerrain();
1581 const contentscript<olterrain> *OTerrain = LevelScript->GetFillSquare()->GetOTerrain();
1582 sLong Counter = 0;
1583 int x;
1585 game::BusyAnimation();
1587 for (x = 0; x < XSize; ++x) {
1588 for (int y = 0; y < YSize; ++y, ++Counter) {
1589 Map[x][y]->SetLTerrain(GTerrain->Instantiate(), OTerrain->Instantiate());
1593 uInt c;
1594 uInt Rooms = LevelScript->GetRooms()->Randomize();
1595 const std::list<roomscript>& RoomList = LevelScript->GetRoom();
1596 std::list<roomscript>::const_iterator Iterator = RoomList.begin();
1598 for (c = 0; c < Rooms; ++c) {
1599 game::BusyAnimation();
1600 if (c < RoomList.size()) {
1601 int i;
1602 for (i = 0; i < 1000; ++i) if (MakeRoom(&*Iterator)) break;
1603 if (i == 1000) { /*ABORT("Failed to place special room #%d!", c);*/ return false; }
1604 ++Iterator;
1605 } else {
1606 const roomscript *RoomScript = LevelScript->GetRoomDefault();
1607 for (int i = 0; i < 50; ++i) if (MakeRoom(RoomScript)) break;
1611 game::BusyAnimation();
1613 if (!*LevelScript->IgnoreDefaultSpecialSquares()) {
1614 /* Gum solution */
1615 const levelscript *LevelBase = static_cast<const levelscript*>(LevelScript->GetBase());
1616 if (LevelBase) {
1617 const std::list<squarescript> &Square = LevelBase->GetSquare();
1618 for (std::list<squarescript>::const_iterator i = Square.begin(); i != Square.end(); ++i) {
1619 game::BusyAnimation();
1620 ApplyLSquareScript(&*i);
1625 const std::list<squarescript> &Square = LevelScript->GetSquare();
1627 for (std::list<squarescript>::const_iterator i = Square.begin(); i != Square.end(); ++i) {
1628 game::BusyAnimation();
1629 ApplyLSquareScript(&*i);
1632 for (c = 0; c < AttachQueue.size(); ++c) AttachPos(AttachQueue[c].X, AttachQueue[c].Y);
1634 for (x = 0; x < XSize; ++x) {
1635 for (int y = 0; y < YSize; ++y) {
1636 Map[x][y]->CalculateGroundBorderPartners();
1637 Map[x][y]->CalculateOverBorderPartners();
1641 AttachQueue.clear();
1642 CreateItems(LevelScript->GetItems()->Randomize());
1644 return true;
1648 truth level::GenerateJungle () {
1649 int x, y;
1651 for (x = 0; x < XSize; ++x) {
1652 for (y = 0; y < YSize; ++y) {
1653 Map[x][y] = new lsquare(this, v2(x, y));
1654 Map[x][y]->SetLTerrain(solidterrain::Spawn(GRASS_TERRAIN), 0);
1658 for (;;) {
1659 CreateTunnelNetwork(1, 4, 20, 120, v2(0, YSize/2));
1660 CreateTunnelNetwork(1, 4, 20, 120, v2(XSize-1, YSize/2));
1662 for (int c = 0; c < 25; ++c) {
1663 v2 StartPos;
1665 switch(RAND_N(5)) {
1666 case 0: StartPos = v2(RAND_N(XSize), 0); break;
1667 case 1: StartPos = v2(RAND_N(XSize), YSize-1); break;
1668 case 2: StartPos = v2(0, RAND_N(YSize)); break;
1669 case 3: StartPos = v2(XSize-1, RAND_N(YSize)); break;
1670 case 4: StartPos = v2(RAND_N(XSize), RAND_N(YSize)); break;
1671 default: StartPos = v2(0, 0); break; /* k8: shut up the compiler */
1673 CreateTunnelNetwork(1,4,20, 120, StartPos);
1676 for (x = 0; x < XSize; ++x) {
1677 game::BusyAnimation();
1678 for (y = 0; y < YSize; ++y) {
1679 if(FlagMap[x][y] != PREFERRED) Map[x][y]->ChangeOLTerrain(wall::Spawn(BRICK_PROPAGANDA));
1680 else if(RAND_2) Map[x][y]->ChangeOLTerrain(decoration::Spawn(PALM));
1685 return true;
1689 void level::CreateTunnelNetwork (int MinLength, int MaxLength, int MinNodes, int MaxNodes, v2 StartPos) {
1690 v2 Pos = StartPos, Direction;
1691 int Length;
1692 game::BusyAnimation();
1693 FlagMap[Pos.X][Pos.Y] = PREFERRED;
1694 for (int c1 = 0; c1 < MaxNodes; ++c1) {
1695 Direction = game::GetBasicMoveVector(RAND()%4);
1696 Length = MinLength+RAND_N(MaxLength-MinLength+1);
1697 for (int c2 = 0; c2 < Length; ++c2) {
1698 if (IsValidPos(Direction+Pos)) {
1699 Pos += Direction;
1700 FlagMap[Pos.X][Pos.Y] = PREFERRED;
1701 } else {
1702 if (c1 >= MinNodes) return;
1703 break;
1710 truth level::GenerateDesert () {
1711 for (int x = 0; x < XSize; ++x) {
1712 for (int y = 0; y < YSize; ++y) {
1713 Map[x][y] = new lsquare(this, v2(x, y));
1714 Map[x][y]->SetLTerrain(solidterrain::Spawn(SAND_TERRAIN), 0);
1717 game::BusyAnimation();
1718 int AmountOfCactuses = RAND_N(10);
1719 int c;
1720 for (c = 0; c < AmountOfCactuses; ++c) Map[RAND_N(XSize)][RAND_N(YSize)]->ChangeOLTerrain(decoration::Spawn(CACTUS));
1721 int AmountOfBoulders = RAND_N(10);
1722 for (c = 0; c < AmountOfBoulders; ++c) Map[RAND_N(XSize)][RAND_N(YSize)]->ChangeOLTerrain(boulder::Spawn(1+RAND_2));
1724 return true;
1728 truth level::GenerateSteppe () {
1729 for (int x = 0; x < XSize; ++x) {
1730 for (int y = 0; y < YSize; ++y) {
1731 Map[x][y] = new lsquare(this, v2(x, y));
1732 Map[x][y]->SetLTerrain(solidterrain::Spawn(GRASS_TERRAIN), 0);
1735 game::BusyAnimation();
1736 int c;
1737 int AmountOfBoulders = RAND_N(20)+5;
1738 for (c = 0; c < AmountOfBoulders; ++c) Map[RAND_N(XSize)][RAND_N(YSize)]->ChangeOLTerrain(boulder::Spawn(1+RAND_2));
1740 return true;
1744 truth level::GenerateLeafyForest () {
1745 for (int x = 0; x < XSize; ++x) {
1746 for (int y = 0; y < YSize; ++y) {
1747 Map[x][y] = new lsquare(this, v2(x, y));
1748 olterrain *OLTerrain;
1749 switch (RAND_4) {
1750 case 0: if (RAND_8) OLTerrain = decoration::Spawn(OAK); else OLTerrain = decoration::Spawn(TEAK); break;
1751 case 1: OLTerrain = decoration::Spawn(BIRCH); break;
1752 case 2: OLTerrain = 0; if (!RAND_4) OLTerrain = boulder::Spawn(1+RAND_2); if (!RAND_4) OLTerrain = boulder::Spawn(3); break;
1753 default: OLTerrain = 0;
1755 Map[x][y]->SetLTerrain(solidterrain::Spawn(GRASS_TERRAIN), OLTerrain);
1759 return true;
1763 truth level::GenerateEvergreenForest () {
1764 for (int x = 0; x < XSize; ++x) {
1765 for (int y = 0; y < YSize; ++y) {
1766 Map[x][y] = new lsquare(this, v2(x, y));
1767 olterrain *OLTerrain = 0;
1768 switch (RAND_4) {
1769 case 0: if (RAND_2) OLTerrain = decoration::Spawn(PINE); break;
1770 case 1: OLTerrain = decoration::Spawn(FIR); break;
1771 case 2: if (!RAND_4) OLTerrain = boulder::Spawn(1+RAND_2); if (!RAND_4) OLTerrain = boulder::Spawn(3); break;
1773 Map[x][y]->SetLTerrain(solidterrain::Spawn(GRASS_TERRAIN), OLTerrain);
1777 return true;
1781 truth level::GenerateTundra () {
1782 for (int x = 0; x < XSize; ++x) {
1783 for (int y = 0; y < YSize; ++y) {
1784 Map[x][y] = new lsquare(this, v2(x, y));
1785 Map[x][y]->SetLTerrain(solidterrain::Spawn(SNOW_TERRAIN), 0);
1788 game::BusyAnimation();
1789 int c;
1790 int AmountOfBoulders = RAND_N(20)+8;
1791 for (c = 0; c < AmountOfBoulders; ++c) Map[RAND_N(XSize)][RAND_N(YSize)]->ChangeOLTerrain(boulder::Spawn(SNOW_BOULDER));
1792 int AmountOfDwarfBirches = RAND_N(10);
1793 for (c = 0; c < AmountOfDwarfBirches; ++c) Map[RAND_N(XSize)][RAND_N(YSize)]->ChangeOLTerrain(decoration::Spawn(DWARF_BIRCH));
1795 return true;
1799 truth level::GenerateGlacier () {
1800 int x, y;
1802 for (x = 0; x < XSize; ++x) {
1803 for (y = 0; y < YSize; ++y) {
1804 Map[x][y] = new lsquare(this, v2(x, y));
1805 Map[x][y]->SetLTerrain(solidterrain::Spawn(SNOW_TERRAIN), 0);
1809 int AmountOfBoulders = RAND_N(20)+5;
1811 for (int c = 0; c < AmountOfBoulders; ++c)
1812 Map[RAND_N(XSize)][RAND_N(YSize)]->ChangeOLTerrain(boulder::Spawn(SNOW_BOULDER));
1814 for (;;) {
1815 CreateTunnelNetwork(1,4,20, 120, v2(0,YSize/2));
1816 CreateTunnelNetwork(1,4,20, 120, v2(XSize-1,YSize/2));
1818 for (int c = 0; c < 20; ++c) {
1819 v2 StartPos;
1820 switch(RAND_N(5)) {
1821 case 0: StartPos = v2(RAND_N(XSize), 0); break;
1822 case 1: StartPos = v2(RAND_N(XSize), YSize-1); break;
1823 case 2: StartPos = v2(0, RAND_N(YSize)); break;
1824 case 3: StartPos = v2(XSize-1, RAND_N(YSize)); break;
1825 case 4: StartPos = v2(RAND_N(XSize), RAND_N(YSize)); break;
1826 default: StartPos = v2(0, 0); break; /* k8: shut up the compiler */
1828 CreateTunnelNetwork(1,4,20, 120, StartPos);
1831 for (x = 0; x < XSize; ++x)
1832 for (y = 0; y < YSize; ++y)
1833 if(FlagMap[x][y] != PREFERRED) FlagMap[x][y] |= RAND_2?ICE_TERRAIN:STONE_TERRAIN;
1835 for (x = 0; x < XSize; ++x) {
1836 game::BusyAnimation();
1837 for (y = 0; y < YSize; ++y) {
1838 if (!(FlagMap[x][y]&PREFERRED)) {
1839 int SquaresAround = 0;
1840 int IceAround = 0;
1841 for (int d = 0; d < 8; ++d) {
1842 v2 Pos = v2(x,y)+game::GetMoveVector(d);
1843 if (IsValidPos(Pos) && !(FlagMap[Pos.X][Pos.Y]&PREFERRED)) {
1844 ++SquaresAround;
1845 if(FlagMap[Pos.X][Pos.Y]&ICE_TERRAIN) ++IceAround;
1848 if (IceAround > SquaresAround/2) FlagMap[x][y] = ICE_TERRAIN;
1849 else FlagMap[x][y] = STONE_TERRAIN;
1854 for (x = 0; x < XSize; ++x) {
1855 for (y = 0; y < YSize; ++y) {
1856 if (!(FlagMap[x][y]&PREFERRED)) {
1857 if(FlagMap[x][y]&ICE_TERRAIN) GetLSquare(x,y)->ChangeOLTerrain(wall::Spawn(ICE_WALL));
1858 else GetLSquare(x,y)->ChangeOLTerrain(wall::Spawn(STONE_WALL));
1863 break; // Doesn't yet check path in any way
1866 return true;
1870 bool nodepointerstorer::operator < (const nodepointerstorer &N) const {
1871 /* In the non-euclidean geometry of IVAN, certain very curved paths are as long as straight ones.
1872 However, they are so ugly that it is best to prefer routes with as few diagonal moves as
1873 possible without lengthening the travel. */
1874 if (Node->TotalDistanceEstimate != N.Node->TotalDistanceEstimate) {
1875 return (Node->TotalDistanceEstimate > N.Node->TotalDistanceEstimate);
1877 return (Node->Diagonals > N.Node->Diagonals);
1881 void node::CalculateNextNodes () {
1882 static const int TryOrder[8] = { 1, 3, 4, 6, 0, 2, 5, 7 };
1883 for (int d = 0; d < 8; ++d) {
1884 v2 NodePos = Pos+game::GetMoveVector(TryOrder[d]);
1885 if (NodePos.X >= 0 && NodePos.Y >= 0 && NodePos.X < XSize && NodePos.Y < YSize) {
1886 node *Node = NodeMap[NodePos.X][NodePos.Y];
1887 if (!Node->Processed && ((!SpecialMover && RequiredWalkability&WalkabilityMap[NodePos.X][NodePos.Y]) || (SpecialMover && SpecialMover->CanTheoreticallyMoveOn(Node->Square)) || NodePos == To)) {
1888 Node->Processed = true;
1889 Node->Distance = Distance+1;
1890 Node->Diagonals = Diagonals;
1891 if (d >= 4) ++Node->Diagonals;
1892 Node->Last = this;
1893 /* We use the heuristic max(abs(distance.x), abs(distance.y)) here,
1894 which is exact in the current geometry if the path is open */
1895 sLong Remaining = To.X-NodePos.X;
1896 if (Remaining < NodePos.X-To.X) Remaining = NodePos.X-To.X;
1897 if (Remaining < NodePos.Y-To.Y) Remaining = NodePos.Y-To.Y;
1898 if (Remaining < To.Y-NodePos.Y) Remaining = To.Y-NodePos.Y;
1899 Node->Remaining = Remaining;
1900 Node->TotalDistanceEstimate = Node->Distance+Node->Remaining;
1901 NodeQueue->push(nodepointerstorer(Node));
1908 /* Finds the shortest (but possibly not the shortest-looking) path between From and To
1909 if such exists. Returns a pointer to the node associated with the last square or zero if
1910 a route can't be found. Calling FindRoute again may invalidate the node, so you must
1911 store the path in another format ASAP. */
1912 node *level::FindRoute (v2 From, v2 To, const std::set<v2>& Illegal, int RequiredWalkability, ccharacter* SpecialMover) {
1913 node::NodeMap = NodeMap;
1914 node::RequiredWalkability = RequiredWalkability;
1915 node::SpecialMover = SpecialMover;
1916 node::To = To;
1917 node::WalkabilityMap = WalkabilityMap;
1918 node::XSize = XSize;
1919 node::YSize = YSize;
1921 if (!Illegal.empty() && Illegal.find(To) != Illegal.end()) return 0;
1923 for (int x = 0; x < XSize; ++x) {
1924 for (int y = 0; y < YSize; ++y) {
1925 NodeMap[x][y]->Processed = false;
1929 node *Node = NodeMap[From.X][From.Y];
1930 Node->Last = 0;
1931 Node->Processed = true;
1932 Node->Distance = 0;
1933 Node->Diagonals = 0;
1934 nodequeue NodeQueue;
1935 NodeQueue.push(nodepointerstorer(Node));
1936 node::NodeQueue = &NodeQueue;
1938 while (!NodeQueue.empty()) {
1939 Node = NodeQueue.top().Node;
1940 NodeQueue.pop();
1941 if (Node->Pos == To) return Node;
1942 if (Illegal.empty() || Illegal.find(Node->Pos) == Illegal.end()) Node->CalculateNextNodes();
1945 return 0;
1949 /* All items on ground are moved to the IVector and all characters to CVector */
1950 void level::CollectEverything (itemvector &IVector, charactervector &CVector) {
1951 for (int x = 0; x < XSize; ++x) {
1952 for (int y = 0; y < YSize; ++y) {
1953 lsquare *LS = Map[x][y];
1954 LS->GetStack()->MoveItemsTo(IVector, CENTER);
1955 character *C = LS->GetCharacter();
1956 if (C && !C->IsPlayer()) {
1957 C->Remove();
1958 CVector.push_back(C);
1965 void level::CreateGlobalRain (liquid *Liquid, v2 Speed) {
1966 GlobalRainLiquid = Liquid;
1967 GlobalRainSpeed = Speed;
1968 for (int x = 0; x < XSize; ++x) {
1969 for (int y = 0; y < YSize; ++y) {
1970 if (!Map[x][y]->IsInside()) Map[x][y]->AddRain(Liquid, Speed, MONSTER_TEAM, false);
1976 void level::CheckSunLight () {
1977 if (Index == 0 && GetDungeon()->GetIndex() == NEW_ATTNAM) {
1978 double Cos = cos(FPI*(game::GetTick()%48000)/24000.0);
1979 if (Cos > 0.01) {
1980 int E = int(100+Cos*30);
1981 SunLightEmitation = MakeRGB24(E, E, E);
1982 AmbientLuminance = MakeRGB24(E-6, E-6, E-6);
1983 } else {
1984 SunLightEmitation = 0;
1985 AmbientLuminance = NightAmbientLuminance;
1987 } else if (Index == 0 && GetDungeon()->GetIndex() == ATTNAM) {
1988 double Cos = cos(FPI*(game::GetTick()%48000)/24000.0);
1989 if (Cos > 0.41) {
1990 int E = int(100+(Cos-0.40)*40);
1991 SunLightEmitation = MakeRGB24(E, E, E);
1992 AmbientLuminance = MakeRGB24(E-8, E-8, E-8);
1993 } else {
1994 SunLightEmitation = 0;
1995 AmbientLuminance = NightAmbientLuminance;
1997 } else if (Index == 0 && GetDungeon()->GetIndex() == MUNTUO) {
1998 double Cos = cos(FPI*(game::GetTick()%48000)/24000.0);
1999 if (Cos > 0.21) {
2000 int E = int(100+(Cos-0.20)*30);
2001 SunLightEmitation = MakeRGB24(E, E, E);
2002 AmbientLuminance = MakeRGB24(E-4, E-4, E-4);
2003 } else {
2004 SunLightEmitation = 0;
2005 AmbientLuminance = NightAmbientLuminance;
2007 } else {
2008 return;
2010 SunLightDirection = game::GetSunLightDirectionVector();
2011 ChangeSunLight();
2015 void level::ChangeSunLight () {
2016 truth SunSet = game::IsDark(SunLightEmitation);
2017 feuLong c;
2018 for (c = 0; c < XSizeTimesYSize; ++c) Map[0][c]->RemoveSunLight();
2019 if (!SunSet) EmitSunBeams();
2020 for (c = 0; c < XSizeTimesYSize; ++c) {
2021 lsquare *Square = Map[0][c];
2022 if (Square->Flags&IS_TRANSPARENT) Square->CalculateSunLightLuminance(EMITTER_SQUARE_PART_BITS);
2023 if (!Square->IsInside()) Square->AmbientLuminance = AmbientLuminance;
2024 Square->SendSunLightSignals();
2026 for (c = 0; c < XSizeTimesYSize; ++c) Map[0][c]->CheckIfIsSecondarySunLightEmitter();
2030 void level::InitSquarePartEmitationTicks () {
2031 for (int x = 0; x < XSize; ++x) {
2032 for (int y = 0; y < YSize; ++y) {
2033 Map[x][y]->SquarePartEmitationTick = 0;
2039 truth level::GenerateWindows (int X, int Y) const {
2040 olterrain *Terrain = Map[X][Y]->GetOLTerrain();
2041 if (Terrain && Terrain->CreateWindowConfigurations() && !(RAND()%6)) {
2042 Terrain->SetConfig(Terrain->GetConfig()|WINDOW);
2043 Map[X][Y]->CalculateIsTransparent();
2044 return true;
2046 return false;
2050 struct sunbeamcontroller : public stackcontroller {
2051 static truth Handler (int, int);
2052 static void ProcessStack ();
2054 static feuLong ID;
2055 static int SunLightBlockHeight;
2056 static v2 SunLightBlockPos;
2057 static truth ReSunEmitation;
2061 feuLong sunbeamcontroller::ID;
2062 int sunbeamcontroller::SunLightBlockHeight;
2063 v2 sunbeamcontroller::SunLightBlockPos;
2064 truth sunbeamcontroller::ReSunEmitation;
2067 void level::ForceEmitterNoxify (const emittervector& Emitter) const {
2068 for (emittervector::const_iterator i = Emitter.begin(); i != Emitter.end(); ++i) {
2069 feuLong ID = i->ID;
2070 lsquare *Square = GetLSquare(ExtractPosFromEmitterID(ID));
2071 if (ID&SECONDARY_SUN_LIGHT) {
2072 Square->Noxify(Square->SecondarySunLightEmitation, SECONDARY_SUN_LIGHT);
2073 } else {
2074 Square->Noxify(Square->Emitation);
2080 void level::ForceEmitterEmitation (const emittervector &Emitter, const sunemittervector &SunEmitter, feuLong IDFlags) const {
2081 for (emittervector::const_iterator i = Emitter.begin(); i != Emitter.end(); ++i) {
2082 feuLong ID = i->ID;
2083 lsquare *Square = GetLSquare(ExtractPosFromEmitterID(ID));
2084 if (ID&SECONDARY_SUN_LIGHT) {
2085 Square->Emitate(Square->SecondarySunLightEmitation, SECONDARY_SUN_LIGHT|IDFlags);
2086 } else {
2087 Square->Emitate(Square->Emitation, IDFlags);
2091 stackcontroller::Map = Map;
2092 stackcontroller::Stack = SquareStack;
2093 stackcontroller::StackIndex = 0;
2094 stackcontroller::LevelXSize = XSize;
2095 stackcontroller::LevelYSize = YSize;
2096 sunbeamcontroller::ReSunEmitation = true;
2097 for (sunemittervector::const_iterator i = SunEmitter.begin(); i != SunEmitter.end(); ++i) {
2098 feuLong ID = (*i&~(EMITTER_SHADOW_BITS|EMITTER_SQUARE_PART_BITS))|RE_SUN_EMITATED, SourceFlags;
2099 int X, Y;
2100 if (ID&ID_X_COORDINATE) {
2101 X = (ID&EMITTER_IDENTIFIER_BITS)-(XSize<<3);
2102 Y = (ID&ID_BEGIN ? -1 : YSize);
2103 SourceFlags = (ID&ID_BEGIN ? SP_BOTTOM : SP_TOP);
2104 } else {
2105 X = (ID&ID_BEGIN ? -1 : XSize);
2106 Y = (ID&EMITTER_IDENTIFIER_BITS)-(YSize<<3);
2107 SourceFlags = (ID&ID_BEGIN ? SP_RIGHT : SP_LEFT);
2109 EmitSunBeam(v2(X, Y), ID, SourceFlags);
2111 sunbeamcontroller::ProcessStack();
2116 struct loscontroller : public tickcontroller, public stackcontroller {
2117 static truth Handler (int x, int y) {
2118 lsquare *Square = Map[x>>1][y>>1];
2119 culong SquareFlags = Square->Flags;
2120 if (SquareFlags&PERFECTLY_QUADRI_HANDLED) return true;
2121 if (!(SquareFlags&IN_SQUARE_STACK)) {
2122 Square->Flags |= IN_SQUARE_STACK;
2123 Stack[StackIndex++] = Square;
2125 if (SquareFlags&IS_TRANSPARENT) {
2126 Square->Flags |= PERFECTLY_QUADRI_HANDLED;
2127 return true;
2129 cint SquarePartIndex = (x&1)+((y&1)<<1);
2130 Square->SquarePartLastSeen = (Square->SquarePartLastSeen&~SquarePartTickMask[SquarePartIndex])|ShiftedTick[SquarePartIndex];
2131 return false;
2133 static feuLong &GetTickReference (int X, int Y) {
2134 return Map[X][Y]->SquarePartLastSeen;
2136 static void ProcessStack () {
2137 for (sLong c = 0; c < StackIndex; ++c) Stack[c]->SignalSeen(Tick);
2142 void level::UpdateLOS () {
2143 game::RemoveLOSUpdateRequest();
2144 stackcontroller::Map = Map;
2145 stackcontroller::Stack = SquareStack;
2146 stackcontroller::StackIndex = 0;
2147 tickcontroller::Tick = game::IncreaseLOSTick();
2148 tickcontroller::PrepareShiftedTick();
2149 int Radius = PLAYER->GetLOSRange();
2150 for (int c = 0; c < PLAYER->GetSquaresUnder(); ++c) {
2151 mapmath<loscontroller>::DoQuadriArea(PLAYER->GetPos(c).X, PLAYER->GetPos(c).Y, Radius*Radius, XSize, YSize);
2153 loscontroller::ProcessStack();
2154 if (PLAYER->StateIsActivated(INFRA_VISION)) {
2155 for (int c = 0; c < game::GetTeams(); ++c) {
2156 for (std::list<character*>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
2157 if ((*i)->IsEnabled()) (*i)->SendNewDrawRequest();
2164 void level::EnableGlobalRain () {
2165 for (int x = 0; x < XSize; ++x) {
2166 for (int y = 0; y < YSize; ++y) {
2167 Map[x][y]->EnableGlobalRain();
2173 void level::DisableGlobalRain () {
2174 for (int x = 0; x < XSize; ++x) {
2175 for (int y = 0; y < YSize; ++y) {
2176 Map[x][y]->DisableGlobalRain();
2182 void level::InitLastSeen () {
2183 for (int x = 0; x < XSize; ++x) {
2184 for (int y = 0; y < YSize; ++y) {
2185 Map[x][y]->InitLastSeen();
2191 void level::EmitSunBeams () {
2192 stackcontroller::Map = Map;
2193 stackcontroller::LevelXSize = XSize;
2194 stackcontroller::LevelYSize = YSize;
2195 sunbeamcontroller::ReSunEmitation = false;
2196 v2 Dir = SunLightDirection;
2197 int x, y, X = 0, Y = 0, SourceFlags;
2198 feuLong IDFlags;
2199 /* Do not try to understand the logic behind the starting points of
2200 sunbeams. I determined the formulas by trial and error since all
2201 understandable loops produced strange shapes for the shadows of
2202 either small of large objects probably due to rounding errors
2203 made during line calculations. */
2204 if (!Dir.X || (Dir.Y && abs(Dir.Y) < abs(Dir.X))) {
2205 if (Dir.Y > 0) {
2206 Y = -1;
2207 SourceFlags = SP_BOTTOM;
2208 IDFlags = ID_X_COORDINATE|ID_BEGIN;
2209 } else {
2210 Y = YSize;
2211 SourceFlags = SP_TOP;
2212 IDFlags = ID_X_COORDINATE;
2214 } else {
2215 if (Dir.X > 0) {
2216 X = -1;
2217 SourceFlags = SP_RIGHT;
2218 IDFlags = ID_BEGIN;
2219 } else {
2220 X = XSize;
2221 SourceFlags = SP_LEFT;
2222 IDFlags = 0;
2225 if (!Dir.X) {
2226 int Index = XSize<<3;
2227 for (x = 0; x < XSize; ++x, ++Index) EmitSunBeam(v2(x, Y), Index|IDFlags, SourceFlags);
2228 } else if (!Dir.Y) {
2229 int Index = YSize<<3;
2230 for (y = 0; y < YSize; ++y, ++Index) EmitSunBeam(v2(X, y), Index|IDFlags, SourceFlags);
2231 } else if (abs(Dir.Y) < abs(Dir.X)) {
2232 int Index = Dir.X > 0 ? 0 : XSize<<3;
2233 int StartX = Dir.X > 0 ? -XSize<<3 : 0;
2234 int EndX = Dir.X > 0 ? XSize : (XSize<<3)+XSize;
2235 for (x = StartX; x < EndX; ++x, ++Index) EmitSunBeam(v2(x, Y), Index|IDFlags, SourceFlags);
2236 } else {
2237 int Index = Dir.Y > 0 ? 0 : YSize<<3;
2238 int StartY = Dir.Y > 0 ? -YSize<<3 : 0;
2239 int EndY = Dir.Y > 0 ? YSize : (YSize<<3)+YSize;
2240 for (y = StartY; y < EndY; ++y, ++Index) EmitSunBeam(v2(X, y), Index|IDFlags, SourceFlags);
2245 void level::EmitSunBeam (v2 S, feuLong ID, int SourceFlags) const {
2246 S <<= 1;
2247 v2 D = S+SunLightDirection;
2248 sunbeamcontroller::ID = ID;
2249 if (SourceFlags&SP_TOP_LEFT) {
2250 sunbeamcontroller::SunLightBlockHeight = 0;
2251 mapmath<sunbeamcontroller>::DoLine(S.X, S.Y, D.X, D.Y, SKIP_FIRST);
2253 if (SourceFlags&SP_TOP_RIGHT) {
2254 sunbeamcontroller::SunLightBlockHeight = 0;
2255 mapmath<sunbeamcontroller>::DoLine(S.X+1, S.Y, D.X+1, D.Y, SKIP_FIRST);
2257 if (SourceFlags&SP_BOTTOM_LEFT) {
2258 sunbeamcontroller::SunLightBlockHeight = 0;
2259 mapmath<sunbeamcontroller>::DoLine(S.X, S.Y+1, D.X, D.Y+1, SKIP_FIRST);
2261 if (SourceFlags&SP_BOTTOM_RIGHT) {
2262 sunbeamcontroller::SunLightBlockHeight = 0;
2263 mapmath<sunbeamcontroller>::DoLine(S.X+1, S.Y+1, D.X+1, D.Y+1, SKIP_FIRST);
2268 truth sunbeamcontroller::Handler (int x, int y) {
2269 int X = x>>1, Y = y>>1;
2271 if (X < 0 || Y < 0 || X >= LevelXSize || Y >= LevelYSize) {
2272 return (X >= -1 && X <= LevelXSize) || (Y >= -1 && Y <= LevelYSize);
2275 lsquare *Square = Map[X][Y];
2276 int SquarePartIndex = (x&1)+((y&1)<<1);
2278 if (SunLightBlockHeight && !Square->IsInside() && HypotSquare(x-SunLightBlockPos.X, y-SunLightBlockPos.Y) > SunLightBlockHeight) {
2279 SunLightBlockHeight = 0;
2282 if (!SunLightBlockHeight) {
2283 feuLong Flag = 1<<EMITTER_SQUARE_PART_SHIFT<<SquarePartIndex;
2284 Square->AddSunLightEmitter(ID|Flag);
2285 } else {
2286 feuLong Flags = ((1<<EMITTER_SQUARE_PART_SHIFT)|(1<<EMITTER_SHADOW_SHIFT))<<SquarePartIndex;
2287 Square->AddSunLightEmitter(ID|Flags);
2290 if (ReSunEmitation) {
2291 if (!(Square->Flags&IN_SQUARE_STACK)) Stack[StackIndex++] = Square;
2292 Square->Flags |= IN_SQUARE_STACK|CHECK_SUN_LIGHT_NEEDED;
2293 for (int d = 0; d < 8; ++d) {
2294 lsquare *Neighbour = Square->GetNeighbourLSquare(d);
2295 if (Neighbour && !(Neighbour->Flags&IN_SQUARE_STACK)) {
2296 Neighbour->Flags |= IN_SQUARE_STACK;
2297 Stack[StackIndex++] = Neighbour;
2302 if (!(Square->Flags&IS_TRANSPARENT) || (SunLightBlockHeight && Square->IsInside())) {
2303 /* This should depend on the square */
2304 SunLightBlockHeight = 81;
2305 SunLightBlockPos = v2(x, y);
2308 return true;
2312 void sunbeamcontroller::ProcessStack () {
2313 sLong c;
2314 for (c = 0; c < StackIndex; ++c) {
2315 lsquare *Square = Stack[c];
2316 if (Square->Flags&CHECK_SUN_LIGHT_NEEDED) {
2317 if (Square->Flags&IS_TRANSPARENT) Square->CalculateSunLightLuminance(EMITTER_SQUARE_PART_BITS);
2318 Square->SendSunLightSignals();
2319 Square->ZeroReSunEmitatedFlags();
2321 Square->Flags &= ~(IN_SQUARE_STACK|CHECK_SUN_LIGHT_NEEDED);
2323 for (c = 0; c < StackIndex; ++c) Stack[c]->CheckIfIsSecondarySunLightEmitter();
2327 int level::DetectMaterial (cmaterial *Material) {
2328 feuLong Tick = game::IncreaseLOSTick();
2329 int Squares = 0;
2330 for (int x = 0; x < XSize; ++x) {
2331 for (int y = 0; y < YSize; ++y) {
2332 lsquare *Square = Map[x][y];
2333 if (Square->DetectMaterial(Material)) {
2334 Square->Reveal(Tick, true);
2335 ++Squares;
2339 return Squares;
2343 void level::BlurMemory () {
2344 int x, y, SquareStackSize = 0;
2345 for (x = 0; x < XSize; ++x) {
2346 for (y = 0; y < YSize; ++y) {
2347 lsquare *Square = Map[x][y];
2348 if (Square->HasNoBorderPartners()) SquareStack[SquareStackSize++] = Square;
2351 for (x = 0; x < XSize; ++x) {
2352 for (y = 0; y < YSize; ++y) {
2353 lsquare *Square = Map[x][y];
2354 Square->Flags |= STRONG_NEW_DRAW_REQUEST|MEMORIZED_UPDATE_REQUEST|DESCRIPTION_CHANGE;
2355 if (Square->HasNoBorderPartners() && RAND()&1 && SquareStackSize) {
2356 Square->SwapMemorized(SquareStack[RAND()%SquareStackSize]);
2357 } else if (RAND()&1) {
2358 Square->DestroyMemorized();
2365 void level::CalculateLuminances () {
2366 for (int x = 0; x < XSize; ++x) {
2367 for (int y = 0; y < YSize; ++y) {
2368 lsquare *Square = Map[x][y];
2369 Square->CalculateLuminance();
2370 Square->Flags |= MEMORIZED_UPDATE_REQUEST|DESCRIPTION_CHANGE;
2376 struct areacontroller : public stackcontroller {
2377 static truth Handler (int x, int y) {
2378 if (x >= 0 && y >= 0 && x < LevelXSize && y < LevelYSize && HypotSquare(x-Center.X, y-Center.Y) <= RadiusSquare) {
2379 lsquare *Square = Map[x][y];
2380 if (!(Square->Flags&IN_SQUARE_STACK)) {
2381 Stack[StackIndex++] = Square;
2382 Square->Flags |= IN_SQUARE_STACK;
2383 return Square->IsFlyable();
2386 return false;
2388 static int GetStartX (int) { return Center.X; }
2389 static int GetStartY (int) { return Center.Y; }
2390 static sLong RadiusSquare;
2394 sLong areacontroller::RadiusSquare;
2397 int level::AddRadiusToSquareStack (v2 Center, sLong RadiusSquare) const {
2398 stackcontroller::Map = Map;
2399 stackcontroller::Stack = SquareStack;
2400 SquareStack[0] = GetLSquare(Center);
2401 stackcontroller::StackIndex = 1;
2402 stackcontroller::LevelXSize = XSize;
2403 stackcontroller::LevelYSize = YSize;
2404 stackcontroller::Center = Center;
2405 areacontroller::RadiusSquare = RadiusSquare;
2406 mapmath<areacontroller>::DoArea();
2407 return stackcontroller::StackIndex;
2411 /* Any fountain is good that is not dry and is NOT Except */
2412 olterrain *level::GetRandomFountainWithWater (olterrain *Except) const {
2413 std::vector<olterrain *> Found;
2414 olterrain *OLTerrain;
2415 for (int x = 0; x < XSize; ++x) {
2416 for (int y = 0; y < YSize; ++y) {
2417 OLTerrain = GetLSquare(x,y)->GetOLTerrain();
2418 if (OLTerrain && OLTerrain != Except && OLTerrain->IsFountainWithWater()) Found.push_back(OLTerrain);
2421 if (Found.empty()) return 0;
2422 return Found[RAND_N(Found.size())];
2426 void level::Amnesia (int Percentile) {
2427 for (int x = 0; x < XSize; ++x) {
2428 for (int y = 0; y < YSize; ++y) {
2429 lsquare *Square = Map[x][y];
2430 if (Square->HasNoBorderPartners() && RAND_N(100) < Percentile) {
2431 Square->Flags |= STRONG_NEW_DRAW_REQUEST|MEMORIZED_UPDATE_REQUEST|DESCRIPTION_CHANGE;
2432 Square->DestroyMemorized();
2439 /* Returns how many of the monsters were seen */
2440 spawnresult level::SpawnMonsters (characterspawner Spawner, team *Team, v2 Pos, int Config, int Amount, truth IgnoreWalkability) {
2441 spawnresult SR = { 0, 0 };
2442 for (int c = 0; c < Amount; ++c) {
2443 character *Char = Spawner(Config, 0);
2444 if (!c) SR.Pioneer = Char;
2445 Char->SetTeam(Team);
2446 if (IgnoreWalkability) Char->ForcePutNear(Pos); else Char->PutNear(Pos);
2447 if (Char->CanBeSeenByPlayer()) ++SR.Seen;
2449 return SR;
2453 void level::AddSpecialCursors () {
2454 for (int x = 0; x < XSize; ++x) {
2455 for (int y = 0; y < YSize; ++y) {
2456 Map[x][y]->AddSpecialCursors();
2462 void level::GasExplosion (gas* GasMaterial, lsquare* Square, character* Terrorist) {
2463 for (int d = 0; d < 9; ++d) {
2464 lsquare *Neighbour = Square->GetNeighbourLSquare(d);
2465 if (Neighbour) {
2466 if (Neighbour->IsFlyable()) Neighbour->AddSmoke(static_cast<gas*>(GasMaterial->SpawnMore(1000)));
2467 if (Terrorist) {
2468 character *Victim = Neighbour->GetCharacter();
2469 if (Victim) Terrorist->Hostility(Victim);