moved almost all hardcoded constants to "define.dat"
[k8-i-v-a-n.git] / src / game / level.cpp
blob0761b5f19a5a1442abe0627eabf56ef3135f6b41
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 room *RoomClass = protocontainer<room>::GetProto(*RoomScript->GetType())->Spawn();
312 RoomClass->SetScript(RoomScript);
313 RoomClass->SetPos(Pos);
314 RoomClass->SetSize(Size);
315 RoomClass->SetFlags(*RoomScript->GetFlags());
316 AddRoom(RoomClass);
317 RoomClass->SetDivineMaster(*RoomScript->GetDivineMaster());
318 game::BusyAnimation();
320 std::vector<v2> OKForDoor, Inside, Border;
322 GenerateRectangularRoom(OKForDoor, Inside, Border, RoomScript, RoomClass, Pos, Size);
323 game::BusyAnimation();
325 if (*RoomScript->GenerateFountains() && !(RAND()%10)) {
326 GetLSquare(Inside[RAND()%Inside.size()])->ChangeOLTerrain(fountain::Spawn());
329 // Ward, which gets generated as per fountain activation
330 if (*RoomScript->GenerateWards() && !(RAND()%5)) {
331 GetLSquare(Inside[RAND()%Inside.size()])->ChangeOLTerrain(ward::Spawn());
334 if (*RoomScript->AltarPossible() && !(RAND()%5)) {
335 int Owner;
336 const fearray<int> *am = RoomScript->GetAllowedDivineMasters();
338 if (!am || am->Size == 0) {
339 Owner = 1+RAND()%GODS;
340 } else {
341 Owner = am->GetRandomElement();
342 if (Owner < 1 || Owner > GODS) ABORT("Your god is a bad god!");
345 GetLSquare(Inside[RAND()%Inside.size()])->ChangeOLTerrain(altar::Spawn(Owner));
346 game::GetGod(Owner)->SignalRandomAltarGeneration(Inside);
347 RoomClass->SetDivineMaster(Owner);
350 if (*RoomScript->GenerateTunnel() && !Door.empty()) {
351 game::BusyAnimation();
352 v2 OutsideDoorPos = Door[RAND()%Door.size()]; // An other room
354 if (OKForDoor.empty()) ABORT("The Doors - You are strange.");
356 v2 InsideDoorPos = OKForDoor[RAND()%OKForDoor.size()]; // this door
357 olterrain *Door = RoomScript->GetDoorSquare()->GetOTerrain()->Instantiate(); //Bug! Wrong room!
359 if (Door && !(RAND()%5) && *RoomScript->AllowLockedDoors()) {
360 if (*RoomScript->AllowBoobyTrappedDoors() && !(RAND()%5)) Door->CreateBoobyTrap();
361 Door->Lock();
364 Map[OutsideDoorPos.X][OutsideDoorPos.Y]->ChangeLTerrain(RoomScript->GetDoorSquare()->GetGTerrain()->Instantiate(), Door);
365 Map[OutsideDoorPos.X][OutsideDoorPos.Y]->Clean();
366 FlagMap[OutsideDoorPos.X][OutsideDoorPos.Y] &= ~FORBIDDEN;
367 FlagMap[OutsideDoorPos.X][OutsideDoorPos.Y] |= PREFERRED;
368 FlagMap[InsideDoorPos.X][InsideDoorPos.Y] &= ~FORBIDDEN;
369 FlagMap[InsideDoorPos.X][InsideDoorPos.Y] |= PREFERRED;
370 Door = RoomScript->GetDoorSquare()->GetOTerrain()->Instantiate();
372 if (Door && !(RAND()%5) && *RoomScript->AllowLockedDoors()) {
373 if (*RoomScript->AllowBoobyTrappedDoors() && !(RAND()%5)) Door->CreateBoobyTrap();
374 Door->Lock();
377 Map[InsideDoorPos.X][InsideDoorPos.Y]->ChangeLTerrain(RoomScript->GetDoorSquare()->GetGTerrain()->Instantiate(), Door);
378 Map[InsideDoorPos.X][InsideDoorPos.Y]->Clean();
379 GenerateTunnel(InsideDoorPos.X, InsideDoorPos.Y, OutsideDoorPos.X, OutsideDoorPos.Y, RAND()&1);
380 FlagMap[OutsideDoorPos.X][OutsideDoorPos.Y] |= FORBIDDEN;
381 FlagMap[OutsideDoorPos.X][OutsideDoorPos.Y] &= ~PREFERRED;
382 FlagMap[InsideDoorPos.X][InsideDoorPos.Y] |= FORBIDDEN;
383 FlagMap[InsideDoorPos.X][InsideDoorPos.Y] &= ~PREFERRED;
386 if (*RoomScript->GenerateDoor()) {
387 game::BusyAnimation();
388 v2 DoorPos;
390 if (OKForDoor.empty()) ABORT("The Doors - This thing has been broken.");
392 DoorPos = OKForDoor[RAND()%OKForDoor.size()];
393 Door.push_back(DoorPos);
395 if (!*RoomScript->GenerateTunnel()) {
396 Map[DoorPos.X][DoorPos.Y]->ChangeLTerrain(RoomScript->GetDoorSquare()->GetGTerrain()->Instantiate(),
397 RoomScript->GetDoorSquare()->GetOTerrain()->Instantiate());
398 Map[DoorPos.X][DoorPos.Y]->Clean();
402 // Make second door for a maze room. If the room has even-numbered dimension, then it will be a rectangular room with two doors...
403 if (*RoomScript->GenerateDoor() && (*RoomScript->GetShape() == MAZE_ROOM)) {
404 game::BusyAnimation();
405 v2 DoorPos;
407 if (!OKForDoor.empty()) {
408 DoorPos = OKForDoor[OKForDoor.size()-1];
409 Door.push_back(DoorPos);
411 if (!*RoomScript->GenerateTunnel()) {
412 Map[DoorPos.X][DoorPos.Y]->ChangeLTerrain(RoomScript->GetDoorSquare()->GetGTerrain()->Instantiate(),
413 RoomScript->GetDoorSquare()->GetOTerrain()->Instantiate());
414 Map[DoorPos.X][DoorPos.Y]->Clean();
419 const charactercontentmap *CharacterMap = RoomScript->GetCharacterMap();
421 if (CharacterMap) {
422 v2 CharPos(Pos + *CharacterMap->GetPos());
423 const contentscript<character> *CharacterScript;
425 for (int x = 0; x < CharacterMap->GetSize()->X; ++x) {
426 game::BusyAnimation();
427 for (int y = 0; y < CharacterMap->GetSize()->Y; ++y) {
428 if (IsValidScript(CharacterScript = CharacterMap->GetContentScript(x, y))) {
429 character *Char = CharacterScript->Instantiate();
431 if (Char) {
432 Char->SetGenerationDanger(Difficulty);
433 if (!Char->GetTeam()) Char->SetTeam(game::GetTeam(*LevelScript->GetTeamDefault()));
434 if (CharacterScript->GetFlags()&IS_LEADER) Char->GetTeam()->SetLeader(Char);
435 Char->PutTo(CharPos+v2(x, y));
436 Char->CreateHomeData();
437 if (CharacterScript->GetFlags()&IS_MASTER) RoomClass->SetMasterID(Char->GetID());
444 const itemcontentmap *ItemMap = RoomScript->GetItemMap();
446 if (ItemMap) {
447 v2 ItemPos(Pos + *ItemMap->GetPos());
448 const fearray<contentscript<item> >* ItemScript;
450 for (int x = 0; x < ItemMap->GetSize()->X; ++x) {
451 game::BusyAnimation();
452 for (int y = 0; y < ItemMap->GetSize()->Y; ++y) {
453 if (IsValidScript(ItemScript = ItemMap->GetContentScript(x, y))) {
454 for (uInt c1 = 0; c1 < ItemScript->Size; ++c1) {
455 const interval* TimesPtr = ItemScript->Data[c1].GetTimes();
456 int Times = TimesPtr ? TimesPtr->Randomize() : 1;
458 for (int c2 = 0; c2 < Times; ++c2) {
459 item *Item = ItemScript->Data[c1].Instantiate();
461 if (Item) {
462 int SquarePosition = ItemScript->Data[c1].GetSquarePosition();
464 if (SquarePosition != CENTER) Item->SignalSquarePositionChange(SquarePosition);
465 Map[ItemPos.X+x][ItemPos.Y+y]->GetStack()->AddItem(Item);
466 Item->SpecialGenerationHandler();
475 const glterraincontentmap *GTerrainMap = RoomScript->GetGTerrainMap();
477 if (GTerrainMap) {
478 v2 GTerrainPos(Pos + *GTerrainMap->GetPos());
479 const contentscript<glterrain> *GTerrainScript;
481 for (int x = 0; x < GTerrainMap->GetSize()->X; ++x) {
482 game::BusyAnimation();
483 for (int y = 0; y < GTerrainMap->GetSize()->Y; ++y) {
484 if (IsValidScript(GTerrainScript = GTerrainMap->GetContentScript(x, y))) {
485 lsquare *Square = Map[GTerrainPos.X+x][GTerrainPos.Y+y];
487 Square->ChangeGLTerrain(GTerrainScript->Instantiate());
488 if (GTerrainScript->IsInside()) {
489 if (*GTerrainScript->IsInside()) Square->Flags |= INSIDE; else Square->Flags &= ~INSIDE;
496 const olterraincontentmap *OTerrainMap = RoomScript->GetOTerrainMap();
498 if (OTerrainMap) {
499 v2 OTerrainPos(Pos + *OTerrainMap->GetPos());
500 const contentscript<olterrain> *OTerrainScript;
502 for (int x = 0; x < OTerrainMap->GetSize()->X; ++x) {
503 game::BusyAnimation();
504 for (int y = 0; y < OTerrainMap->GetSize()->Y; ++y) {
505 if (IsValidScript(OTerrainScript = OTerrainMap->GetContentScript(x, y))) {
506 olterrain *Terrain = OTerrainScript->Instantiate();
508 if (Terrain->AcceptsOffers()) {
509 //FIXME: make IsAltar()? for now only altars can accept offers
510 if (RoomClass->GetDivineMaster()) {
511 //if (Terrain->GetConfig() != RoomClass->GetDivineMaster()) ABORT("Random altar in room with DivineMaster!");
512 if (Terrain->GetConfig() != RoomClass->GetDivineMaster()) {
513 // force altar type
514 fprintf(stderr, "forced altar!\n");
515 delete Terrain;
516 Terrain = altar::Spawn(RoomClass->GetDivineMaster());
518 } else {
519 // no DivineMaster yet, assign it
520 fprintf(stderr, "spawned altar in room w/o divine master, assigning %d\n", Terrain->GetConfig());
521 RoomClass->SetDivineMaster(Terrain->GetConfig());
524 Map[OTerrainPos.X+x][OTerrainPos.Y+y]->ChangeOLTerrain(Terrain);
530 const std::list<squarescript> Square = RoomScript->GetSquare();
532 for (std::list<squarescript>::const_iterator i = Square.begin(); i != Square.end(); ++i) {
533 game::BusyAnimation();
534 const squarescript *Script = &*i;
535 const interval *ScriptTimes = Script->GetTimes();
536 int Times = ScriptTimes ? ScriptTimes->Randomize() : 1;
538 for (int t = 0; t < Times; ++t) {
539 v2 SquarePos;
541 if (Script->GetPosition()->GetRandom()) {
542 const rect *ScriptBorders = Script->GetPosition()->GetBorders();
543 rect Borders = ScriptBorders ? *ScriptBorders+Pos : rect(Pos, Pos+Size-v2(1, 1));
545 SquarePos = GetRandomSquare(0, Script->GetPosition()->GetFlags(), &Borders);
546 } else {
547 SquarePos = Pos+Script->GetPosition()->GetVector();
549 Map[SquarePos.X][SquarePos.Y]->ApplyScript(Script, RoomClass);
553 return true;
557 truth level::GenerateLanterns (int X, int Y, int SquarePos) const {
558 if (!(RAND()%7)) {
559 lantern *Lantern = lantern::Spawn();
560 Lantern->SignalSquarePositionChange(SquarePos);
561 Map[X][Y]->GetStack()->AddItem(Lantern);
562 return true;
564 return false;
568 void level::CreateRoomSquare (glterrain *GLTerrain, olterrain* OLTerrain, int X, int Y, int Room, int Flags) const {
569 Map[X][Y]->ChangeLTerrain(GLTerrain, OLTerrain);
570 FlagMap[X][Y] |= FORBIDDEN;
571 Map[X][Y]->SetRoomIndex(Room);
572 Map[X][Y]->AddFlags(Flags);
576 void level::GenerateMonsters () {
577 if (*LevelScript->GenerateMonsters() &&
578 game::GetTeam(MONSTER_TEAM)->GetEnabledMembers() < IdealPopulation &&
579 (MonsterGenerationInterval <= 1 || !RAND_N(MonsterGenerationInterval))) {
580 GenerateNewMonsters(1);
581 ++MonsterGenerationInterval;
586 void level::Save (outputfile &SaveFile) const {
587 area::Save(SaveFile);
588 SaveFile << Room << GlobalRainLiquid << GlobalRainSpeed;
590 for (int x = 0; x < XSize; ++x) {
591 for (int y = 0; y < YSize; ++y) {
592 Map[x][y]->Save(SaveFile);
596 SaveFile << Door << LevelMessage << IdealPopulation << MonsterGenerationInterval << Difficulty;
597 SaveFile << SunLightEmitation << SunLightDirection << AmbientLuminance << NightAmbientLuminance;
601 void level::Load (inputfile &SaveFile) {
602 game::SetIsGenerating(true);
603 game::SetIsLoading(true);
604 area::Load(SaveFile);
605 Map = reinterpret_cast<lsquare ***>(area::Map);
606 SaveFile >> Room;
607 GlobalRainLiquid = static_cast<liquid *>(ReadType(material *, SaveFile));
608 SaveFile >> GlobalRainSpeed;
610 if (GlobalRainLiquid) GlobalRainLiquid->SetVolumeNoSignals(0);
612 game::SetGlobalRainLiquid(GlobalRainLiquid);
613 game::SetGlobalRainSpeed(GlobalRainSpeed);
615 for (int x = 0; x < XSize; ++x) {
616 for (int y = 0; y < YSize; ++y) {
617 Map[x][y] = new lsquare(this, v2(x, y));
621 for (int x = 0; x < XSize; ++x) {
622 for (int y = 0; y < YSize; ++y) {
623 game::SetSquareInLoad(Map[x][y]);
624 Map[x][y]->Load(SaveFile);
625 Map[x][y]->CalculateNeighbourLSquares();
629 SaveFile >> Door >> LevelMessage >> IdealPopulation >> MonsterGenerationInterval >> Difficulty;
630 SaveFile >> SunLightEmitation >> SunLightDirection >> AmbientLuminance >> NightAmbientLuminance;
631 Alloc2D(NodeMap, XSize, YSize);
632 Alloc2D(WalkabilityMap, XSize, YSize);
634 for (int x = 0; x < XSize; ++x) {
635 for (int y = 0; y < YSize; ++y) {
636 if (!Map[x][y]->IsInside()) Map[x][y]->AmbientLuminance = AmbientLuminance;
637 NodeMap[x][y] = new node(x, y, Map[x][y]);
638 WalkabilityMap[x][y] = Map[x][y]->GetTheoreticalWalkability();
639 Map[x][y]->CalculateGroundBorderPartners();
640 Map[x][y]->CalculateOverBorderPartners();
644 SquareStack = new lsquare * [XSizeTimesYSize];
645 game::SetIsLoading(false);
646 game::SetIsGenerating(false);
650 void level::FiatLux () {
651 for (int x = 0; x < XSize; ++x) {
652 for (int y = 0; y < YSize; ++y) {
653 Map[x][y]->CalculateEmitation();
654 Map[x][y]->Emitate();
655 Map[x][y]->CalculateLuminance();
658 CheckSunLight();
662 void level::GenerateNewMonsters (int HowMany, truth ConsiderPlayer) {
663 for (int c1 = 0; c1 < HowMany; ++c1) {
664 v2 Pos;
665 character *Char = protosystem::BalancedCreateMonster(this);
666 // gum solution
667 Char->CalculateEnchantments();
668 for (int c2 = 0; c2 < 30; ++c2) {
669 Pos = GetRandomSquare(Char);
670 if (Pos == ERROR_V2) break;
671 lsquare *Square = GetLSquare(Pos);
672 if ((!Square->GetRoomIndex() || !Square->GetRoom()->DontGenerateMonsters()) &&
673 (!ConsiderPlayer || (Pos-PLAYER->GetPos()).GetManhattanLength() > 6)) break;
675 if (Pos != ERROR_V2) {
676 Char->PutTo(Pos);
677 Char->SetGenerationDanger(Difficulty);
678 Char->SignalGeneration();
679 Char->SignalNaturalGeneration();
680 ivantime Time;
681 game::GetTime(Time);
682 int Modifier = Time.Day-EDIT_ATTRIBUTE_DAY_MIN;
683 if (Modifier > 0) Char->EditAllAttributes(Modifier>>EDIT_ATTRIBUTE_DAY_SHIFT);
684 } else {
685 delete Char;
686 //k8:delete Char;
687 //Char->SendToHell(); // equipment
693 /* Example of the usage: GetRandomSquare() gives out a random walkable square */
694 v2 level::GetRandomSquare (ccharacter* Char, int Flags, const rect* Borders) const {
695 rect LocalBorder;
696 lsquare *LSquare;
698 if (Borders) {
699 LocalBorder = *Borders;
700 Borders = &LocalBorder;
701 LimitRef(LocalBorder.X1, 0, XSize-1);
702 LimitRef(LocalBorder.X2, 0, XSize-1);
703 LimitRef(LocalBorder.Y1, 0, YSize-1);
704 LimitRef(LocalBorder.Y2, 0, YSize-1);
706 for (int c = 0;; ++c) {
707 v2 Pos;
709 if (c == 50) Char = 0;
710 if (c == 500) return ERROR_V2;
711 if (Borders) {
712 Pos.X = Borders->X1+RAND()%(Borders->X2-Borders->X1+1);
713 Pos.Y = Borders->Y1+RAND()%(Borders->Y2-Borders->Y1+1);
714 } else {
715 Pos.X = 1+RAND()%(XSize-2);
716 Pos.Y = 1+RAND()%(YSize-2);
718 LSquare = Map[Pos.X][Pos.Y];
719 if (((Char ? Char->CanMoveOn(LSquare) : (LSquare->GetWalkability()&WALK)) != !(Flags&NOT_WALKABLE)) ||
720 ((Char ? Char->IsFreeForMe(LSquare) : !LSquare->GetCharacter()) != !(Flags&HAS_CHARACTER)) ||
721 (Flags&ATTACHABLE && FlagMap[Pos.X][Pos.Y]&FORBIDDEN) ||
722 (Flags&HAS_NO_OTERRAIN && LSquare->GetOTerrain()))
724 continue;
726 int RoomFlags = Flags&(IN_ROOM|NOT_IN_ROOM);
727 if ((RoomFlags == IN_ROOM && !LSquare->GetRoomIndex()) || (RoomFlags == NOT_IN_ROOM && LSquare->GetRoomIndex())) continue;
728 return Pos;
733 void level::ParticleTrail (v2 StartPos, v2 EndPos) {
734 if (StartPos.X != EndPos.X && StartPos.Y != EndPos.Y) {
735 ABORT("666th rule of thermodynamics - Particles don't move the way you want them to move.");
740 truth level::IsOnGround () const {
741 return *LevelScript->IsOnGround();
745 truth level::EarthquakesAffectTunnels () const {
746 return *LevelScript->EarthquakesAffectTunnels();
750 int level::GetLOSModifier () const {
751 return *LevelScript->GetLOSModifier();
755 void level::AddRoom (room *NewRoom) {
756 NewRoom->SetIndex(Room.size());
757 Room.push_back(NewRoom);
761 room *level::GetRoom (int I) const {
762 if (!I) ABORT("Access to room zero denied!");
763 return Room[I];
767 void level::Explosion (character *Terrorist, cfestring &DeathMsg, v2 Pos, int Strength, truth HurtNeutrals) {
768 static int StrengthLimit[6] = { 500, 250, 100, 50, 25, 10 };
769 int Size = 6;
771 for (int c = 0; c < 6; ++c) if (Strength >= StrengthLimit[c]) { Size = c; break; }
772 PlayerHurt.resize(PlayerHurt.size()+1);
773 explosion *Exp = new explosion;
774 Exp->Terrorist = Terrorist;
775 Exp->DeathMsg = DeathMsg;
776 Exp->Pos = Pos;
777 Exp->ID = NextExplosionID++;
778 Exp->Strength = Strength;
779 Exp->RadiusSquare = (8-Size)*(8-Size);
780 Exp->Size = Size;
781 Exp->HurtNeutrals = HurtNeutrals;
782 ExplosionQueue.push_back(Exp);
784 if (ExplosionQueue.size() == 1) {
785 unsigned int Explosions = 0;
787 while (Explosions != ExplosionQueue.size()) {
788 unsigned int c;
790 for (c = Explosions; c != ExplosionQueue.size(); c = TriggerExplosions(c)) ;
791 unsigned int NewExplosions = c;
792 for (c = Explosions; c < NewExplosions; ++c) {
793 if (PlayerHurt[c] && PLAYER->IsEnabled()) {
794 PLAYER->GetHitByExplosion(ExplosionQueue[c], ExplosionQueue[c]->Strength/((PLAYER->GetPos()-ExplosionQueue[c]->Pos).GetLengthSquare()+1));
797 Explosions = NewExplosions;
799 for (unsigned int c = 0; c < ExplosionQueue.size(); ++c) delete ExplosionQueue[c];
800 ExplosionQueue.clear();
801 PlayerHurt.clear();
802 NextExplosionID = 1;
803 for (int x = 0; x < XSize; ++x) {
804 for (int y = 0; y < YSize; ++y) {
805 Map[x][y]->LastExplosionID = 0;
812 truth level::DrawExplosion (const explosion *Explosion) const {
813 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) };
814 v2 BPos = game::CalculateScreenCoordinates(Explosion->Pos)-v2((6-Explosion->Size)<<4, (6-Explosion->Size)<<4);
815 v2 SizeVect(16+((6-Explosion->Size)<<5), 16+((6-Explosion->Size)<<5));
816 v2 OldSizeVect = SizeVect;
817 v2 PicPos = StrengthPicPos[Explosion->Size];
819 if (BPos.X < 0) {
820 if (BPos.X+SizeVect.X <= 0) return false;
821 PicPos.X -= BPos.X;
822 SizeVect.X += BPos.X;
823 BPos.X = 0;
826 if (BPos.Y < 0) {
827 if (BPos.Y+SizeVect.Y <= 0) return false;
828 PicPos.Y -= BPos.Y;
829 SizeVect.Y += BPos.Y;
830 BPos.Y = 0;
833 if (BPos.X >= RES.X || BPos.Y >= RES.Y) return false;
834 if (BPos.X+SizeVect.X > RES.X) SizeVect.X = RES.X-BPos.X;
835 if (BPos.Y+SizeVect.Y > RES.Y) SizeVect.Y = RES.Y-BPos.Y;
837 int Flags = RAND()&7;
838 blitdata BlitData = {
840 { PicPos.X, PicPos.Y },
841 { 0, 0 },
842 { SizeVect.X, SizeVect.Y },
843 { 0 },
844 TRANSPARENT_COLOR,
848 if (!Flags || SizeVect != OldSizeVect) {
849 BlitData.Bitmap = DOUBLE_BUFFER;
850 BlitData.Dest = BPos;
851 BlitData.Luminance = ivanconfig::GetContrastLuminance();
852 igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
853 } else {
854 /* Cache these */
855 bitmap ExplosionPic(SizeVect);
856 ExplosionPic.ActivateFastFlag();
857 BlitData.Bitmap = &ExplosionPic;
858 BlitData.Flags = Flags;
859 igraph::GetSymbolGraphic()->NormalBlit(BlitData);
860 BlitData.Bitmap = DOUBLE_BUFFER;
861 BlitData.Dest = BPos;
862 BlitData.Src.X = BlitData.Src.Y = 0;
863 BlitData.Luminance = ivanconfig::GetContrastLuminance();
864 ExplosionPic.LuminanceMaskedBlit(BlitData);
867 return true;
871 struct explosioncontroller {
872 static truth Handler (int x, int y) {
873 lsquare *Square = Map[x][y];
874 Square->GetHitByExplosion(CurrentExplosion);
875 return Square->IsFlyable();
877 static lsquare ***Map;
878 static explosion *CurrentExplosion;
882 lsquare ***explosioncontroller::Map;
883 explosion *explosioncontroller::CurrentExplosion;
886 int level::TriggerExplosions (int MinIndex) {
887 int LastExplosion = ExplosionQueue.size();
888 int NotSeen = 0;
889 int c;
891 for (c = MinIndex; c < LastExplosion; ++c) {
892 int EmitChange = Min(50+ExplosionQueue[c]->Strength, 255);
893 GetLSquare(ExplosionQueue[c]->Pos)->SetTemporaryEmitation(MakeRGB24(EmitChange, EmitChange, EmitChange));
894 if (!GetSquare(ExplosionQueue[c]->Pos)->CanBeSeenByPlayer(true)) ++NotSeen;
896 if (NotSeen) {
897 if (NotSeen == 1) ADD_MESSAGE("You hear an explosion."); else ADD_MESSAGE("You hear explosions.");
899 game::DrawEverythingNoBlit();
900 truth Drawn = false;
901 for (c = MinIndex; c < LastExplosion; ++c) if (DrawExplosion(ExplosionQueue[c])) Drawn = true;
902 if (Drawn) {
903 graphics::BlitDBToScreen();
904 game::GetCurrentArea()->SendNewDrawRequest();
905 clock_t StartTime = clock();
906 while(clock()-StartTime < 0.3*CLOCKS_PER_SEC);
908 for (c = MinIndex; c < LastExplosion; ++c) {
909 explosion *Explosion = ExplosionQueue[c];
910 int Radius = 8-Explosion->Size;
911 game::SetPlayerWasHurtByExplosion(false);
912 explosioncontroller::Map = Map;
913 explosioncontroller::CurrentExplosion = Explosion;
914 rect Rect;
915 femath::CalculateEnvironmentRectangle(Rect, GetBorder(), Explosion->Pos, Radius);
916 for (int x = Rect.X1; x <= Rect.X2; ++x) {
917 mapmath<explosioncontroller>::DoLine(Explosion->Pos.X, Explosion->Pos.Y, x, Rect.Y1);
918 mapmath<explosioncontroller>::DoLine(Explosion->Pos.X, Explosion->Pos.Y, x, Rect.Y2);
920 for (int y = Rect.Y1+1; y < Rect.Y2; ++y) {
921 mapmath<explosioncontroller>::DoLine(Explosion->Pos.X, Explosion->Pos.Y, Rect.X1, y);
922 mapmath<explosioncontroller>::DoLine(Explosion->Pos.X, Explosion->Pos.Y, Rect.X2, y);
924 PlayerHurt[c] = game::PlayerWasHurtByExplosion();
925 if (GetLSquare(Explosion->Pos)->IsFlyable()) GetLSquare(Explosion->Pos)->AddSmoke(gas::Spawn(SMOKE, 1000));
927 for (c = MinIndex; c < LastExplosion; ++c) GetLSquare(ExplosionQueue[c]->Pos)->SetTemporaryEmitation(0);
928 return LastExplosion;
932 truth level::CollectCreatures (charactervector &CharacterArray, character* Leader, truth AllowHostiles) {
933 if (!AllowHostiles) {
934 for (int c = 0; c < game::GetTeams(); ++c) {
935 if (Leader->GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) {
936 for (std::list<character*>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
937 if ((*i)->IsEnabled() && Leader->CanBeSeenBy(*i) && Leader->SquareUnderCanBeSeenBy(*i, true) && (*i)->CanFollow()) {
938 ADD_MESSAGE("You can't escape when there are hostile creatures nearby.");
939 return false;
946 truth TakeAll = true;
948 for (int c = 0; c < game::GetTeams(); ++c) {
949 if (game::GetTeam(c)->GetEnabledMembers() && Leader->GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) {
950 TakeAll = false;
951 break;
955 for (int c = 0; c < game::GetTeams(); ++c) {
956 if (game::GetTeam(c) == Leader->GetTeam() || Leader->GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE) {
957 for (std::list<character*>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
958 if ((*i)->IsEnabled() && *i != Leader &&
959 (TakeAll || (Leader->CanBeSeenBy(*i) && Leader->SquareUnderCanBeSeenBy(*i, true))) &&
960 (*i)->CanFollow() && (*i)->GetCommandFlags()&FOLLOW_LEADER)
962 if ((*i)->GetAction() && (*i)->GetAction()->IsVoluntary()) (*i)->GetAction()->Terminate(false);
963 if (!(*i)->GetAction()) {
964 ADD_MESSAGE("%s follows you.", (*i)->CHAR_NAME(DEFINITE));
965 CharacterArray.push_back(*i);
966 (*i)->Remove();
973 return true;
977 void level::Draw (truth AnimationDraw) const {
978 cint XMin = Max(game::GetCamera().X, 0);
979 cint YMin = Max(game::GetCamera().Y, 0);
980 cint XMax = Min(XSize, game::GetCamera().X+game::GetScreenXSize());
981 cint YMax = Min(YSize, game::GetCamera().Y+game::GetScreenYSize());
982 culong LOSTick = game::GetLOSTick();
983 blitdata BlitData = {
984 DOUBLE_BUFFER,
985 { 0, 0 },
986 { 0, 0 },
987 { TILE_SIZE, TILE_SIZE },
988 { 0 },
989 TRANSPARENT_COLOR,
990 ALLOW_ANIMATE|ALLOW_ALPHA
993 if (!game::GetSeeWholeMapCheatMode()) {
994 if (!AnimationDraw) {
995 for (int x = XMin; x < XMax; ++x) {
996 BlitData.Dest = game::CalculateScreenCoordinates(v2(x, YMin));
997 lsquare **SquarePtr = &Map[x][YMin];
998 for (int y = YMin; y < YMax; ++y, ++SquarePtr, BlitData.Dest.Y += TILE_SIZE) {
999 const lsquare* Square = *SquarePtr;
1000 culong LastSeen = Square->LastSeen;
1001 if (LastSeen == LOSTick) Square->Draw(BlitData);
1002 else if ((Square->Flags&STRONG_BIT) || LastSeen == LOSTick-2) Square->DrawMemorized(BlitData);
1005 } else {
1006 for (int x = XMin; x < XMax; ++x) {
1007 BlitData.Dest = game::CalculateScreenCoordinates(v2(x, YMin));
1008 lsquare **SquarePtr = &Map[x][YMin];
1009 for (int y = YMin; y < YMax; ++y, ++SquarePtr, BlitData.Dest.Y += TILE_SIZE) {
1010 const lsquare *Square = *SquarePtr;
1011 if (Square->LastSeen == LOSTick) Square->Draw(BlitData);
1012 else if (Square->Flags&STRONG_BIT) Square->DrawMemorized(BlitData);
1013 else {
1014 ccharacter *C = Square->Character;
1015 if (C) {
1016 if (C->CanBeSeenByPlayer()) Square->DrawMemorizedCharacter(BlitData); else Square->DrawMemorized(BlitData);
1022 } else {
1023 for (int x = XMin; x < XMax; ++x) {
1024 BlitData.Dest = game::CalculateScreenCoordinates(v2(x, YMin));
1025 lsquare **SquarePtr = &Map[x][YMin];
1026 for (int y = YMin; y < YMax; ++y, ++SquarePtr, BlitData.Dest.Y += TILE_SIZE) (*SquarePtr)->Draw(BlitData);
1032 v2 level::GetEntryPos (ccharacter *Char, int I) const {
1033 if (I == FOUNTAIN) {
1034 std::vector<v2> Fountains;
1035 for (int x = 0; x < XSize; ++x) {
1036 for (int y = 0; y < YSize; ++y) {
1037 if (GetLSquare(x,y)->GetOLTerrain() && GetLSquare(x,y)->GetOLTerrain()->IsFountainWithWater()) Fountains.push_back(v2(x,y));
1040 if (Fountains.empty()) return GetRandomSquare();
1041 return Fountains[RAND_N(Fountains.size())];
1043 std::map<int, v2>::const_iterator i = EntryMap.find(I);
1044 return (i == EntryMap.end() ? GetRandomSquare(Char) : i->second);
1048 void level::GenerateRectangularRoom (std::vector<v2> &OKForDoor, std::vector<v2> &Inside,
1049 std::vector<v2> &Border, const roomscript *RoomScript, room *RoomClass, v2 Pos, v2 Size)
1051 const contentscript<glterrain> *GTerrain;
1052 const contentscript<olterrain> *OTerrain;
1054 if (*RoomScript->UseFillSquareWalls()) {
1055 GTerrain = LevelScript->GetFillSquare()->GetGTerrain();
1056 OTerrain = LevelScript->GetFillSquare()->GetOTerrain();
1057 } else {
1058 GTerrain = RoomScript->GetWallSquare()->GetGTerrain();
1059 OTerrain = RoomScript->GetWallSquare()->GetOTerrain();
1062 int Room = RoomClass->GetIndex();
1063 sLong Counter = 0;
1064 truth AllowLanterns = *RoomScript->GenerateLanterns();
1065 truth AllowWindows = *RoomScript->GenerateWindows();
1066 int x, y;
1067 int Shape = *RoomScript->GetShape();
1068 int Flags = ((GTerrain->IsInside() ? *GTerrain->IsInside() : *RoomScript->IsInside()) ? INSIDE : 0);
1071 if ((Shape == ROUND_CORNERS || Shape == MAZE_ROOM) && (Size.X < 5 || Size.Y < 5)) Shape = RECTANGLE; /* No weird shapes this way. */
1072 if ((Shape == MAZE_ROOM) && (!(Size.X % 2) || !(Size.Y % 2))) Shape = RECTANGLE; /* Alas, no even numbered maze rooms allowed. */
1074 maze MazeRoom(Size.X, Size.Y);
1076 if (Shape == MAZE_ROOM) {
1077 MazeRoom.CreateMaze();
1080 for (x = Pos.X; x < Pos.X+Size.X; ++x, Counter += 2) {
1081 if (Shape == ROUND_CORNERS) {
1082 if (x == Pos.X) {
1083 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x+1, Pos.Y+1, Room, Flags);
1084 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x+1, Pos.Y+Size.Y-2, Room, Flags);
1085 Border.push_back(v2(x+1, Pos.Y+1));
1086 Border.push_back(v2(x+1, Pos.Y+Size.Y-2));
1087 continue;
1088 } else if (x == Pos.X+Size.X-1) {
1089 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x-1, Pos.Y+1, Room, Flags);
1090 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x-1, Pos.Y+Size.Y-2, Room, Flags);
1091 Border.push_back(v2(x-1, Pos.Y+1));
1092 Border.push_back(v2(x-1, Pos.Y+Size.Y-2));
1093 continue;
1096 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x, Pos.Y, Room, Flags);
1097 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x, Pos.Y+Size.Y-1, Room, Flags);
1098 if ((Shape == RECTANGLE && x != Pos.X && x != Pos.X+Size.X-1) ||
1099 (Shape == ROUND_CORNERS && x > Pos.X+1 && x < Pos.X+Size.X-2))
1101 OKForDoor.push_back(v2(x, Pos.Y));
1102 OKForDoor.push_back(v2(x, Pos.Y+Size.Y-1));
1103 if ((!AllowLanterns || !GenerateLanterns(x, Pos.Y, DOWN)) && AllowWindows) GenerateWindows(x, Pos.Y);
1104 if ((!AllowLanterns || !GenerateLanterns(x, Pos.Y+Size.Y-1, UP)) && AllowWindows) GenerateWindows(x, Pos.Y+Size.Y-1);
1106 Border.push_back(v2(x, Pos.Y));
1107 Border.push_back(v2(x, Pos.Y+Size.Y-1));
1110 game::BusyAnimation();
1112 for (y = Pos.Y+1; y < Pos.Y+Size.Y-1; ++y, Counter += 2) {
1113 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), Pos.X, y, Room, Flags);
1114 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), Pos.X+Size.X-1, y, Room, Flags);
1115 if (Shape == RECTANGLE || (Shape == ROUND_CORNERS && y != Pos.Y+1 && y != Pos.Y+Size.Y-2)) {
1116 OKForDoor.push_back(v2(Pos.X, y));
1117 OKForDoor.push_back(v2(Pos.X+Size.X-1, y));
1118 if ((!AllowLanterns || !GenerateLanterns(Pos.X, y, RIGHT)) && AllowWindows) GenerateWindows(Pos.X, y);
1119 if ((!AllowLanterns || !GenerateLanterns(Pos.X+Size.X-1, y, LEFT)) && AllowWindows) GenerateWindows(Pos.X+Size.X-1, y);
1121 Border.push_back(v2(Pos.X, y));
1122 Border.push_back(v2(Pos.X+Size.X-1, y));
1125 // Maze rooms only: put in the doors, in the corners.
1126 if (Shape == MAZE_ROOM) {
1127 int MazeDoors = RAND() % 4;
1128 switch (MazeDoors) {
1129 case 0:
1130 OKForDoor.push_back(v2(Pos.X, Pos.Y + 1));
1131 OKForDoor.push_back(v2(Pos.X + Size.X - 1, Pos.Y + Size.Y - 2));
1132 break;
1133 case 1:
1134 OKForDoor.push_back(v2(Pos.X + 1, Pos.Y));
1135 OKForDoor.push_back(v2(Pos.X + Size.X - 2, Pos.Y + Size.Y - 1));
1136 break;
1137 case 2:
1138 OKForDoor.push_back(v2(Pos.X + 1, Pos.Y + Size.Y - 1));
1139 OKForDoor.push_back(v2(Pos.X + Size.X - 2, Pos.Y));
1140 break;
1141 case 3:
1142 OKForDoor.push_back(v2(Pos.X, Pos.Y + Size.Y - 2));
1143 OKForDoor.push_back(v2(Pos.X + Size.X - 1, Pos.Y + 1));
1144 break;
1145 default:
1146 break;
1150 GTerrain = RoomScript->GetFloorSquare()->GetGTerrain();
1151 OTerrain = RoomScript->GetFloorSquare()->GetOTerrain();
1152 Counter = 0;
1153 Flags = ((GTerrain->IsInside() ? *GTerrain->IsInside() : *RoomScript->IsInside()) ? INSIDE : 0);
1155 for (x = Pos.X+1; x < Pos.X+Size.X-1; ++x) {
1156 for (y = Pos.Y+1; y < Pos.Y+Size.Y-1; ++y, ++Counter) {
1157 /* if not in the corner */
1158 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))) {
1159 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x, y, Room, Flags);
1160 Inside.push_back(v2(x,y));
1165 // Maze rooms only: put in the internal walls.
1166 if (Shape == MAZE_ROOM) {
1167 for (y = Pos.Y + 1; y < Pos.Y + Size.Y - 1; ++y) {
1168 for (x = Pos.X + 1; x < Pos.X + Size.X - 1; ++x, ++Counter) {
1169 // 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(?)).
1170 if (!MazeRoom.MazeKernel[(y-Pos.Y-1)*(Size.X-2)+(x-Pos.X-1)]) {
1171 if (*RoomScript->UseFillSquareWalls()) {
1172 GTerrain = LevelScript->GetFillSquare()->GetGTerrain();
1173 OTerrain = LevelScript->GetFillSquare()->GetOTerrain();
1174 } else {
1175 GTerrain = RoomScript->GetWallSquare()->GetGTerrain();
1176 OTerrain = RoomScript->GetWallSquare()->GetOTerrain();
1178 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x, y, Room, Flags);
1179 } else if (MazeRoom.MazeKernel[(y-Pos.Y-1)*(Size.X-2)+(x-Pos.X-1)]) {
1180 // Put in a floor
1181 GTerrain = RoomScript->GetFloorSquare()->GetGTerrain();
1182 OTerrain = RoomScript->GetFloorSquare()->GetOTerrain();
1183 CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x, y, Room, Flags);
1184 Inside.push_back(v2(x, y));
1192 void level::Reveal () {
1193 feuLong Tick = game::GetLOSTick();
1194 for (int x = 0; x < XSize; ++x) {
1195 for (int y = 0; y < YSize; ++y) {
1196 Map[x][y]->Reveal(Tick);
1202 void level::ParticleBeam (beamdata &Beam) {
1203 v2 CurrentPos = Beam.StartPos;
1204 if (Beam.Direction != YOURSELF) {
1205 for (int Length = 0; Length < Beam.Range; ++Length) {
1206 CurrentPos += game::GetMoveVector(Beam.Direction);
1207 if (!IsValidPos(CurrentPos)) break;
1208 lsquare *CurrentSquare = GetLSquare(CurrentPos);
1209 if (!CurrentSquare->IsFlyable()) {
1210 (CurrentSquare->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam);
1211 break;
1212 } else {
1213 CurrentSquare->DrawParticles(Beam.BeamColor);
1214 if ((CurrentSquare->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam)) break;
1217 } else {
1218 lsquare *Where = GetLSquare(CurrentPos);
1219 Where->DrawParticles(Beam.BeamColor);
1220 (Where->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam);
1225 /* Note: You will most likely need some help from supernatural entities to comprehend this code. Sorry. */
1226 void level::LightningBeam (beamdata &Beam) {
1227 v2 CurrentPos = Beam.StartPos;
1229 if (Beam.Direction == YOURSELF) {
1230 lsquare *Where = GetLSquare(CurrentPos);
1231 for (int c = 0; c < 4; ++c) Where->DrawLightning(v2(8, 8), Beam.BeamColor, YOURSELF);
1232 (Where->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam);
1233 return;
1236 v2 StartPos;
1238 switch (Beam.Direction) {
1239 case 0: StartPos = v2(15, 15); break;
1240 case 1: StartPos = v2(RAND()&15, 15); break;
1241 case 2: StartPos = v2(0, 15); break;
1242 case 3: StartPos = v2(15, RAND()&15); break;
1243 case 4: StartPos = v2(0, RAND()&15); break;
1244 case 5: StartPos = v2(15, 0); break;
1245 case 6: StartPos = v2(RAND()&15, 0); break;
1246 case 7: StartPos = v2(0, 0); break;
1247 default: StartPos = v2(0, 0); break;
1250 for (int Length = 0; Length < Beam.Range; ++Length) {
1251 CurrentPos += game::GetMoveVector(Beam.Direction);
1252 if (!IsValidPos(CurrentPos)) break;
1253 lsquare *CurrentSquare = GetLSquare(CurrentPos);
1254 if (!CurrentSquare->IsFlyable()) {
1255 if ((CurrentSquare->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam)) break;
1256 truth W1, W2;
1257 switch (Beam.Direction) {
1258 case 0:
1259 W1 = GetLSquare(CurrentPos+v2(1, 0))->IsFlyable();
1260 W2 = GetLSquare(CurrentPos+v2(0, 1))->IsFlyable();
1261 if (W1 == W2) {
1262 Beam.Direction = 7;
1263 } else if (W1) {
1264 ++CurrentPos.Y;
1265 Beam.Direction = 2;
1266 } else {
1267 ++CurrentPos.X;
1268 Beam.Direction = 5;
1270 break;
1271 case 1: Beam.Direction = 6; StartPos.Y = 0; break;
1272 case 2:
1273 W1 = GetLSquare(CurrentPos+v2(-1, 0))->IsFlyable();
1274 W2 = GetLSquare(CurrentPos+v2(0, 1))->IsFlyable();
1275 if (W1 == W2) {
1276 Beam.Direction = 5;
1277 } else if (W1) {
1278 ++CurrentPos.Y;
1279 Beam.Direction = 0;
1280 } else {
1281 --CurrentPos.X;
1282 Beam.Direction = 7;
1284 break;
1285 case 3: Beam.Direction = 4; StartPos.X = 0; break;
1286 case 4: Beam.Direction = 3; StartPos.X = 15; break;
1287 case 5:
1288 W1 = GetLSquare(CurrentPos+v2(1, 0))->IsFlyable();
1289 W2 = GetLSquare(CurrentPos+v2(0, -1))->IsFlyable();
1290 if (W1 == W2) {
1291 Beam.Direction = 2;
1292 } else if (W1) {
1293 --CurrentPos.Y;
1294 Beam.Direction = 7;
1295 } else {
1296 ++CurrentPos.X;
1297 Beam.Direction = 0;
1299 break;
1300 case 6: Beam.Direction = 1; StartPos.Y = 15; break;
1301 case 7:
1302 W1 = GetLSquare(CurrentPos+v2(-1, 0))->IsFlyable();
1303 W2 = GetLSquare(CurrentPos+v2(0, -1))->IsFlyable();
1304 if (W1 == W2) {
1305 Beam.Direction = 0;
1306 } else if (W1) {
1307 --CurrentPos.Y;
1308 Beam.Direction = 5;
1309 } else {
1310 --CurrentPos.X;
1311 Beam.Direction = 2;
1313 break;
1315 switch (Beam.Direction) {
1316 case 0: StartPos = v2(15, 15); break;
1317 case 2: StartPos = v2(0, 15); break;
1318 case 5: StartPos = v2(15, 0); break;
1319 case 7: StartPos = v2(0, 0); break;
1321 } else {
1322 StartPos = CurrentSquare->DrawLightning(StartPos, Beam.BeamColor, Beam.Direction);
1323 if ((CurrentSquare->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam)) break;
1329 void level::ShieldBeam (beamdata &Beam) {
1330 v2 Pos[3];
1331 switch (Beam.Direction) {
1332 case 0:
1333 Pos[0] = v2(-1, 0);
1334 Pos[1] = v2(-1, -1);
1335 Pos[2] = v2(0, -1);
1336 break;
1337 case 1:
1338 Pos[0] = v2(-1, -1);
1339 Pos[1] = v2(0, -1);
1340 Pos[2] = v2(1, -1);
1341 break;
1342 case 2:
1343 Pos[0] = v2(0, -1);
1344 Pos[1] = v2(1, -1);
1345 Pos[2] = v2(1, 0);
1346 break;
1347 case 3:
1348 Pos[0] = v2(-1, 1);
1349 Pos[1] = v2(-1, 0);
1350 Pos[2] = v2(-1, -1);
1351 break;
1352 case 4:
1353 Pos[0] = v2(1, -1);
1354 Pos[1] = v2(1, 0);
1355 Pos[2] = v2(1, 1);
1356 break;
1357 case 5:
1358 Pos[0] = v2(0, 1);
1359 Pos[1] = v2(-1, 1);
1360 Pos[2] = v2(-1, 0);
1361 break;
1362 case 6:
1363 Pos[0] = v2(1, 1);
1364 Pos[1] = v2(0, 1);
1365 Pos[2] = v2(-1, 1);
1366 break;
1367 case 7:
1368 Pos[0] = v2(1, 0);
1369 Pos[1] = v2(1, 1);
1370 Pos[2] = v2(0, 1);
1371 break;
1372 case 8:
1373 GetLSquare(Beam.StartPos)->DrawParticles(Beam.BeamColor);
1374 (GetLSquare(Beam.StartPos)->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam);
1375 return;
1376 default: return;
1378 for (int c = 0; c < 3; ++c) {
1379 if (IsValidPos(Beam.StartPos+Pos[c])) {
1380 GetLSquare(Beam.StartPos+Pos[c])->DrawParticles(Beam.BeamColor);
1381 (GetLSquare(Beam.StartPos+Pos[c])->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam);
1387 outputfile &operator << (outputfile &SaveFile, const level *Level) {
1388 Level->Save(SaveFile);
1389 return SaveFile;
1393 inputfile &operator >> (inputfile &SaveFile, level *&Level) {
1394 Level = new level;
1395 Level->Load(SaveFile);
1396 return SaveFile;
1400 void (level::*Beam[BEAM_STYLES]) (beamdata&) = {
1401 &level::ParticleBeam,
1402 &level::LightningBeam,
1403 &level::ShieldBeam
1407 void (level::*level::GetBeam(int I)) (beamdata &) {
1408 return Beam[I];
1412 v2 level::FreeSquareSeeker (ccharacter *Char, v2 StartPos, v2 Prohibited, int MaxDistance, truth AllowStartPos) const {
1413 int c;
1415 for (c = 0; c < 8; ++c) {
1416 v2 Pos = StartPos+game::GetMoveVector(c);
1417 if (IsValidPos(Pos) && Char->CanMoveOn(GetLSquare(Pos)) && Char->IsFreeForMe(GetLSquare(Pos)) && Pos != Prohibited && (AllowStartPos || !Char->PlaceIsIllegal(Pos, Prohibited))) {
1418 return Pos;
1422 if (MaxDistance) {
1423 for (c = 0; c < 8; ++c) {
1424 v2 Pos = StartPos+game::GetMoveVector(c);
1425 if (IsValidPos(Pos)) {
1426 if (Char->CanMoveOn(GetLSquare(Pos)) && Pos != Prohibited) {
1427 Pos = FreeSquareSeeker(Char, Pos, Prohibited, MaxDistance-1, AllowStartPos);
1428 if (Pos != ERROR_V2) return Pos;
1434 return ERROR_V2;
1438 /* Returns ERROR_V2 if no free square was found */
1439 v2 level::GetNearestFreeSquare (ccharacter *Char, v2 StartPos, truth AllowStartPos) const {
1440 if (AllowStartPos && Char->CanMoveOn(GetLSquare(StartPos)) && Char->IsFreeForMe(GetLSquare(StartPos))) return StartPos;
1442 int c;
1444 for (c = 0; c < 8; ++c) {
1445 v2 Pos = StartPos+game::GetMoveVector(c);
1446 if (IsValidPos(Pos) && Char->CanMoveOn(GetLSquare(Pos)) && Char->IsFreeForMe(GetLSquare(Pos)) && (AllowStartPos || !Char->PlaceIsIllegal(Pos, StartPos))) {
1447 return Pos;
1451 for (int Dist = 0; Dist < 5; ++Dist) {
1452 for (c = 0; c < 8; ++c) {
1453 v2 Pos = StartPos+game::GetMoveVector(c);
1454 if (IsValidPos(Pos) && Char->CanMoveOn(GetLSquare(Pos))) {
1455 Pos = FreeSquareSeeker(Char, Pos, StartPos, Dist, AllowStartPos);
1456 if (Pos != ERROR_V2) return Pos;
1461 return ERROR_V2;
1465 v2 level::GetFreeAdjacentSquare (ccharacter *Char, v2 StartPos, truth AllowCharacter) const {
1466 int PossibleDir[8];
1467 int Index = 0;
1468 lsquare *Origo = GetLSquare(StartPos);
1469 for (int d = 0; d < 8; ++d) {
1470 lsquare *Square = Origo->GetNeighbourLSquare(d);
1471 if (Square && Char->CanMoveOn(Square) && (AllowCharacter || Char->IsFreeForMe(Square))) PossibleDir[Index++] = d;
1473 return (Index ? StartPos+game::GetMoveVector(PossibleDir[RAND()%Index]) : ERROR_V2);
1477 void (level::*level::GetBeamEffectVisualizer(int I)) (const fearray<lsquare*>&, col16) const {
1478 static void (level::*Visualizer[BEAM_STYLES])(const fearray<lsquare*>&, col16) const = { &level::ParticleVisualizer, &level::LightningVisualizer, &level::ParticleVisualizer };
1479 return Visualizer[I];
1483 void level::ParticleVisualizer (const fearray<lsquare *> &Stack, col16 BeamColor) const {
1484 clock_t StartTime = clock();
1485 game::DrawEverythingNoBlit();
1486 for (fearray<lsquare*>::sizetype c = 0; c < Stack.Size; ++c) Stack[c]->DrawParticles(BeamColor, false);
1487 graphics::BlitDBToScreen();
1488 while (clock()-StartTime < 0.05*CLOCKS_PER_SEC) {}
1492 void level::LightningVisualizer (const fearray<lsquare *> &Stack, col16 BeamColor) const {
1493 clock_t StartTime = clock();
1494 game::DrawEverythingNoBlit();
1495 for (fearray<lsquare*>::sizetype c = 0; c < Stack.Size; ++c) Stack[c]->DrawLightning(v2(8, 8), BeamColor, YOURSELF, false);
1496 graphics::BlitDBToScreen();
1497 while (clock()-StartTime < 0.05*CLOCKS_PER_SEC) {}
1501 truth level::PreProcessForBone () {
1502 if (!*LevelScript->CanGenerateBone()) return false;
1503 /* Gum solution */
1504 game::SetQuestMonstersFound(0);
1505 for (int x = 0; x < XSize; ++x) {
1506 for (int y = 0; y < YSize; ++y) {
1507 Map[x][y]->PreProcessForBone();
1510 //int DungeonIndex = GetDungeon()->GetIndex();
1511 /*k8: this logic allows to generate bones on special levels; i don't quite understand it, but... */
1512 /* ok, let's use level tags for special levels, instead of hardcoding the numbers */
1514 return
1515 !(DungeonIndex == ELPURI_CAVE && Index == IVAN_LEVEL && game::GetQuestMonstersFound() < 5) &&
1516 (game::GetQuestMonstersFound() ||
1517 ((DungeonIndex != UNDER_WATER_TUNNEL || Index != VESANA_LEVEL) &&
1518 (DungeonIndex != ELPURI_CAVE || (Index != ENNER_BEAST_LEVEL && Index != DARK_LEVEL)) &&
1519 (DungeonIndex != ALIEN_VESSEL || Index != ALIENQUEEN_LEVEL)));
1521 // Ivan the Communist level?
1522 if (IsGCIvanLevel()) return (game::GetQuestMonstersFound() >= 5); // why 5?
1523 // other special level conditions are in effect only if no quest mosters are found yet
1524 if (game::GetQuestMonstersFound()) return true; // see above
1525 // other special levels
1526 cfestring *tag = GetLevelScript()->GetTag();
1527 if (!tag) return true;
1528 return (tag->Find("(!)") == festring::NPos);
1531 // check for special levels
1532 truth level::IsGCIvanLevel () const { return (GetLevelScript()->GetTag() ? *(GetLevelScript()->GetTag()) == "GCIvanLevel(!)" : false); }
1534 truth level::IsUTVesanaLevel () const { return (GetTag() ? *GetTag() == "UTVesanaLevel(!)" : false); }
1535 truth level::IsGCEnnerLevel () const { return (GetTag() ? *GetTag() == "GCEnnerBeastLevel(!)" : false); }
1536 truth level::IsGCElpuriLevel () const { return (GetTag() ? *GetTag() == "GCElpuriLevel(!)" : false); }
1537 truth level::IsGCOreeLevel () const { return (GetTag() ? *GetTag() == "GCOreeLair(!)" : false); }
1538 truth level::IsSolicitusLevel () const { return (GetTag() ? *GetTag() == "SolicitusLevel(!)" : false); }
1539 truth level::IsAlienQueenLevel () const { return (GetTag() ? *GetTag() == "AlienQueenLevel(!)" : false); }
1543 truth level::PostProcessForBone () {
1544 game::SetTooGreatDangerFound(false);
1545 double DangerSum = 0;
1546 int Enemies = 0;
1547 for (int x = 0; x < XSize; ++x) {
1548 for (int y = 0; y < YSize; ++y) {
1549 Map[x][y]->PostProcessForBone(DangerSum, Enemies);
1552 return !(game::TooGreatDangerFound() || (Enemies && DangerSum/Enemies > Difficulty*10));
1556 void level::FinalProcessForBone () {
1557 for (int x = 0; x < XSize; ++x) {
1558 for (int y = 0; y < YSize; ++y) {
1559 Map[x][y]->FinalProcessForBone();
1562 for (uInt c = 1; c < Room.size(); ++c) Room[c]->FinalProcessForBone();
1566 truth level::GenerateDungeon (int Index) {
1567 cfestring *Msg = LevelScript->GetLevelMessage();
1568 if (Msg) LevelMessage = *Msg;
1570 if (*LevelScript->GenerateMonsters()) {
1571 MonsterGenerationInterval = *LevelScript->GetMonsterGenerationIntervalBase() + *LevelScript->GetMonsterGenerationIntervalDelta()*Index;
1572 IdealPopulation = *LevelScript->GetMonsterAmountBase() + *LevelScript->GetMonsterAmountDelta()*Index;
1575 Difficulty = 0.001*(*LevelScript->GetDifficultyBase() + *LevelScript->GetDifficultyDelta()*Index);
1576 EnchantmentMinusChance = *LevelScript->GetEnchantmentMinusChanceBase() + *LevelScript->GetEnchantmentMinusChanceDelta()*Index;
1577 EnchantmentPlusChance = *LevelScript->GetEnchantmentPlusChanceBase() + *LevelScript->GetEnchantmentPlusChanceDelta()*Index;
1578 const contentscript<glterrain> *GTerrain = LevelScript->GetFillSquare()->GetGTerrain();
1579 const contentscript<olterrain> *OTerrain = LevelScript->GetFillSquare()->GetOTerrain();
1580 sLong Counter = 0;
1581 int x;
1583 game::BusyAnimation();
1585 for (x = 0; x < XSize; ++x) {
1586 for (int y = 0; y < YSize; ++y, ++Counter) {
1587 Map[x][y]->SetLTerrain(GTerrain->Instantiate(), OTerrain->Instantiate());
1591 uInt c;
1592 uInt Rooms = LevelScript->GetRooms()->Randomize();
1593 const std::list<roomscript>& RoomList = LevelScript->GetRoom();
1594 std::list<roomscript>::const_iterator Iterator = RoomList.begin();
1596 for (c = 0; c < Rooms; ++c) {
1597 game::BusyAnimation();
1598 if (c < RoomList.size()) {
1599 int i;
1600 for (i = 0; i < 1000; ++i) if (MakeRoom(&*Iterator)) break;
1601 if (i == 1000) { /*ABORT("Failed to place special room #%d!", c);*/ return false; }
1602 ++Iterator;
1603 } else {
1604 const roomscript *RoomScript = LevelScript->GetRoomDefault();
1605 for (int i = 0; i < 50; ++i) if (MakeRoom(RoomScript)) break;
1609 game::BusyAnimation();
1611 if (!*LevelScript->IgnoreDefaultSpecialSquares()) {
1612 /* Gum solution */
1613 const levelscript *LevelBase = static_cast<const levelscript*>(LevelScript->GetBase());
1614 if (LevelBase) {
1615 const std::list<squarescript> &Square = LevelBase->GetSquare();
1616 for (std::list<squarescript>::const_iterator i = Square.begin(); i != Square.end(); ++i) {
1617 game::BusyAnimation();
1618 ApplyLSquareScript(&*i);
1623 const std::list<squarescript> &Square = LevelScript->GetSquare();
1625 for (std::list<squarescript>::const_iterator i = Square.begin(); i != Square.end(); ++i) {
1626 game::BusyAnimation();
1627 ApplyLSquareScript(&*i);
1630 for (c = 0; c < AttachQueue.size(); ++c) AttachPos(AttachQueue[c].X, AttachQueue[c].Y);
1632 for (x = 0; x < XSize; ++x) {
1633 for (int y = 0; y < YSize; ++y) {
1634 Map[x][y]->CalculateGroundBorderPartners();
1635 Map[x][y]->CalculateOverBorderPartners();
1639 AttachQueue.clear();
1640 CreateItems(LevelScript->GetItems()->Randomize());
1642 return true;
1646 truth level::GenerateJungle () {
1647 int x, y;
1649 for (x = 0; x < XSize; ++x) {
1650 for (y = 0; y < YSize; ++y) {
1651 Map[x][y] = new lsquare(this, v2(x, y));
1652 Map[x][y]->SetLTerrain(solidterrain::Spawn(GRASS_TERRAIN), 0);
1656 for (;;) {
1657 CreateTunnelNetwork(1, 4, 20, 120, v2(0, YSize/2));
1658 CreateTunnelNetwork(1, 4, 20, 120, v2(XSize-1, YSize/2));
1660 for (int c = 0; c < 25; ++c) {
1661 v2 StartPos;
1663 switch(RAND_N(5)) {
1664 case 0: StartPos = v2(RAND_N(XSize), 0); break;
1665 case 1: StartPos = v2(RAND_N(XSize), YSize-1); break;
1666 case 2: StartPos = v2(0, RAND_N(YSize)); break;
1667 case 3: StartPos = v2(XSize-1, RAND_N(YSize)); break;
1668 case 4: StartPos = v2(RAND_N(XSize), RAND_N(YSize)); break;
1669 default: StartPos = v2(0, 0); break; /* k8: shut up the compiler */
1671 CreateTunnelNetwork(1,4,20, 120, StartPos);
1674 for (x = 0; x < XSize; ++x) {
1675 game::BusyAnimation();
1676 for (y = 0; y < YSize; ++y) {
1677 if(FlagMap[x][y] != PREFERRED) Map[x][y]->ChangeOLTerrain(wall::Spawn(BRICK_PROPAGANDA));
1678 else if(RAND_2) Map[x][y]->ChangeOLTerrain(decoration::Spawn(PALM));
1683 return true;
1687 void level::CreateTunnelNetwork (int MinLength, int MaxLength, int MinNodes, int MaxNodes, v2 StartPos) {
1688 v2 Pos = StartPos, Direction;
1689 int Length;
1690 game::BusyAnimation();
1691 FlagMap[Pos.X][Pos.Y] = PREFERRED;
1692 for (int c1 = 0; c1 < MaxNodes; ++c1) {
1693 Direction = game::GetBasicMoveVector(RAND()%4);
1694 Length = MinLength+RAND_N(MaxLength-MinLength+1);
1695 for (int c2 = 0; c2 < Length; ++c2) {
1696 if (IsValidPos(Direction+Pos)) {
1697 Pos += Direction;
1698 FlagMap[Pos.X][Pos.Y] = PREFERRED;
1699 } else {
1700 if (c1 >= MinNodes) return;
1701 break;
1708 truth level::GenerateDesert () {
1709 for (int x = 0; x < XSize; ++x) {
1710 for (int y = 0; y < YSize; ++y) {
1711 Map[x][y] = new lsquare(this, v2(x, y));
1712 Map[x][y]->SetLTerrain(solidterrain::Spawn(SAND_TERRAIN), 0);
1715 game::BusyAnimation();
1716 int AmountOfCactuses = RAND_N(10);
1717 int c;
1718 for (c = 0; c < AmountOfCactuses; ++c) Map[RAND_N(XSize)][RAND_N(YSize)]->ChangeOLTerrain(decoration::Spawn(CACTUS));
1719 int AmountOfBoulders = RAND_N(10);
1720 for (c = 0; c < AmountOfBoulders; ++c) Map[RAND_N(XSize)][RAND_N(YSize)]->ChangeOLTerrain(boulder::Spawn(1+RAND_2));
1722 return true;
1726 truth level::GenerateSteppe () {
1727 for (int x = 0; x < XSize; ++x) {
1728 for (int y = 0; y < YSize; ++y) {
1729 Map[x][y] = new lsquare(this, v2(x, y));
1730 Map[x][y]->SetLTerrain(solidterrain::Spawn(GRASS_TERRAIN), 0);
1733 game::BusyAnimation();
1734 int c;
1735 int AmountOfBoulders = RAND_N(20)+5;
1736 for (c = 0; c < AmountOfBoulders; ++c) Map[RAND_N(XSize)][RAND_N(YSize)]->ChangeOLTerrain(boulder::Spawn(1+RAND_2));
1738 return true;
1742 truth level::GenerateLeafyForest () {
1743 for (int x = 0; x < XSize; ++x) {
1744 for (int y = 0; y < YSize; ++y) {
1745 Map[x][y] = new lsquare(this, v2(x, y));
1746 olterrain *OLTerrain;
1747 switch (RAND_4) {
1748 case 0: if (RAND_8) OLTerrain = decoration::Spawn(OAK); else OLTerrain = decoration::Spawn(TEAK); break;
1749 case 1: OLTerrain = decoration::Spawn(BIRCH); break;
1750 case 2: OLTerrain = 0; if (!RAND_4) OLTerrain = boulder::Spawn(1+RAND_2); if (!RAND_4) OLTerrain = boulder::Spawn(3); break;
1751 default: OLTerrain = 0;
1753 Map[x][y]->SetLTerrain(solidterrain::Spawn(GRASS_TERRAIN), OLTerrain);
1757 return true;
1761 truth level::GenerateEvergreenForest () {
1762 for (int x = 0; x < XSize; ++x) {
1763 for (int y = 0; y < YSize; ++y) {
1764 Map[x][y] = new lsquare(this, v2(x, y));
1765 olterrain *OLTerrain = 0;
1766 switch (RAND_4) {
1767 case 0: if (RAND_2) OLTerrain = decoration::Spawn(PINE); break;
1768 case 1: OLTerrain = decoration::Spawn(FIR); break;
1769 case 2: if (!RAND_4) OLTerrain = boulder::Spawn(1+RAND_2); if (!RAND_4) OLTerrain = boulder::Spawn(3); break;
1771 Map[x][y]->SetLTerrain(solidterrain::Spawn(GRASS_TERRAIN), OLTerrain);
1775 return true;
1779 truth level::GenerateTundra () {
1780 for (int x = 0; x < XSize; ++x) {
1781 for (int y = 0; y < YSize; ++y) {
1782 Map[x][y] = new lsquare(this, v2(x, y));
1783 Map[x][y]->SetLTerrain(solidterrain::Spawn(SNOW_TERRAIN), 0);
1786 game::BusyAnimation();
1787 int c;
1788 int AmountOfBoulders = RAND_N(20)+8;
1789 for (c = 0; c < AmountOfBoulders; ++c) Map[RAND_N(XSize)][RAND_N(YSize)]->ChangeOLTerrain(boulder::Spawn(SNOW_BOULDER));
1790 int AmountOfDwarfBirches = RAND_N(10);
1791 for (c = 0; c < AmountOfDwarfBirches; ++c) Map[RAND_N(XSize)][RAND_N(YSize)]->ChangeOLTerrain(decoration::Spawn(DWARF_BIRCH));
1793 return true;
1797 truth level::GenerateGlacier () {
1798 int x, y;
1800 for (x = 0; x < XSize; ++x) {
1801 for (y = 0; y < YSize; ++y) {
1802 Map[x][y] = new lsquare(this, v2(x, y));
1803 Map[x][y]->SetLTerrain(solidterrain::Spawn(SNOW_TERRAIN), 0);
1807 int AmountOfBoulders = RAND_N(20)+5;
1809 for (int c = 0; c < AmountOfBoulders; ++c)
1810 Map[RAND_N(XSize)][RAND_N(YSize)]->ChangeOLTerrain(boulder::Spawn(SNOW_BOULDER));
1812 for (;;) {
1813 CreateTunnelNetwork(1,4,20, 120, v2(0,YSize/2));
1814 CreateTunnelNetwork(1,4,20, 120, v2(XSize-1,YSize/2));
1816 for (int c = 0; c < 20; ++c) {
1817 v2 StartPos;
1818 switch(RAND_N(5)) {
1819 case 0: StartPos = v2(RAND_N(XSize), 0); break;
1820 case 1: StartPos = v2(RAND_N(XSize), YSize-1); break;
1821 case 2: StartPos = v2(0, RAND_N(YSize)); break;
1822 case 3: StartPos = v2(XSize-1, RAND_N(YSize)); break;
1823 case 4: StartPos = v2(RAND_N(XSize), RAND_N(YSize)); break;
1824 default: StartPos = v2(0, 0); break; /* k8: shut up the compiler */
1826 CreateTunnelNetwork(1,4,20, 120, StartPos);
1829 for (x = 0; x < XSize; ++x)
1830 for (y = 0; y < YSize; ++y)
1831 if(FlagMap[x][y] != PREFERRED) FlagMap[x][y] |= RAND_2?ICE_TERRAIN:STONE_TERRAIN;
1833 for (x = 0; x < XSize; ++x) {
1834 game::BusyAnimation();
1835 for (y = 0; y < YSize; ++y) {
1836 if (!(FlagMap[x][y]&PREFERRED)) {
1837 int SquaresAround = 0;
1838 int IceAround = 0;
1839 for (int d = 0; d < 8; ++d) {
1840 v2 Pos = v2(x,y)+game::GetMoveVector(d);
1841 if (IsValidPos(Pos) && !(FlagMap[Pos.X][Pos.Y]&PREFERRED)) {
1842 ++SquaresAround;
1843 if(FlagMap[Pos.X][Pos.Y]&ICE_TERRAIN) ++IceAround;
1846 if (IceAround > SquaresAround/2) FlagMap[x][y] = ICE_TERRAIN;
1847 else FlagMap[x][y] = STONE_TERRAIN;
1852 for (x = 0; x < XSize; ++x) {
1853 for (y = 0; y < YSize; ++y) {
1854 if (!(FlagMap[x][y]&PREFERRED)) {
1855 if(FlagMap[x][y]&ICE_TERRAIN) GetLSquare(x,y)->ChangeOLTerrain(wall::Spawn(ICE_WALL));
1856 else GetLSquare(x,y)->ChangeOLTerrain(wall::Spawn(STONE_WALL));
1861 break; // Doesn't yet check path in any way
1864 return true;
1868 bool nodepointerstorer::operator < (const nodepointerstorer &N) const {
1869 /* In the non-euclidean geometry of IVAN, certain very curved paths are as long as straight ones.
1870 However, they are so ugly that it is best to prefer routes with as few diagonal moves as
1871 possible without lengthening the travel. */
1872 if (Node->TotalDistanceEstimate != N.Node->TotalDistanceEstimate) {
1873 return (Node->TotalDistanceEstimate > N.Node->TotalDistanceEstimate);
1875 return (Node->Diagonals > N.Node->Diagonals);
1879 void node::CalculateNextNodes () {
1880 static const int TryOrder[8] = { 1, 3, 4, 6, 0, 2, 5, 7 };
1881 for (int d = 0; d < 8; ++d) {
1882 v2 NodePos = Pos+game::GetMoveVector(TryOrder[d]);
1883 if (NodePos.X >= 0 && NodePos.Y >= 0 && NodePos.X < XSize && NodePos.Y < YSize) {
1884 node *Node = NodeMap[NodePos.X][NodePos.Y];
1885 if (!Node->Processed && ((!SpecialMover && RequiredWalkability&WalkabilityMap[NodePos.X][NodePos.Y]) || (SpecialMover && SpecialMover->CanTheoreticallyMoveOn(Node->Square)) || NodePos == To)) {
1886 Node->Processed = true;
1887 Node->Distance = Distance+1;
1888 Node->Diagonals = Diagonals;
1889 if (d >= 4) ++Node->Diagonals;
1890 Node->Last = this;
1891 /* We use the heuristic max(abs(distance.x), abs(distance.y)) here,
1892 which is exact in the current geometry if the path is open */
1893 sLong Remaining = To.X-NodePos.X;
1894 if (Remaining < NodePos.X-To.X) Remaining = NodePos.X-To.X;
1895 if (Remaining < NodePos.Y-To.Y) Remaining = NodePos.Y-To.Y;
1896 if (Remaining < To.Y-NodePos.Y) Remaining = To.Y-NodePos.Y;
1897 Node->Remaining = Remaining;
1898 Node->TotalDistanceEstimate = Node->Distance+Node->Remaining;
1899 NodeQueue->push(nodepointerstorer(Node));
1906 /* Finds the shortest (but possibly not the shortest-looking) path between From and To
1907 if such exists. Returns a pointer to the node associated with the last square or zero if
1908 a route can't be found. Calling FindRoute again may invalidate the node, so you must
1909 store the path in another format ASAP. */
1910 node *level::FindRoute (v2 From, v2 To, const std::set<v2>& Illegal, int RequiredWalkability, ccharacter* SpecialMover) {
1911 node::NodeMap = NodeMap;
1912 node::RequiredWalkability = RequiredWalkability;
1913 node::SpecialMover = SpecialMover;
1914 node::To = To;
1915 node::WalkabilityMap = WalkabilityMap;
1916 node::XSize = XSize;
1917 node::YSize = YSize;
1919 if (!Illegal.empty() && Illegal.find(To) != Illegal.end()) return 0;
1921 for (int x = 0; x < XSize; ++x) {
1922 for (int y = 0; y < YSize; ++y) {
1923 NodeMap[x][y]->Processed = false;
1927 node *Node = NodeMap[From.X][From.Y];
1928 Node->Last = 0;
1929 Node->Processed = true;
1930 Node->Distance = 0;
1931 Node->Diagonals = 0;
1932 nodequeue NodeQueue;
1933 NodeQueue.push(nodepointerstorer(Node));
1934 node::NodeQueue = &NodeQueue;
1936 while (!NodeQueue.empty()) {
1937 Node = NodeQueue.top().Node;
1938 NodeQueue.pop();
1939 if (Node->Pos == To) return Node;
1940 if (Illegal.empty() || Illegal.find(Node->Pos) == Illegal.end()) Node->CalculateNextNodes();
1943 return 0;
1947 /* All items on ground are moved to the IVector and all characters to CVector */
1948 void level::CollectEverything (itemvector &IVector, charactervector &CVector) {
1949 for (int x = 0; x < XSize; ++x) {
1950 for (int y = 0; y < YSize; ++y) {
1951 lsquare *LS = Map[x][y];
1952 LS->GetStack()->MoveItemsTo(IVector, CENTER);
1953 character *C = LS->GetCharacter();
1954 if (C && !C->IsPlayer()) {
1955 C->Remove();
1956 CVector.push_back(C);
1963 void level::CreateGlobalRain (liquid *Liquid, v2 Speed) {
1964 GlobalRainLiquid = Liquid;
1965 GlobalRainSpeed = Speed;
1966 for (int x = 0; x < XSize; ++x) {
1967 for (int y = 0; y < YSize; ++y) {
1968 if (!Map[x][y]->IsInside()) Map[x][y]->AddRain(Liquid, Speed, MONSTER_TEAM, false);
1974 void level::CheckSunLight () {
1975 if (Index == 0 && GetDungeon()->GetIndex() == NEW_ATTNAM) {
1976 double Cos = cos(FPI*(game::GetTick()%48000)/24000.0);
1977 if (Cos > 0.01) {
1978 int E = int(100+Cos*30);
1979 SunLightEmitation = MakeRGB24(E, E, E);
1980 AmbientLuminance = MakeRGB24(E-6, E-6, E-6);
1981 } else {
1982 SunLightEmitation = 0;
1983 AmbientLuminance = NightAmbientLuminance;
1985 } else if (Index == 0 && GetDungeon()->GetIndex() == ATTNAM) {
1986 double Cos = cos(FPI*(game::GetTick()%48000)/24000.0);
1987 if (Cos > 0.41) {
1988 int E = int(100+(Cos-0.40)*40);
1989 SunLightEmitation = MakeRGB24(E, E, E);
1990 AmbientLuminance = MakeRGB24(E-8, E-8, E-8);
1991 } else {
1992 SunLightEmitation = 0;
1993 AmbientLuminance = NightAmbientLuminance;
1995 } else if (Index == 0 && GetDungeon()->GetIndex() == MUNTUO) {
1996 double Cos = cos(FPI*(game::GetTick()%48000)/24000.0);
1997 if (Cos > 0.21) {
1998 int E = int(100+(Cos-0.20)*30);
1999 SunLightEmitation = MakeRGB24(E, E, E);
2000 AmbientLuminance = MakeRGB24(E-4, E-4, E-4);
2001 } else {
2002 SunLightEmitation = 0;
2003 AmbientLuminance = NightAmbientLuminance;
2005 } else {
2006 return;
2008 SunLightDirection = game::GetSunLightDirectionVector();
2009 ChangeSunLight();
2013 void level::ChangeSunLight () {
2014 truth SunSet = game::IsDark(SunLightEmitation);
2015 feuLong c;
2016 for (c = 0; c < XSizeTimesYSize; ++c) Map[0][c]->RemoveSunLight();
2017 if (!SunSet) EmitSunBeams();
2018 for (c = 0; c < XSizeTimesYSize; ++c) {
2019 lsquare *Square = Map[0][c];
2020 if (Square->Flags&IS_TRANSPARENT) Square->CalculateSunLightLuminance(EMITTER_SQUARE_PART_BITS);
2021 if (!Square->IsInside()) Square->AmbientLuminance = AmbientLuminance;
2022 Square->SendSunLightSignals();
2024 for (c = 0; c < XSizeTimesYSize; ++c) Map[0][c]->CheckIfIsSecondarySunLightEmitter();
2028 void level::InitSquarePartEmitationTicks () {
2029 for (int x = 0; x < XSize; ++x) {
2030 for (int y = 0; y < YSize; ++y) {
2031 Map[x][y]->SquarePartEmitationTick = 0;
2037 truth level::GenerateWindows (int X, int Y) const {
2038 olterrain *Terrain = Map[X][Y]->GetOLTerrain();
2039 if (Terrain && Terrain->CreateWindowConfigurations() && !(RAND()%6)) {
2040 Terrain->SetConfig(Terrain->GetConfig()|WINDOW);
2041 Map[X][Y]->CalculateIsTransparent();
2042 return true;
2044 return false;
2048 struct sunbeamcontroller : public stackcontroller {
2049 static truth Handler (int, int);
2050 static void ProcessStack ();
2052 static feuLong ID;
2053 static int SunLightBlockHeight;
2054 static v2 SunLightBlockPos;
2055 static truth ReSunEmitation;
2059 feuLong sunbeamcontroller::ID;
2060 int sunbeamcontroller::SunLightBlockHeight;
2061 v2 sunbeamcontroller::SunLightBlockPos;
2062 truth sunbeamcontroller::ReSunEmitation;
2065 void level::ForceEmitterNoxify (const emittervector& Emitter) const {
2066 for (emittervector::const_iterator i = Emitter.begin(); i != Emitter.end(); ++i) {
2067 feuLong ID = i->ID;
2068 lsquare *Square = GetLSquare(ExtractPosFromEmitterID(ID));
2069 if (ID&SECONDARY_SUN_LIGHT) {
2070 Square->Noxify(Square->SecondarySunLightEmitation, SECONDARY_SUN_LIGHT);
2071 } else {
2072 Square->Noxify(Square->Emitation);
2078 void level::ForceEmitterEmitation (const emittervector &Emitter, const sunemittervector &SunEmitter, feuLong IDFlags) const {
2079 for (emittervector::const_iterator i = Emitter.begin(); i != Emitter.end(); ++i) {
2080 feuLong ID = i->ID;
2081 lsquare *Square = GetLSquare(ExtractPosFromEmitterID(ID));
2082 if (ID&SECONDARY_SUN_LIGHT) {
2083 Square->Emitate(Square->SecondarySunLightEmitation, SECONDARY_SUN_LIGHT|IDFlags);
2084 } else {
2085 Square->Emitate(Square->Emitation, IDFlags);
2089 stackcontroller::Map = Map;
2090 stackcontroller::Stack = SquareStack;
2091 stackcontroller::StackIndex = 0;
2092 stackcontroller::LevelXSize = XSize;
2093 stackcontroller::LevelYSize = YSize;
2094 sunbeamcontroller::ReSunEmitation = true;
2095 for (sunemittervector::const_iterator i = SunEmitter.begin(); i != SunEmitter.end(); ++i) {
2096 feuLong ID = (*i&~(EMITTER_SHADOW_BITS|EMITTER_SQUARE_PART_BITS))|RE_SUN_EMITATED, SourceFlags;
2097 int X, Y;
2098 if (ID&ID_X_COORDINATE) {
2099 X = (ID&EMITTER_IDENTIFIER_BITS)-(XSize<<3);
2100 Y = (ID&ID_BEGIN ? -1 : YSize);
2101 SourceFlags = (ID&ID_BEGIN ? SP_BOTTOM : SP_TOP);
2102 } else {
2103 X = (ID&ID_BEGIN ? -1 : XSize);
2104 Y = (ID&EMITTER_IDENTIFIER_BITS)-(YSize<<3);
2105 SourceFlags = (ID&ID_BEGIN ? SP_RIGHT : SP_LEFT);
2107 EmitSunBeam(v2(X, Y), ID, SourceFlags);
2109 sunbeamcontroller::ProcessStack();
2114 struct loscontroller : public tickcontroller, public stackcontroller {
2115 static truth Handler (int x, int y) {
2116 lsquare *Square = Map[x>>1][y>>1];
2117 culong SquareFlags = Square->Flags;
2118 if (SquareFlags&PERFECTLY_QUADRI_HANDLED) return true;
2119 if (!(SquareFlags&IN_SQUARE_STACK)) {
2120 Square->Flags |= IN_SQUARE_STACK;
2121 Stack[StackIndex++] = Square;
2123 if (SquareFlags&IS_TRANSPARENT) {
2124 Square->Flags |= PERFECTLY_QUADRI_HANDLED;
2125 return true;
2127 cint SquarePartIndex = (x&1)+((y&1)<<1);
2128 Square->SquarePartLastSeen = (Square->SquarePartLastSeen&~SquarePartTickMask[SquarePartIndex])|ShiftedTick[SquarePartIndex];
2129 return false;
2131 static feuLong &GetTickReference (int X, int Y) {
2132 return Map[X][Y]->SquarePartLastSeen;
2134 static void ProcessStack () {
2135 for (sLong c = 0; c < StackIndex; ++c) Stack[c]->SignalSeen(Tick);
2140 void level::UpdateLOS () {
2141 game::RemoveLOSUpdateRequest();
2142 stackcontroller::Map = Map;
2143 stackcontroller::Stack = SquareStack;
2144 stackcontroller::StackIndex = 0;
2145 tickcontroller::Tick = game::IncreaseLOSTick();
2146 tickcontroller::PrepareShiftedTick();
2147 int Radius = PLAYER->GetLOSRange();
2148 for (int c = 0; c < PLAYER->GetSquaresUnder(); ++c) {
2149 mapmath<loscontroller>::DoQuadriArea(PLAYER->GetPos(c).X, PLAYER->GetPos(c).Y, Radius*Radius, XSize, YSize);
2151 loscontroller::ProcessStack();
2152 if (PLAYER->StateIsActivated(INFRA_VISION)) {
2153 for (int c = 0; c < game::GetTeams(); ++c) {
2154 for (std::list<character*>::const_iterator i = game::GetTeam(c)->GetMember().begin(); i != game::GetTeam(c)->GetMember().end(); ++i) {
2155 if ((*i)->IsEnabled()) (*i)->SendNewDrawRequest();
2162 void level::EnableGlobalRain () {
2163 for (int x = 0; x < XSize; ++x) {
2164 for (int y = 0; y < YSize; ++y) {
2165 Map[x][y]->EnableGlobalRain();
2171 void level::DisableGlobalRain () {
2172 for (int x = 0; x < XSize; ++x) {
2173 for (int y = 0; y < YSize; ++y) {
2174 Map[x][y]->DisableGlobalRain();
2180 void level::InitLastSeen () {
2181 for (int x = 0; x < XSize; ++x) {
2182 for (int y = 0; y < YSize; ++y) {
2183 Map[x][y]->InitLastSeen();
2189 void level::EmitSunBeams () {
2190 stackcontroller::Map = Map;
2191 stackcontroller::LevelXSize = XSize;
2192 stackcontroller::LevelYSize = YSize;
2193 sunbeamcontroller::ReSunEmitation = false;
2194 v2 Dir = SunLightDirection;
2195 int x, y, X = 0, Y = 0, SourceFlags;
2196 feuLong IDFlags;
2197 /* Do not try to understand the logic behind the starting points of
2198 sunbeams. I determined the formulas by trial and error since all
2199 understandable loops produced strange shapes for the shadows of
2200 either small of large objects probably due to rounding errors
2201 made during line calculations. */
2202 if (!Dir.X || (Dir.Y && abs(Dir.Y) < abs(Dir.X))) {
2203 if (Dir.Y > 0) {
2204 Y = -1;
2205 SourceFlags = SP_BOTTOM;
2206 IDFlags = ID_X_COORDINATE|ID_BEGIN;
2207 } else {
2208 Y = YSize;
2209 SourceFlags = SP_TOP;
2210 IDFlags = ID_X_COORDINATE;
2212 } else {
2213 if (Dir.X > 0) {
2214 X = -1;
2215 SourceFlags = SP_RIGHT;
2216 IDFlags = ID_BEGIN;
2217 } else {
2218 X = XSize;
2219 SourceFlags = SP_LEFT;
2220 IDFlags = 0;
2223 if (!Dir.X) {
2224 int Index = XSize<<3;
2225 for (x = 0; x < XSize; ++x, ++Index) EmitSunBeam(v2(x, Y), Index|IDFlags, SourceFlags);
2226 } else if (!Dir.Y) {
2227 int Index = YSize<<3;
2228 for (y = 0; y < YSize; ++y, ++Index) EmitSunBeam(v2(X, y), Index|IDFlags, SourceFlags);
2229 } else if (abs(Dir.Y) < abs(Dir.X)) {
2230 int Index = Dir.X > 0 ? 0 : XSize<<3;
2231 int StartX = Dir.X > 0 ? -XSize<<3 : 0;
2232 int EndX = Dir.X > 0 ? XSize : (XSize<<3)+XSize;
2233 for (x = StartX; x < EndX; ++x, ++Index) EmitSunBeam(v2(x, Y), Index|IDFlags, SourceFlags);
2234 } else {
2235 int Index = Dir.Y > 0 ? 0 : YSize<<3;
2236 int StartY = Dir.Y > 0 ? -YSize<<3 : 0;
2237 int EndY = Dir.Y > 0 ? YSize : (YSize<<3)+YSize;
2238 for (y = StartY; y < EndY; ++y, ++Index) EmitSunBeam(v2(X, y), Index|IDFlags, SourceFlags);
2243 void level::EmitSunBeam (v2 S, feuLong ID, int SourceFlags) const {
2244 S <<= 1;
2245 v2 D = S+SunLightDirection;
2246 sunbeamcontroller::ID = ID;
2247 if (SourceFlags&SP_TOP_LEFT) {
2248 sunbeamcontroller::SunLightBlockHeight = 0;
2249 mapmath<sunbeamcontroller>::DoLine(S.X, S.Y, D.X, D.Y, SKIP_FIRST);
2251 if (SourceFlags&SP_TOP_RIGHT) {
2252 sunbeamcontroller::SunLightBlockHeight = 0;
2253 mapmath<sunbeamcontroller>::DoLine(S.X+1, S.Y, D.X+1, D.Y, SKIP_FIRST);
2255 if (SourceFlags&SP_BOTTOM_LEFT) {
2256 sunbeamcontroller::SunLightBlockHeight = 0;
2257 mapmath<sunbeamcontroller>::DoLine(S.X, S.Y+1, D.X, D.Y+1, SKIP_FIRST);
2259 if (SourceFlags&SP_BOTTOM_RIGHT) {
2260 sunbeamcontroller::SunLightBlockHeight = 0;
2261 mapmath<sunbeamcontroller>::DoLine(S.X+1, S.Y+1, D.X+1, D.Y+1, SKIP_FIRST);
2266 truth sunbeamcontroller::Handler (int x, int y) {
2267 int X = x>>1, Y = y>>1;
2269 if (X < 0 || Y < 0 || X >= LevelXSize || Y >= LevelYSize) {
2270 return (X >= -1 && X <= LevelXSize) || (Y >= -1 && Y <= LevelYSize);
2273 lsquare *Square = Map[X][Y];
2274 int SquarePartIndex = (x&1)+((y&1)<<1);
2276 if (SunLightBlockHeight && !Square->IsInside() && HypotSquare(x-SunLightBlockPos.X, y-SunLightBlockPos.Y) > SunLightBlockHeight) {
2277 SunLightBlockHeight = 0;
2280 if (!SunLightBlockHeight) {
2281 feuLong Flag = 1<<EMITTER_SQUARE_PART_SHIFT<<SquarePartIndex;
2282 Square->AddSunLightEmitter(ID|Flag);
2283 } else {
2284 feuLong Flags = ((1<<EMITTER_SQUARE_PART_SHIFT)|(1<<EMITTER_SHADOW_SHIFT))<<SquarePartIndex;
2285 Square->AddSunLightEmitter(ID|Flags);
2288 if (ReSunEmitation) {
2289 if (!(Square->Flags&IN_SQUARE_STACK)) Stack[StackIndex++] = Square;
2290 Square->Flags |= IN_SQUARE_STACK|CHECK_SUN_LIGHT_NEEDED;
2291 for (int d = 0; d < 8; ++d) {
2292 lsquare *Neighbour = Square->GetNeighbourLSquare(d);
2293 if (Neighbour && !(Neighbour->Flags&IN_SQUARE_STACK)) {
2294 Neighbour->Flags |= IN_SQUARE_STACK;
2295 Stack[StackIndex++] = Neighbour;
2300 if (!(Square->Flags&IS_TRANSPARENT) || (SunLightBlockHeight && Square->IsInside())) {
2301 /* This should depend on the square */
2302 SunLightBlockHeight = 81;
2303 SunLightBlockPos = v2(x, y);
2306 return true;
2310 void sunbeamcontroller::ProcessStack () {
2311 sLong c;
2312 for (c = 0; c < StackIndex; ++c) {
2313 lsquare *Square = Stack[c];
2314 if (Square->Flags&CHECK_SUN_LIGHT_NEEDED) {
2315 if (Square->Flags&IS_TRANSPARENT) Square->CalculateSunLightLuminance(EMITTER_SQUARE_PART_BITS);
2316 Square->SendSunLightSignals();
2317 Square->ZeroReSunEmitatedFlags();
2319 Square->Flags &= ~(IN_SQUARE_STACK|CHECK_SUN_LIGHT_NEEDED);
2321 for (c = 0; c < StackIndex; ++c) Stack[c]->CheckIfIsSecondarySunLightEmitter();
2325 int level::DetectMaterial (cmaterial *Material) {
2326 feuLong Tick = game::IncreaseLOSTick();
2327 int Squares = 0;
2328 for (int x = 0; x < XSize; ++x) {
2329 for (int y = 0; y < YSize; ++y) {
2330 lsquare *Square = Map[x][y];
2331 if (Square->DetectMaterial(Material)) {
2332 Square->Reveal(Tick, true);
2333 ++Squares;
2337 return Squares;
2341 void level::BlurMemory () {
2342 int x, y, SquareStackSize = 0;
2343 for (x = 0; x < XSize; ++x) {
2344 for (y = 0; y < YSize; ++y) {
2345 lsquare *Square = Map[x][y];
2346 if (Square->HasNoBorderPartners()) SquareStack[SquareStackSize++] = Square;
2349 for (x = 0; x < XSize; ++x) {
2350 for (y = 0; y < YSize; ++y) {
2351 lsquare *Square = Map[x][y];
2352 Square->Flags |= STRONG_NEW_DRAW_REQUEST|MEMORIZED_UPDATE_REQUEST|DESCRIPTION_CHANGE;
2353 if (Square->HasNoBorderPartners() && RAND()&1 && SquareStackSize) {
2354 Square->SwapMemorized(SquareStack[RAND()%SquareStackSize]);
2355 } else if (RAND()&1) {
2356 Square->DestroyMemorized();
2363 void level::CalculateLuminances () {
2364 for (int x = 0; x < XSize; ++x) {
2365 for (int y = 0; y < YSize; ++y) {
2366 lsquare *Square = Map[x][y];
2367 Square->CalculateLuminance();
2368 Square->Flags |= MEMORIZED_UPDATE_REQUEST|DESCRIPTION_CHANGE;
2374 struct areacontroller : public stackcontroller {
2375 static truth Handler (int x, int y) {
2376 if (x >= 0 && y >= 0 && x < LevelXSize && y < LevelYSize && HypotSquare(x-Center.X, y-Center.Y) <= RadiusSquare) {
2377 lsquare *Square = Map[x][y];
2378 if (!(Square->Flags&IN_SQUARE_STACK)) {
2379 Stack[StackIndex++] = Square;
2380 Square->Flags |= IN_SQUARE_STACK;
2381 return Square->IsFlyable();
2384 return false;
2386 static int GetStartX (int) { return Center.X; }
2387 static int GetStartY (int) { return Center.Y; }
2388 static sLong RadiusSquare;
2392 sLong areacontroller::RadiusSquare;
2395 int level::AddRadiusToSquareStack (v2 Center, sLong RadiusSquare) const {
2396 stackcontroller::Map = Map;
2397 stackcontroller::Stack = SquareStack;
2398 SquareStack[0] = GetLSquare(Center);
2399 stackcontroller::StackIndex = 1;
2400 stackcontroller::LevelXSize = XSize;
2401 stackcontroller::LevelYSize = YSize;
2402 stackcontroller::Center = Center;
2403 areacontroller::RadiusSquare = RadiusSquare;
2404 mapmath<areacontroller>::DoArea();
2405 return stackcontroller::StackIndex;
2409 /* Any fountain is good that is not dry and is NOT Except */
2410 olterrain *level::GetRandomFountainWithWater (olterrain *Except) const {
2411 std::vector<olterrain *> Found;
2412 olterrain *OLTerrain;
2413 for (int x = 0; x < XSize; ++x) {
2414 for (int y = 0; y < YSize; ++y) {
2415 OLTerrain = GetLSquare(x,y)->GetOLTerrain();
2416 if (OLTerrain && OLTerrain != Except && OLTerrain->IsFountainWithWater()) Found.push_back(OLTerrain);
2419 if (Found.empty()) return 0;
2420 return Found[RAND_N(Found.size())];
2424 void level::Amnesia (int Percentile) {
2425 for (int x = 0; x < XSize; ++x) {
2426 for (int y = 0; y < YSize; ++y) {
2427 lsquare *Square = Map[x][y];
2428 if (Square->HasNoBorderPartners() && RAND_N(100) < Percentile) {
2429 Square->Flags |= STRONG_NEW_DRAW_REQUEST|MEMORIZED_UPDATE_REQUEST|DESCRIPTION_CHANGE;
2430 Square->DestroyMemorized();
2437 /* Returns how many of the monsters were seen */
2438 spawnresult level::SpawnMonsters (characterspawner Spawner, team *Team, v2 Pos, int Config, int Amount, truth IgnoreWalkability) {
2439 spawnresult SR = { 0, 0 };
2440 for (int c = 0; c < Amount; ++c) {
2441 character *Char = Spawner(Config, 0);
2442 if (!c) SR.Pioneer = Char;
2443 Char->SetTeam(Team);
2444 if (IgnoreWalkability) Char->ForcePutNear(Pos); else Char->PutNear(Pos);
2445 if (Char->CanBeSeenByPlayer()) ++SR.Seen;
2447 return SR;
2451 void level::AddSpecialCursors () {
2452 for (int x = 0; x < XSize; ++x) {
2453 for (int y = 0; y < YSize; ++y) {
2454 Map[x][y]->AddSpecialCursors();
2460 void level::GasExplosion (gas* GasMaterial, lsquare* Square, character* Terrorist) {
2461 for (int d = 0; d < 9; ++d) {
2462 lsquare *Neighbour = Square->GetNeighbourLSquare(d);
2463 if (Neighbour) {
2464 if (Neighbour->IsFlyable()) Neighbour->AddSmoke(static_cast<gas*>(GasMaterial->SpawnMore(1000)));
2465 if (Terrorist) {
2466 character *Victim = Neighbour->GetCharacter();
2467 if (Victim) Terrorist->Hostility(Victim);