1 /*
2  *
3  *  Iter Vehemens ad Necem (IVAN)
4  *  Copyright (C) Timo Kiviluoto
5  *  Released under the GNU General
6  *  Public License
7  *
8  *  See LICENSING which should be included
9  *  along with this file for more details
10  *
11  */
12 
13 /* Compiled through levelset.cpp */
14 
15 #include "dbgmsgproj.h"
16 
17 #define FORBIDDEN 1
18 #define ON_POSSIBLE_ROUTE 2
19 #define STILL_ON_POSSIBLE_ROUTE 4
20 #define PREFERRED 8
21 #define ICE_TERRAIN 16
22 #define STONE_TERRAIN 32
23 #define EXPANSIVE 64
24 
level()25 level::level()
26 : Room(1, static_cast<room*>(0)), GlobalRainLiquid(0), SunLightEmitation(0),
27   AmbientLuminance(0), SquareStack(0), NightAmbientLuminance(0) { }
SetRoom(int I,room * What)28 void level::SetRoom(int I, room* What) { Room[I] = What; }
AddToAttachQueue(v2 Pos)29 void level::AddToAttachQueue(v2 Pos) { AttachQueue.push_back(Pos); }
30 
31 ulong level::NextExplosionID = 1;
32 
33 node*** node::NodeMap;
34 int node::RequiredWalkability;
35 ccharacter* node::SpecialMover;
36 v2 node::To;
37 uchar** node::WalkabilityMap;
38 int node::XSize, node::YSize;
39 nodequeue* node::NodeQueue;
40 
~level()41 level::~level()
42 {
43   ulong c;
44 
45   for(c = 0; c < XSizeTimesYSize; ++c)
46     delete NodeMap[0][c];
47 
48   for(c = 0; c < Room.size(); ++c)
49     delete Room[c];
50 
51   delete [] NodeMap;
52   delete [] WalkabilityMap;
53   delete GlobalRainLiquid;
54   delete [] SquareStack;
55   game::SetGlobalRainLiquid(0);
56 }
57 
ExpandPossibleRoute(int OrigoX,int OrigoY,int TargetX,int TargetY,truth XMode)58 void level::ExpandPossibleRoute(int OrigoX, int OrigoY, int TargetX, int TargetY, truth XMode)
59 {
60 #define CHECK(x, y) !(FlagMap[x][y] & (ON_POSSIBLE_ROUTE|FORBIDDEN))
61 
62 #define CALL_EXPAND(x, y)\
63   {\
64     ExpandPossibleRoute(x, y, TargetX, TargetY, XMode);\
65     \
66     if(FlagMap[TargetX][TargetY] & ON_POSSIBLE_ROUTE)\
67       return;\
68   }
69 
70   FlagMap[OrigoX][OrigoY] |= ON_POSSIBLE_ROUTE;
71 
72   if(XMode)
73   {
74     if(TargetX < OrigoX)
75       if(CHECK(OrigoX - 1, OrigoY))
76         CALL_EXPAND(OrigoX - 1, OrigoY);
77 
78     if(TargetX > OrigoX)
79       if(CHECK(OrigoX + 1, OrigoY))
80         CALL_EXPAND(OrigoX + 1, OrigoY);
81 
82     if(TargetY < OrigoY)
83       if(CHECK(OrigoX, OrigoY - 1))
84         CALL_EXPAND(OrigoX, OrigoY - 1);
85 
86     if(TargetY > OrigoY)
87       if(CHECK(OrigoX, OrigoY + 1))
88         CALL_EXPAND(OrigoX, OrigoY + 1);
89 
90     if(TargetX <= OrigoX)
91       if(OrigoX < XSize - 2 && CHECK(OrigoX + 1, OrigoY))
92         CALL_EXPAND(OrigoX + 1, OrigoY);
93 
94     if(TargetX >= OrigoX)
95       if(OrigoX > 1 && CHECK(OrigoX - 1, OrigoY))
96         CALL_EXPAND(OrigoX - 1, OrigoY);
97 
98     if(TargetY <= OrigoY)
99       if(OrigoY < YSize - 2 && CHECK(OrigoX, OrigoY + 1))
100         CALL_EXPAND(OrigoX, OrigoY + 1);
101 
102     if(TargetY >= OrigoY)
103       if(OrigoY > 1 && CHECK(OrigoX, OrigoY - 1))
104         CALL_EXPAND(OrigoX, OrigoY - 1);
105   }
106   else
107   {
108     if(TargetY < OrigoY)
109       if(CHECK(OrigoX, OrigoY - 1))
110         CALL_EXPAND(OrigoX, OrigoY - 1);
111 
112     if(TargetY > OrigoY)
113       if(CHECK(OrigoX, OrigoY + 1))
114         CALL_EXPAND(OrigoX, OrigoY + 1);
115 
116     if(TargetX < OrigoX)
117       if(CHECK(OrigoX - 1, OrigoY))
118         CALL_EXPAND(OrigoX - 1, OrigoY);
119 
120     if(TargetX > OrigoX)
121       if(CHECK(OrigoX + 1, OrigoY))
122         CALL_EXPAND(OrigoX + 1, OrigoY);
123 
124     if(TargetY <= OrigoY)
125       if(OrigoY < YSize - 2 && CHECK(OrigoX, OrigoY + 1))
126         CALL_EXPAND(OrigoX, OrigoY + 1);
127 
128     if(TargetY >= OrigoY)
129       if(OrigoY > 1 && CHECK(OrigoX, OrigoY - 1))
130         CALL_EXPAND(OrigoX, OrigoY - 1);
131 
132     if(TargetX <= OrigoX)
133       if(OrigoX < XSize - 2 && CHECK(OrigoX + 1, OrigoY))
134         CALL_EXPAND(OrigoX + 1, OrigoY);
135 
136     if(TargetX >= OrigoX)
137       if(OrigoX > 1 && CHECK(OrigoX - 1, OrigoY))
138         CALL_EXPAND(OrigoX - 1, OrigoY);
139   }
140 
141 #undef CHECK
142 
143 #undef CALL_EXPAND
144 }
145 
ExpandStillPossibleRoute(int OrigoX,int OrigoY,int TargetX,int TargetY,truth XMode)146 void level::ExpandStillPossibleRoute(int OrigoX, int OrigoY, int TargetX, int TargetY, truth XMode)
147 {
148 #define CHECK(x, y) (FlagMap[x][y] & (STILL_ON_POSSIBLE_ROUTE|ON_POSSIBLE_ROUTE)) == ON_POSSIBLE_ROUTE
149 
150 #define CALL_EXPAND(x, y) \
151   {\
152     ExpandStillPossibleRoute(x, y, TargetX, TargetY, XMode);\
153     \
154     if(FlagMap[TargetX][TargetY] & STILL_ON_POSSIBLE_ROUTE) \
155       return;\
156   }
157 
158   FlagMap[OrigoX][OrigoY] |= STILL_ON_POSSIBLE_ROUTE;
159 
160   if(XMode)
161   {
162     if(TargetX < OrigoX)
163       if(CHECK(OrigoX - 1, OrigoY))
164         CALL_EXPAND(OrigoX - 1, OrigoY);
165 
166     if(TargetX > OrigoX)
167       if(CHECK(OrigoX + 1, OrigoY))
168         CALL_EXPAND(OrigoX + 1, OrigoY);
169 
170     if(TargetY < OrigoY)
171       if(CHECK(OrigoX, OrigoY - 1))
172         CALL_EXPAND(OrigoX, OrigoY - 1);
173 
174     if(TargetY > OrigoY)
175       if(CHECK(OrigoX, OrigoY + 1))
176         CALL_EXPAND(OrigoX, OrigoY + 1);
177 
178     if(TargetX <= OrigoX)
179       if(OrigoX < XSize - 2 && CHECK(OrigoX + 1, OrigoY))
180         CALL_EXPAND(OrigoX + 1, OrigoY);
181 
182     if(TargetX >= OrigoX)
183       if(OrigoX > 1 && CHECK(OrigoX - 1, OrigoY))
184         CALL_EXPAND(OrigoX - 1, OrigoY);
185 
186     if(TargetY <= OrigoY)
187       if(OrigoY < YSize - 2 && CHECK(OrigoX, OrigoY + 1))
188         CALL_EXPAND(OrigoX, OrigoY + 1);
189 
190     if(TargetY >= OrigoY)
191       if(OrigoY > 1 && CHECK(OrigoX, OrigoY - 1))
192         CALL_EXPAND(OrigoX, OrigoY - 1);
193   }
194   else
195   {
196     if(TargetY < OrigoY)
197       if(CHECK(OrigoX, OrigoY - 1))
198         CALL_EXPAND(OrigoX, OrigoY - 1);
199 
200     if(TargetY > OrigoY)
201       if(CHECK(OrigoX, OrigoY + 1))
202         CALL_EXPAND(OrigoX, OrigoY + 1);
203 
204     if(TargetX < OrigoX)
205       if(CHECK(OrigoX - 1, OrigoY))
206         CALL_EXPAND(OrigoX - 1, OrigoY);
207 
208     if(TargetX > OrigoX)
209       if(CHECK(OrigoX + 1, OrigoY))
210         CALL_EXPAND(OrigoX + 1, OrigoY);
211 
212     if(TargetY <= OrigoY)
213       if(OrigoY < YSize - 2 && CHECK(OrigoX, OrigoY + 1))
214         CALL_EXPAND(OrigoX, OrigoY + 1);
215 
216     if(TargetY >= OrigoY)
217       if(OrigoY > 1 && CHECK(OrigoX, OrigoY - 1))
218         CALL_EXPAND(OrigoX, OrigoY - 1);
219 
220     if(TargetX <= OrigoX)
221       if(OrigoX < XSize - 2 && CHECK(OrigoX + 1, OrigoY))
222         CALL_EXPAND(OrigoX + 1, OrigoY);
223 
224     if(TargetX >= OrigoX)
225       if(OrigoX > 1 && CHECK(OrigoX - 1, OrigoY))
226         CALL_EXPAND(OrigoX - 1, OrigoY);
227   }
228 
229 #undef CHECK
230 
231 #undef CALL_EXPAND
232 }
233 
GenerateTunnel(int FromX,int FromY,int TargetX,int TargetY,truth XMode)234 void level::GenerateTunnel(int FromX, int FromY, int TargetX, int TargetY, truth XMode)
235 {
236   FlagMap[FromX][FromY] |= ON_POSSIBLE_ROUTE;
237   ExpandPossibleRoute(FromX, FromY, TargetX, TargetY, XMode);
238   const contentscript<glterrain>* GTerrain = LevelScript->GetTunnelSquare()->GetGTerrain();
239   const contentscript<olterrain>* OTerrain = LevelScript->GetTunnelSquare()->GetOTerrain();
240 
241   if(FlagMap[TargetX][TargetY] & ON_POSSIBLE_ROUTE)
242     for(int x = 0; x < XSize; ++x)
243       for(int y = 0; y < YSize; ++y)
244         if((FlagMap[x][y] & (ON_POSSIBLE_ROUTE|PREFERRED)) == ON_POSSIBLE_ROUTE
245            && !(x == FromX && y == FromY) && !(x == TargetX && y == TargetY))
246         {
247           FlagMap[x][y] &= ~ON_POSSIBLE_ROUTE;
248           FlagMap[FromX][FromY] |= STILL_ON_POSSIBLE_ROUTE;
249           ExpandStillPossibleRoute(FromX, FromY, TargetX, TargetY, XMode);
250 
251           if(!(FlagMap[TargetX][TargetY] & STILL_ON_POSSIBLE_ROUTE))
252           {
253             FlagMap[x][y] |= ON_POSSIBLE_ROUTE|PREFERRED;
254             Map[x][y]->ChangeGLTerrain(GTerrain->Instantiate());
255             Map[x][y]->ChangeOLTerrain(OTerrain->Instantiate());
256           }
257 
258           for(int X = 0; X < XSize; ++X)
259             for(int Y = 0; Y < YSize; ++Y)
260               FlagMap[X][Y] &= ~STILL_ON_POSSIBLE_ROUTE;
261         }
262 
263   for(int x = 1; x < XSize - 1; ++x)
264     for(int y = 1; y < YSize - 1; ++y)
265       FlagMap[x][y] &= ~ON_POSSIBLE_ROUTE;
266 }
267 
CreateMaze()268 void maze::CreateMaze()
269 {
270   InitializeMaze();
271   CarveMaze(2, 2);
272   StripMazeHusk();
273 }
274 
InitializeMaze()275 void maze::InitializeMaze()
276 {
277   std::fill(MazeVector.begin(), MazeVector.end(), false);
278 
279   for(unsigned x = 0; x < MazeXSize; x++)
280   {
281     MazeVector[x] = true;
282     MazeVector[(MazeYSize - 1) * MazeXSize + x] = true;
283   }
284   for(unsigned y = 0; y < MazeYSize; y++)
285   {
286     MazeVector[y * MazeXSize] = true;
287     MazeVector[y * MazeXSize + MazeXSize - 1] = true;
288   }
289 }
290 
CarveMaze(int x,int y)291 void maze::CarveMaze(int x, int y)
292 {
293   MazeVector[y * MazeXSize + x] = true;
294   const unsigned d = RAND();
295   for(unsigned i = 0; i < 4; i++)
296   {
297     const int dirs[] = { 1, -1, 0, 0 };
298     const int dx = dirs[(i + d + 0) % 4];
299     const int dy = dirs[(i + d + 2) % 4];
300     const int x1 = x + dx, y1 = y + dy;
301     const int x2 = x1 + dx, y2 = y1 + dy;
302     if(!MazeVector[y1 * MazeXSize + x1] && !MazeVector[y2 * MazeXSize + x2])
303     {
304       MazeVector[y1 * MazeXSize + x1] = true;
305       CarveMaze(x2, y2);
306     }
307   }
308 }
309 
StripMazeHusk()310 void maze::StripMazeHusk()
311 {
312   for(uint y1 = 2; y1 < MazeYSize - 2; y1++)
313   {
314     for(uint x1 = 2; x1 < MazeXSize - 2; x1++)
315     {
316       MazeKernel.push_back(MazeVector[(y1) * (MazeXSize) + (x1)]);
317     }
318   }
319 }
320 
Generate(int Index)321 void level::Generate(int Index)
322 {
323   game::BusyAnimation();
324   Initialize(LevelScript->GetSize()->X, LevelScript->GetSize()->Y);
325   game::SetCurrentArea(this);
326   game::SetCurrentLevel(this);
327   Alloc2D(NodeMap, XSize, YSize);
328   Alloc2D(WalkabilityMap, XSize, YSize);
329   Map = reinterpret_cast<lsquare***>(area::Map);
330   SquareStack = new lsquare*[XSizeTimesYSize];
331 
332   /*if((Index == 0 && GetDungeon()->GetIndex() == NEW_ATTNAM)
333      || (Index == 0 && GetDungeon()->GetIndex() == ATTNAM))
334     NightAmbientLuminance = MakeRGB24(95, 95, 95);
335   */
336   if((Index == 0) && (GetDungeon()->GetIndex() == XINROCH_TOMB))
337     NightAmbientLuminance = MakeRGB24(105, 95, 95);
338   else if(IsOnGround())
339     NightAmbientLuminance = MakeRGB24(95, 95, 95);
340 
341   int x, y;
342 
343   for(x = 0; x < XSize; ++x)
344     for(y = 0; y < YSize; ++y)
345     {
346       Map[x][y] = new lsquare(this, v2(x, y));
347       NodeMap[x][y] = new node(x, y, Map[x][y]);
348     }
349 
350   for(x = 0; x < XSize; ++x)
351     for(y = 0; y < YSize; ++y)
352       Map[x][y]->CalculateNeighbourLSquares();
353 
354   int Type = LevelScript->GetType() ? *LevelScript->GetType() : 0;
355 
356   switch(Type)
357   {
358    case 0:
359     GenerateDungeon(Index);
360     break;
361    case DESERT:
362     GenerateDesert();
363     break;
364    case JUNGLE:
365     GenerateJungle();
366     break;
367    case STEPPE:
368     GenerateSteppe();
369     break;
370    case LEAFY_FOREST:
371     GenerateLeafyForest();
372     break;
373    case EVERGREEN_FOREST:
374     GenerateEvergreenForest();
375     break;
376    case TUNDRA:
377     GenerateTundra();
378     break;
379    case GLACIER:
380     GenerateGlacier();
381     break;
382    default:
383     ABORT("You are a terrorist. Please stop creating wterrains that are stupid.");
384   }
385 }
386 
ApplyLSquareScript(const squarescript * Script)387 void level::ApplyLSquareScript(const squarescript* Script)
388 {
389   const interval* ScriptTimes = Script->GetTimes();
390   int Times = ScriptTimes ? ScriptTimes->Randomize() : 1;
391 
392   for(int c = 0; c < Times; ++c)
393   {
394     v2 Pos;
395 
396     while(true)
397     {
398       if(Script->GetPosition()->GetRandom())
399         Pos = GetRandomSquare(0, Script->GetPosition()->GetFlags(), Script->GetPosition()->GetBorders());
400       else
401         Pos = Script->GetPosition()->GetVector();
402 
403       Map[Pos.X][Pos.Y]->ApplyScript(Script, 0);
404 
405       if(CheckExpansiveArea(GetLSquare(Pos)->GetOLTerrain(), Pos.X, Pos.Y, !Script->GetPosition()->GetRandom()))
406         break;
407     }
408   }
409 }
410 
AttachPos(int WhatX,int WhatY)411 void level::AttachPos(int WhatX, int WhatY)
412 {
413   int PosX = 1 + RAND() % (XSize - 2);
414   int PosY = 1 + RAND() % (YSize - 2);
415 
416   while(!(FlagMap[PosX][PosY] & PREFERRED))
417   {
418     PosX = 1 + RAND() % (XSize - 2);
419     PosY = 1 + RAND() % (YSize - 2);
420   }
421 
422   FlagMap[WhatX][WhatY] &= ~FORBIDDEN;
423   FlagMap[WhatX][WhatY] |= PREFERRED;
424   GenerateTunnel(WhatX, WhatY, PosX, PosY, RAND() & 1);
425   FlagMap[WhatX][WhatY] |= FORBIDDEN;
426   FlagMap[WhatX][WhatY] &= ~PREFERRED;
427 }
428 
CreateItems(int Amount)429 void level::CreateItems(int Amount)
430 {
431   if(Amount)
432   {
433     long MinPrice = *LevelScript->GetItemMinPriceBase() + *LevelScript->GetItemMinPriceDelta() * Index;
434 
435     for(int x = 0; x < Amount; ++x)
436     {
437       v2 Pos = GetRandomSquare();
438       item* Item = protosystem::BalancedCreateItem(MinPrice, MAX_PRICE, ANY_CATEGORY, 0, IGNORE_BROKEN_PRICE);
439       Item->CalculateEnchantment();
440       Map[Pos.X][Pos.Y]->Stack->AddItem(Item);
441       Item->SpecialGenerationHandler();
442     }
443   }
444 }
445 
MakeRoom(const roomscript * RoomScript)446 truth level::MakeRoom(const roomscript* RoomScript)
447 {
448   game::BusyAnimation();
449   v2 Pos = RoomScript->GetPos()->Randomize();
450   v2 Size = RoomScript->GetSize()->Randomize();
451   int x, y;
452 
453   if(Pos.X + Size.X > XSize - 2)
454     return false;
455 
456   if(Pos.Y + Size.Y > YSize - 2)
457     return false;
458 
459   for(x = Pos.X - 1; x <= Pos.X + Size.X; ++x)
460     for(y = Pos.Y - 1; y <= Pos.Y + Size.Y; ++y)
461       if(IsValidPos(x, y) && FlagMap[x][y] & (FORBIDDEN|PREFERRED|EXPANSIVE))
462         return false;
463 
464   room* RoomClass = protocontainer<room>::GetProto(*RoomScript->GetType())->Spawn();
465   RoomClass->SetPos(Pos);
466   RoomClass->SetSize(Size);
467   RoomClass->SetFlags(*RoomScript->GetFlags());
468   AddRoom(RoomClass);
469   RoomClass->SetDivineMaster(*RoomScript->GetDivineMaster());
470   game::BusyAnimation();
471   std::vector<v2> OKForDoor, Inside, Border;
472 
473   GenerateRectangularRoom(OKForDoor, Inside, Border, RoomScript, RoomClass, Pos, Size);
474   game::BusyAnimation();
475 
476   if(*RoomScript->GenerateFountains() && !(RAND() % 10))
477     GetLSquare(Inside[RAND() % Inside.size()])->ChangeOLTerrain(fountain::Spawn());
478 
479   if(*RoomScript->AltarPossible() && !(RAND() % 5))
480   {
481     int Owner = 1 + RAND() % GODS;
482     GetLSquare(Inside[RAND() % Inside.size()])->ChangeOLTerrain(altar::Spawn(Owner));
483     game::GetGod(Owner)->SignalRandomAltarGeneration(Inside);
484     RoomClass->SetDivineMaster(Owner);
485   }
486 
487   if(*RoomScript->GenerateTunnel() && !Door.empty())
488   {
489     game::BusyAnimation();
490     v2 OutsideDoorPos = Door[RAND() % Door.size()]; // An other room
491 
492     if(OKForDoor.empty())
493       ABORT("The Doors - You are strange.");
494 
495     v2 InsideDoorPos = OKForDoor[RAND() % OKForDoor.size()]; // this door
496     olterrain* Door = RoomScript->GetDoorSquare()->GetOTerrain()->Instantiate(); // Bug! Wrong room!
497 
498     if(Door && !(RAND() % 5) && *RoomScript->AllowLockedDoors())
499     {
500       if(*RoomScript->AllowBoobyTrappedDoors() && !(RAND() % 5))
501         Door->CreateBoobyTrap();
502 
503       Door->Lock();
504     }
505 
506     Map[OutsideDoorPos.X][OutsideDoorPos.Y]->ChangeLTerrain(RoomScript->GetDoorSquare()->GetGTerrain()->Instantiate(), Door);
507     Map[OutsideDoorPos.X][OutsideDoorPos.Y]->Clean();
508     FlagMap[OutsideDoorPos.X][OutsideDoorPos.Y] &= ~FORBIDDEN;
509     FlagMap[OutsideDoorPos.X][OutsideDoorPos.Y] |= PREFERRED;
510     FlagMap[InsideDoorPos.X][InsideDoorPos.Y] &= ~FORBIDDEN;
511     FlagMap[InsideDoorPos.X][InsideDoorPos.Y] |= PREFERRED;
512     Door = RoomScript->GetDoorSquare()->GetOTerrain()->Instantiate();
513 
514     if(Door && !(RAND() % 5) && *RoomScript->AllowLockedDoors())
515     {
516       if(*RoomScript->AllowBoobyTrappedDoors() && !(RAND() % 5))
517         Door->CreateBoobyTrap();
518 
519       Door->Lock();
520     }
521 
522     Map[InsideDoorPos.X][InsideDoorPos.Y]->ChangeLTerrain(RoomScript->GetDoorSquare()->GetGTerrain()->Instantiate(), Door);
523     Map[InsideDoorPos.X][InsideDoorPos.Y]->Clean();
524     GenerateTunnel(InsideDoorPos.X, InsideDoorPos.Y, OutsideDoorPos.X, OutsideDoorPos.Y, RAND() & 1);
525     FlagMap[OutsideDoorPos.X][OutsideDoorPos.Y] |= FORBIDDEN;
526     FlagMap[OutsideDoorPos.X][OutsideDoorPos.Y] &= ~PREFERRED;
527     FlagMap[InsideDoorPos.X][InsideDoorPos.Y] |= FORBIDDEN;
528     FlagMap[InsideDoorPos.X][InsideDoorPos.Y] &= ~PREFERRED;
529   }
530 
531   if(*RoomScript->GenerateDoor())
532   {
533     game::BusyAnimation();
534     v2 DoorPos;
535 
536     if(OKForDoor.empty())
537       ABORT("The Doors - This thing has been broken.");
538 
539     DoorPos = OKForDoor[RAND() % OKForDoor.size()];
540     Door.push_back(DoorPos);
541 
542     if(!*RoomScript->GenerateTunnel())
543     {
544       Map[DoorPos.X][DoorPos.Y]->ChangeLTerrain(RoomScript->GetDoorSquare()->GetGTerrain()->Instantiate(),
545                                                 RoomScript->GetDoorSquare()->GetOTerrain()->Instantiate());
546       Map[DoorPos.X][DoorPos.Y]->Clean();
547     }
548   }
549   // Make second door for a maze room. If the room has even-numbered dimension, then it will be a rectangular room with two doors...
550   if(*RoomScript->GenerateDoor() && (*RoomScript->GetShape() == MAZE_ROOM))
551   {
552     game::BusyAnimation();
553     v2 DoorPos;
554 
555     if(!OKForDoor.empty())
556     {
557       DoorPos = OKForDoor[OKForDoor.size() - 1];
558       Door.push_back(DoorPos);
559 
560       if(!*RoomScript->GenerateTunnel())
561       {
562         Map[DoorPos.X][DoorPos.Y]->ChangeLTerrain(RoomScript->GetDoorSquare()->GetGTerrain()->Instantiate(),
563                                                   RoomScript->GetDoorSquare()->GetOTerrain()->Instantiate());
564         Map[DoorPos.X][DoorPos.Y]->Clean();
565       }
566     }
567   }
568 
569   const charactercontentmap* CharacterMap = RoomScript->GetCharacterMap();
570 
571   if(CharacterMap)
572   {
573     v2 CharPos = Pos + *CharacterMap->GetPos();
574     v2 CharSize = *CharacterMap->GetSize();
575 
576     if(!IsValidPos(CharPos) || !IsValidPos(CharPos + CharSize - v2(1, 1)))
577       ABORT("Invalid Dungeon#%d Level#%d(%dx%d) Room#%lu CharacterMap(%dx%d) at (%d,%d)!",
578             GetDungeon()->GetIndex(), Index, XSize, YSize, Room.size(), CharSize.X, CharSize.Y, CharPos.X, CharPos.Y);
579 
580     for(x = 0; x < CharSize.X; ++x)
581     {
582       game::BusyAnimation();
583 
584       for(y = 0; y < CharSize.Y; ++y)
585       {
586         const contentscript<character>* CharacterScript = CharacterMap->GetContentScript(x, y);
587 
588         if(IsValidScript(CharacterScript))
589         {
590           character* Char = CharacterScript->Instantiate();
591           Char->SetGenerationDanger(Difficulty);
592 
593           if(!Char->GetTeam())
594             Char->SetTeam(game::GetTeam(*LevelScript->GetTeamDefault()));
595 
596           if(CharacterScript->GetFlags() & IS_LEADER)
597             Char->GetTeam()->SetLeader(Char);
598 
599           Char->PutTo(CharPos + v2(x, y));
600           Char->CreateHomeData();
601 
602           if(CharacterScript->GetFlags() & IS_MASTER)
603             RoomClass->SetMasterID(Char->GetID());
604         }
605       }
606     }
607   }
608 
609   const itemcontentmap* ItemMap = RoomScript->GetItemMap();
610 
611   if(ItemMap)
612   {
613     v2 ItemPos = Pos + *ItemMap->GetPos();
614     v2 ItemSize = *ItemMap->GetSize();
615 
616     if(!IsValidPos(ItemPos) || !IsValidPos(ItemPos + ItemSize - v2(1, 1)))
617       ABORT("Invalid Dungeon#%d Level#%d(%dx%d) Room#%lu ItemMap(%dx%d) at (%d,%d)!",
618             GetDungeon()->GetIndex(), Index, XSize, YSize, Room.size(), ItemSize.X, ItemSize.Y, ItemPos.X, ItemPos.Y);
619 
620     for(x = 0; x < ItemSize.X; ++x)
621     {
622       game::BusyAnimation();
623 
624       for(y = 0; y < ItemSize.Y; ++y)
625       {
626         const fearray<contentscript<item>>* ItemScript = ItemMap->GetContentScript(x, y);
627 
628         if(IsValidScript(ItemScript))
629           for(uint c1 = 0; c1 < ItemScript->Size; ++c1)
630           {
631             const interval* TimesPtr = ItemScript->Data[c1].GetTimes();
632             int Times = TimesPtr ? TimesPtr->Randomize() : 1;
633 
634             for(int c2 = 0; c2 < Times; ++c2)
635             {
636               item* Item = ItemScript->Data[c1].Instantiate();
637 
638               if(Item)
639               {
640                 int SquarePosition = ItemScript->Data[c1].GetSquarePosition();
641 
642                 if(SquarePosition != CENTER)
643                   Item->SignalSquarePositionChange(SquarePosition);
644 
645                 Map[ItemPos.X + x][ItemPos.Y + y]->GetStack()->AddItem(Item);
646                 Item->SpecialGenerationHandler();
647               }
648             }
649           }
650       }
651     }
652   }
653 
654   const glterraincontentmap* GTerrainMap = RoomScript->GetGTerrainMap();
655 
656   if(GTerrainMap)
657   {
658     v2 GTerrainPos = Pos + *GTerrainMap->GetPos();
659     v2 GTerrainSize = *GTerrainMap->GetSize();
660 
661     if(!IsValidPos(GTerrainPos) || !IsValidPos(GTerrainPos + GTerrainSize - v2(1, 1)))
662       ABORT("Invalid Dungeon#%d Level#%d(%dx%d) Room#%lu GTerrainMap(%dx%d) at (%d,%d)!",
663             GetDungeon()->GetIndex(), Index, XSize, YSize, Room.size(), GTerrainSize.X, GTerrainSize.Y, GTerrainPos.X, GTerrainPos.Y);
664 
665     for(x = 0; x < GTerrainSize.X; ++x)
666     {
667       game::BusyAnimation();
668 
669       for(y = 0; y < GTerrainSize.Y; ++y)
670       {
671         v2 SquarePos = v2(GTerrainPos.X + x, GTerrainPos.Y + y);
672         const contentscript<glterrain>* GTerrainScript = GTerrainMap->GetContentScript(x, y);
673 
674         if(IsValidScript(GTerrainScript))
675         {
676           lsquare* Square = GetLSquare(SquarePos);
677           Square->ChangeGLTerrain(GTerrainScript->Instantiate());
678 
679           if(GTerrainScript->IsInside())
680           {
681             if(*GTerrainScript->IsInside())
682               Square->Flags |= INSIDE;
683             else
684               Square->Flags &= ~INSIDE;
685           }
686         }
687 
688         // Forbid generating random impassable OTerrain around it.
689         if(GTerrainScript->IsExpansive() && *GTerrainScript->IsExpansive())
690           AddFlag(SquarePos, EXPANSIVE);
691       }
692     }
693   }
694 
695   const olterraincontentmap* OTerrainMap = RoomScript->GetOTerrainMap();
696 
697   if(OTerrainMap)
698   {
699     v2 OTerrainPos = Pos + *OTerrainMap->GetPos();
700     v2 OTerrainSize = *OTerrainMap->GetSize();
701 
702     if(!IsValidPos(OTerrainPos) || !IsValidPos(OTerrainPos + OTerrainSize - v2(1, 1)))
703       ABORT("Invalid Dungeon#%d Level#%d(%dx%d) Room#%lu OTerrainMap(%dx%d) at (%d,%d)!",
704             GetDungeon()->GetIndex(), Index, XSize, YSize, Room.size(), OTerrainSize.X, OTerrainSize.Y, OTerrainPos.X, OTerrainPos.Y);
705 
706     for(x = 0; x < OTerrainSize.X; ++x)
707     {
708       game::BusyAnimation();
709 
710       for(y = 0; y < OTerrainSize.Y; ++y)
711       {
712         v2 SquarePos = v2(OTerrainPos.X + x, OTerrainPos.Y + y);
713         const contentscript<olterrain>* OTerrainScript = OTerrainMap->GetContentScript(x, y);
714 
715         if(IsValidScript(OTerrainScript))
716         {
717           olterrain* OLTerrain = OTerrainScript->Instantiate();
718           CheckExpansiveArea(OLTerrain, SquarePos.X, SquarePos.Y, true);
719           GetLSquare(SquarePos)->ChangeOLTerrain(OLTerrain);
720         }
721       }
722     }
723   }
724 
725   for(const squarescript& Script : RoomScript->GetSquare())
726   {
727     game::BusyAnimation();
728     const interval* ScriptTimes = Script.GetTimes();
729     int Times = ScriptTimes ? ScriptTimes->Randomize() : 1;
730 
731     for(int t = 0; t < Times; ++t)
732     {
733       v2 SquarePos;
734 
735       while(true)
736       {
737         if(Script.GetPosition()->GetRandom())
738         {
739           const rect* ScriptBorders = Script.GetPosition()->GetBorders();
740           rect Borders = ScriptBorders ? *ScriptBorders + Pos : rect(Pos, Pos + Size - v2(1, 1));
741           SquarePos = GetRandomSquare(0, Script.GetPosition()->GetFlags(), &Borders);
742         }
743         else
744           SquarePos = Pos + Script.GetPosition()->GetVector();
745 
746         Map[SquarePos.X][SquarePos.Y]->ApplyScript(&Script, RoomClass);
747 
748         if(CheckExpansiveArea(GetLSquare(SquarePos)->GetOLTerrain(), SquarePos.X, SquarePos.Y, !Script.GetPosition()->GetRandom()))
749           break;
750       }
751     }
752   }
753 
754   return true;
755 }
756 
CheckExpansiveArea(olterrain * OLTerrain,int X,int Y,truth Abort) const757 truth level::CheckExpansiveArea(olterrain* OLTerrain, int X, int Y, truth Abort) const
758 {
759   if(OLTerrain == NULL)
760     return true;
761 
762   for(int x = X - 1; x <= X + 1; ++x)
763     for(int y = Y - 1; y <= Y + 1; ++y)
764       if(IsValidPos(x, y) && FlagMap[x][y] & EXPANSIVE)
765       {
766         lsquare* LSquare = GetLSquare(x, y);
767         glterrain* GLTerrain = LSquare->GetGLTerrain();
768         int Flags = GLTerrain->GetWalkability();
769 
770         if((Flags & WALK) && !(OLTerrain->GetWalkability() & WALK))
771         {
772           if(Abort)
773             ABORT("Dungeon#%d Level#%d GLTerrain[%d][%d] may be blocked by OLTerrain[%d][%d]!",
774                   GetDungeon()->GetIndex(), Index, x, y, X, Y);
775 
776           return false;
777         }
778       }
779 
780   return true;
781 }
782 
GenerateLanterns(int X,int Y,int SquarePos) const783 truth level::GenerateLanterns(int X, int Y, int SquarePos) const
784 {
785   if(!(RAND() % 7))
786   {
787     lantern* Lantern = lantern::Spawn();
788     Lantern->SignalSquarePositionChange(SquarePos);
789     Map[X][Y]->GetStack()->AddItem(Lantern);
790     return true;
791   }
792 
793   return false;
794 }
795 
CreateRoomSquare(glterrain * GLTerrain,olterrain * OLTerrain,int X,int Y,int Room,int Flags) const796 void level::CreateRoomSquare(glterrain* GLTerrain, olterrain* OLTerrain, int X, int Y, int Room, int Flags) const
797 {
798   Map[X][Y]->ChangeLTerrain(GLTerrain, OLTerrain);
799   FlagMap[X][Y] |= FORBIDDEN;
800   Map[X][Y]->SetRoomIndex(Room);
801   Map[X][Y]->AddFlags(Flags);
802 }
803 
GenerateMonsters()804 void level::GenerateMonsters()
805 {
806   if(*LevelScript->GenerateMonsters()
807      && game::GetTeam(MONSTER_TEAM)->GetEnabledMembers() < IdealPopulation
808      && (MonsterGenerationInterval <= 1 || !RAND_N(MonsterGenerationInterval)))
809   {
810     GenerateNewMonsters(1);
811     ++MonsterGenerationInterval;
812   }
813 }
814 
Save(outputfile & SaveFile) const815 void level::Save(outputfile& SaveFile) const
816 {
817   area::Save(SaveFile);
818   SaveFile << Room << GlobalRainLiquid << GlobalRainSpeed;
819 
820   for(int x = 0; x < XSize; ++x)
821     for(int y = 0; y < YSize; ++y)
822       Map[x][y]->Save(SaveFile);
823 
824   SaveFile << Door << LevelMessage << IdealPopulation << MonsterGenerationInterval << Difficulty;
825   SaveFile << SunLightEmitation << SunLightDirection << AmbientLuminance << NightAmbientLuminance;
826 }
827 
Load(inputfile & SaveFile)828 void level::Load(inputfile& SaveFile)
829 {
830   game::SetIsGenerating(true);
831   game::SetIsLoading(true);
832   area::Load(SaveFile);
833   Map = reinterpret_cast<lsquare***>(area::Map);
834   SaveFile >> Room;
835   GlobalRainLiquid = static_cast<liquid*>(ReadType<material*>(SaveFile));
836   SaveFile >> GlobalRainSpeed;
837 
838   if(GlobalRainLiquid)
839     GlobalRainLiquid->SetVolumeNoSignals(0);
840 
841   game::SetGlobalRainLiquid(GlobalRainLiquid);
842   game::SetGlobalRainSpeed(GlobalRainSpeed);
843   int x, y;
844 
845   for(x = 0; x < XSize; ++x)
846     for(y = 0; y < YSize; ++y)
847       Map[x][y] = new lsquare(this, v2(x, y));
848 
849   for(x = 0; x < XSize; ++x)
850     for(y = 0; y < YSize; ++y)
851     {
852       game::SetSquareInLoad(Map[x][y]);
853       Map[x][y]->Load(SaveFile);
854       Map[x][y]->CalculateNeighbourLSquares();
855     }
856 
857   SaveFile >> Door >> LevelMessage >> IdealPopulation >> MonsterGenerationInterval >> Difficulty;
858   SaveFile >> SunLightEmitation >> SunLightDirection >> AmbientLuminance >> NightAmbientLuminance;
859   Alloc2D(NodeMap, XSize, YSize);
860   Alloc2D(WalkabilityMap, XSize, YSize);
861 
862   for(x = 0; x < XSize; ++x)
863     for(y = 0; y < YSize; ++y)
864     {
865       if(!Map[x][y]->IsInside())
866         Map[x][y]->AmbientLuminance = AmbientLuminance;
867 
868       NodeMap[x][y] = new node(x, y, Map[x][y]);
869       WalkabilityMap[x][y] = Map[x][y]->GetTheoreticalWalkability();
870       Map[x][y]->CalculateGroundBorderPartners();
871       Map[x][y]->CalculateOverBorderPartners();
872     }
873 
874   SquareStack = new lsquare*[XSizeTimesYSize];
875   game::SetIsLoading(false);
876   game::SetIsGenerating(false);
877 }
878 
FiatLux()879 void level::FiatLux()
880 {
881   for(int x = 0; x < XSize; ++x)
882     for(int y = 0; y < YSize; ++y)
883     {
884       Map[x][y]->CalculateEmitation();
885       Map[x][y]->Emitate();
886       Map[x][y]->CalculateLuminance();
887     }
888 
889   CheckSunLight();
890 }
891 
GenerateNewMonsters(int HowMany,truth ConsiderPlayer)892 void level::GenerateNewMonsters(int HowMany, truth ConsiderPlayer)
893 {
894   v2 Pos;
895 
896   for(int c1 = 0; c1 < HowMany; ++c1)
897   {
898     character* Char = protosystem::BalancedCreateMonster();
899     Char->CalculateEnchantments();
900 
901     for(int c2 = 0; c2 < 30; ++c2)
902     {
903       Pos = GetRandomSquare(Char);
904 
905       if(Pos == ERROR_V2)
906         break;
907 
908       lsquare* Square = GetLSquare(Pos);
909 
910       if((!Square->GetRoomIndex()
911           || !Square->GetRoom()->DontGenerateMonsters())
912          && (!ConsiderPlayer
913              || (Pos - PLAYER->GetPos()).GetManhattanLength() > 6))
914         break;
915     }
916 
917     if(Pos != ERROR_V2)
918     {
919       Char->PutTo(Pos);
920       Char->SetGenerationDanger(Difficulty);
921       Char->SignalGeneration();
922       Char->SignalNaturalGeneration();
923       ivantime Time;
924       game::GetTime(Time);
925       int Modifier = Time.Day - EDIT_ATTRIBUTE_DAY_MIN;
926 
927       if(Modifier > 0)
928         Char->EditAllAttributes(Modifier >> EDIT_ATTRIBUTE_DAY_SHIFT);
929     }
930     else
931       delete Char;
932   }
933 }
934 
935 /* Example of the usage: GetRandomSquare() gives out a random walkable square */
936 
GetRandomSquare(ccharacter * Char,int Flags,const rect * Borders) const937 v2 level::GetRandomSquare(ccharacter* Char, int Flags, const rect* Borders) const
938 {
939   rect LocalBorder;
940 
941   if(Borders)
942   {
943     LocalBorder = *Borders;
944     Borders = &LocalBorder;
945     LimitRef(LocalBorder.X1, 0, XSize - 1);
946     LimitRef(LocalBorder.X2, 0, XSize - 1);
947     LimitRef(LocalBorder.Y1, 0, YSize - 1);
948     LimitRef(LocalBorder.Y2, 0, YSize - 1);
949   }
950 
951   lsquare* LSquare;
952 
953   for(int c = 0;; ++c)
954   {
955     if(c == 50)
956       Char = 0;
957 
958     if(c == 500)
959       return ERROR_V2;
960 
961     v2 Pos;
962 
963     if(Borders)
964     {
965       Pos.X = Borders->X1 + RAND() % (Borders->X2 - Borders->X1 + 1);
966       Pos.Y = Borders->Y1 + RAND() % (Borders->Y2 - Borders->Y1 + 1);
967     }
968     else
969     {
970       Pos.X = 1 + RAND() % (XSize - 2);
971       Pos.Y = 1 + RAND() % (YSize - 2);
972     }
973 
974     LSquare = Map[Pos.X][Pos.Y];
975 
976     if(((Char ? Char->CanMoveOn(LSquare) : (LSquare->GetWalkability() & WALK)) != !(Flags & NOT_WALKABLE))
977        || ((Char ? Char->IsFreeForMe(LSquare) : !LSquare->GetCharacter()) != !(Flags & HAS_CHARACTER))
978        || (Flags & ATTACHABLE && FlagMap[Pos.X][Pos.Y] & FORBIDDEN)
979        || (Flags & HAS_NO_OTERRAIN && LSquare->GetOTerrain()))
980       continue;
981 
982     int RoomFlags = Flags & (IN_ROOM|NOT_IN_ROOM);
983 
984     if((RoomFlags == IN_ROOM && !LSquare->GetRoomIndex())
985        || (RoomFlags == NOT_IN_ROOM && LSquare->GetRoomIndex()))
986       continue;
987 
988     return Pos;
989   }
990 }
991 
ParticleTrail(v2 StartPos,v2 EndPos)992 void level::ParticleTrail(v2 StartPos, v2 EndPos)
993 {
994   if(StartPos.X != EndPos.X && StartPos.Y != EndPos.Y)
995     ABORT("666th rule of thermodynamics - Particles don't move the way you want them to move.");
996 }
997 
IsOnGround() const998 truth level::IsOnGround() const
999 {
1000   return *LevelScript->IsOnGround();
1001 }
1002 
EarthquakesAffectTunnels() const1003 truth level::EarthquakesAffectTunnels() const
1004 {
1005   return *LevelScript->EarthquakesAffectTunnels();
1006 }
1007 
GetLOSModifier() const1008 int level::GetLOSModifier() const
1009 {
1010   return *LevelScript->GetLOSModifier();
1011 }
1012 
AddRoom(room * NewRoom)1013 void level::AddRoom(room* NewRoom)
1014 {
1015   NewRoom->SetIndex(Room.size());
1016   Room.push_back(NewRoom);
1017 }
1018 
GetRoom(int I) const1019 room* level::GetRoom(int I) const
1020 {
1021   if(!I)
1022     ABORT("Access to room zero denied!");
1023 
1024   return Room[I];
1025 }
1026 
Explosion(character * Terrorist,cfestring & DeathMsg,v2 Pos,int Strength,truth HurtNeutrals,truth FireOnly)1027 void level::Explosion(character* Terrorist, cfestring& DeathMsg, v2 Pos, int Strength, truth HurtNeutrals, truth FireOnly)
1028 {
1029   static int StrengthLimit[6] = { 500, 250, 100, 50, 25, 10 };
1030   uint c;
1031   int Size = 6;
1032 
1033   for(c = 0; c < 6; ++c)
1034     if(Strength >= StrengthLimit[c])
1035     {
1036       Size = c;
1037       break;
1038     }
1039 
1040   PlayerHurt.resize(PlayerHurt.size() + 1);
1041   explosion* Exp = new explosion;
1042   Exp->Terrorist = Terrorist;
1043   Exp->DeathMsg = DeathMsg;
1044   Exp->Pos = Pos;
1045   Exp->ID = NextExplosionID++;
1046   Exp->Strength = Strength;
1047   Exp->RadiusSquare = (8 - Size) * (8 - Size);
1048   Exp->Size = Size;
1049   Exp->HurtNeutrals = HurtNeutrals;
1050   Exp->FireOnly = FireOnly;
1051   ExplosionQueue.push_back(Exp);
1052 
1053   if(ExplosionQueue.size() == 1)
1054   {
1055     uint Explosions = 0;
1056 
1057     while(Explosions != ExplosionQueue.size())
1058     {
1059       for(c = Explosions; c != ExplosionQueue.size(); c = TriggerExplosions(c));
1060       uint NewExplosions = c;
1061 
1062       for(c = Explosions; c < NewExplosions; ++c)
1063         if(PlayerHurt[c] && PLAYER->IsEnabled())
1064           PLAYER->GetHitByExplosion(ExplosionQueue[c], ExplosionQueue[c]->Strength
1065                                     / ((PLAYER->GetPos() - ExplosionQueue[c]->Pos).GetLengthSquare() + 1));
1066 
1067       Explosions = NewExplosions;
1068     }
1069 
1070     for(uint c = 0; c < ExplosionQueue.size(); ++c)
1071       delete ExplosionQueue[c];
1072 
1073     ExplosionQueue.clear();
1074     PlayerHurt.clear();
1075     NextExplosionID = 1;
1076 
1077     for(int x = 0; x < XSize; ++x)
1078       for(int y = 0; y < YSize; ++y)
1079         Map[x][y]->LastExplosionID = 0;
1080   }
1081 }
1082 
DrawExplosion(const explosion * Explosion) const1083 truth level::DrawExplosion(const explosion* Explosion) const
1084 {
1085   static v2 StrengthPicPos[7] =
1086   { v2(176, 176), v2(0, 144), v2(256, 32), v2(144, 32), v2(64, 32), v2(16, 32), v2(0, 32) };
1087   v2 BPos = game::CalculateScreenCoordinates(Explosion->Pos) - v2((6 - Explosion->Size) << 4,
1088                                                                   (6 - Explosion->Size) << 4);
1089   v2 SizeVect(16 + ((6 - Explosion->Size) << 5), 16 + ((6 - Explosion->Size) << 5));
1090   v2 OldSizeVect = SizeVect;
1091   v2 PicPos = StrengthPicPos[Explosion->Size];
1092 
1093   if(BPos.X < 0)
1094   {
1095     if(BPos.X + SizeVect.X <= 0)
1096       return false;
1097     else
1098     {
1099       PicPos.X -= BPos.X;
1100       SizeVect.X += BPos.X;
1101       BPos.X = 0;
1102     }
1103   }
1104 
1105   if(BPos.Y < 0)
1106   {
1107     if(BPos.Y + SizeVect.Y <= 0)
1108       return false;
1109     else
1110     {
1111       PicPos.Y -= BPos.Y;
1112       SizeVect.Y += BPos.Y;
1113       BPos.Y = 0;
1114     }
1115   }
1116 
1117   if(BPos.X >= RES.X || BPos.Y >= RES.Y)
1118     return false;
1119 
1120   if(BPos.X + SizeVect.X > RES.X)
1121     SizeVect.X = RES.X - BPos.X;
1122 
1123   if(BPos.Y + SizeVect.Y > RES.Y)
1124     SizeVect.Y = RES.Y - BPos.Y;
1125 
1126   int Flags = RAND() & 7;
1127   blitdata BlitData = { 0,
1128                         { PicPos.X, PicPos.Y },
1129                         { 0, 0 },
1130                         { SizeVect.X, SizeVect.Y },
1131                         { 0 },
1132                         TRANSPARENT_COLOR,
1133                         0 };
1134 
1135   if(!Flags || SizeVect != OldSizeVect)
1136   {
1137     BlitData.Bitmap = DOUBLE_BUFFER;
1138     BlitData.Dest = BPos;
1139     BlitData.Luminance = ivanconfig::GetContrastLuminance();
1140     igraph::GetSymbolGraphic()->LuminanceMaskedBlit(BlitData);
1141   }
1142   else
1143   {
1144     /* Cache these */
1145     bitmap ExplosionPic(SizeVect);
1146     ExplosionPic.ActivateFastFlag();
1147     BlitData.Bitmap = &ExplosionPic;
1148     BlitData.Flags = Flags;
1149     igraph::GetSymbolGraphic()->NormalBlit(BlitData);
1150     BlitData.Bitmap = DOUBLE_BUFFER;
1151     BlitData.Dest = BPos;
1152     BlitData.Src.X = BlitData.Src.Y = 0;
1153     BlitData.Luminance = ivanconfig::GetContrastLuminance();
1154     ExplosionPic.LuminanceMaskedBlit(BlitData);
1155   }
1156 
1157   return true;
1158 }
1159 
1160 struct explosioncontroller
1161 {
Handlerexplosioncontroller1162   static truth Handler(int x, int y)
1163   {
1164     lsquare* Square = Map[x][y];
1165     Square->GetHitByExplosion(CurrentExplosion);
1166     return Square->IsFlyable();
1167   }
1168   static lsquare*** Map;
1169   static explosion* CurrentExplosion;
1170 };
1171 
1172 lsquare*** explosioncontroller::Map;
1173 explosion* explosioncontroller::CurrentExplosion;
1174 
TriggerExplosions(int MinIndex)1175 int level::TriggerExplosions(int MinIndex)
1176 {
1177   int LastExplosion = ExplosionQueue.size();
1178   int NotSeen = 0;
1179   int c;
1180 
1181   for(c = MinIndex; c < LastExplosion; ++c)
1182   {
1183     int EmitChange = Min(50 + ExplosionQueue[c]->Strength, 255);
1184     GetLSquare(ExplosionQueue[c]->Pos)->SetTemporaryEmitation(MakeRGB24(EmitChange, EmitChange, EmitChange));
1185 
1186     if(!GetSquare(ExplosionQueue[c]->Pos)->CanBeSeenByPlayer(true))
1187       ++NotSeen;
1188   }
1189 
1190   if(NotSeen)
1191   {
1192     if(NotSeen == 1)
1193       ADD_MESSAGE("You hear an explosion.");
1194     else
1195       ADD_MESSAGE("You hear explosions.");
1196   }
1197 
1198   game::DrawEverythingNoBlit();
1199   truth Drawn = false;
1200 
1201   for(c = MinIndex; c < LastExplosion; ++c)
1202   {
1203     if(DrawExplosion(ExplosionQueue[c]))
1204       Drawn = true;
1205   }
1206 
1207   if(Drawn)
1208   {
1209     graphics::BlitDBToScreen();
1210     game::GetCurrentArea()->SendNewDrawRequest();
1211     clock_t StartTime = clock();
1212     while(clock() - StartTime < 0.3 * CLOCKS_PER_SEC);
1213   }
1214 
1215   for(c = MinIndex; c < LastExplosion; ++c)
1216   {
1217     explosion* Explosion = ExplosionQueue[c];
1218     int Radius = 8 - Explosion->Size;
1219     game::SetPlayerWasHurtByExplosion(false);
1220     explosioncontroller::Map = Map;
1221     explosioncontroller::CurrentExplosion = Explosion;
1222 
1223     rect Rect;
1224     femath::CalculateEnvironmentRectangle(Rect, GetBorder(), Explosion->Pos, Radius);
1225 
1226     for(int x = Rect.X1; x <= Rect.X2; ++x)
1227     {
1228       mapmath<explosioncontroller>::DoLine(Explosion->Pos.X, Explosion->Pos.Y, x, Rect.Y1);
1229       mapmath<explosioncontroller>::DoLine(Explosion->Pos.X, Explosion->Pos.Y, x, Rect.Y2);
1230     }
1231 
1232     for(int y = Rect.Y1 + 1; y < Rect.Y2; ++y)
1233     {
1234       mapmath<explosioncontroller>::DoLine(Explosion->Pos.X, Explosion->Pos.Y, Rect.X1, y);
1235       mapmath<explosioncontroller>::DoLine(Explosion->Pos.X, Explosion->Pos.Y, Rect.X2, y);
1236     }
1237 
1238     PlayerHurt[c] = game::PlayerWasHurtByExplosion();
1239 
1240     if(GetLSquare(Explosion->Pos)->IsFlyable())
1241       GetLSquare(Explosion->Pos)->AddSmoke(gas::Spawn(SMOKE, 1000));
1242   }
1243 
1244   for(c = MinIndex; c < LastExplosion; ++c)
1245     GetLSquare(ExplosionQueue[c]->Pos)->SetTemporaryEmitation(0);
1246 
1247   return LastExplosion;
1248 }
1249 
CollectCreatures(charactervector & CharacterArray,character * Leader,truth AllowHostiles)1250 truth level::CollectCreatures(charactervector& CharacterArray, character* Leader, truth AllowHostiles)
1251 {
1252   int c;
1253 
1254   if(!AllowHostiles)
1255     for(c = 0; c < game::GetTeams(); ++c)
1256       if(Leader->GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE)
1257         for(character* p : game::GetTeam(c)->GetMember())
1258           if(p->IsEnabled() && Leader->CanBeSeenBy(p)
1259              && Leader->SquareUnderCanBeSeenBy(p, true) && p->CanFollow())
1260           {
1261             ADD_MESSAGE("You can't escape when there are hostile creatures nearby.");
1262             return false;
1263           }
1264 
1265   truth TakeAll = true;
1266 
1267   for(c = 0; c < game::GetTeams(); ++c)
1268     if(game::GetTeam(c)->GetEnabledMembers()
1269        && Leader->GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE)
1270     {
1271       TakeAll = false;
1272       break;
1273     }
1274 
1275   for(c = 0; c < game::GetTeams(); ++c)
1276     if(game::GetTeam(c) == Leader->GetTeam() || Leader->GetTeam()->GetRelation(game::GetTeam(c)) == HOSTILE)
1277       for(character* p : game::GetTeam(c)->GetMember())
1278         if(p->IsEnabled() && p != Leader
1279            && (TakeAll
1280                || (Leader->CanBeSeenBy(p)
1281                    && Leader->SquareUnderCanBeSeenBy(p, true)))
1282            && p->CanFollow()
1283            && p->GetCommandFlags() & FOLLOW_LEADER)
1284         {
1285           if(p->GetAction() && p->GetAction()->IsVoluntary())
1286             p->GetAction()->Terminate(false);
1287 
1288           if(!p->GetAction())
1289           {
1290             ADD_MESSAGE("%s follows you.", p->CHAR_NAME(DEFINITE));
1291             CharacterArray.push_back(p);
1292             p->Remove();
1293           }
1294         }
1295 
1296   return true;
1297 }
1298 
DrawHitEffects(cint XMin,cint XMax,cint YMin,cint YMax) const1299 void level::DrawHitEffects(cint XMin,cint XMax,cint YMin,cint YMax) const
1300 {
1301   if(ivanconfig::GetHitIndicator()==0)return;
1302 
1303   for(int x = XMin; x < XMax; ++x)
1304   {
1305     lsquare** SquarePtr = &Map[x][YMin];
1306     for(int y = YMin; y < YMax; ++y, ++SquarePtr)
1307       (*SquarePtr)->DrawHitEffect();
1308   }
1309 }
1310 
Draw(truth AnimationDraw) const1311 void level::Draw(truth AnimationDraw) const
1312 {
1313   cint XMin = Max(game::GetCamera().X, 0);
1314   cint YMin = Max(game::GetCamera().Y, 0);
1315   cint XMax = Min(XSize, game::GetCamera().X + game::GetScreenXSize());
1316   cint YMax = Min(YSize, game::GetCamera().Y + game::GetScreenYSize());
1317   culong LOSTick = game::GetLOSTick();
1318   blitdata BlitData = { DOUBLE_BUFFER,
1319                         { 0, 0 },
1320                         { 0, 0 },
1321                         { TILE_SIZE, TILE_SIZE },
1322                         { 0 },
1323                         TRANSPARENT_COLOR,
1324                         ALLOW_ANIMATE|ALLOW_ALPHA };
1325 
1326   if(!game::GetSeeWholeMapCheatMode())
1327   {
1328     if(!AnimationDraw)
1329     {
1330       for(int x = XMin; x < XMax; ++x)
1331       {
1332         BlitData.Dest = game::CalculateScreenCoordinates(v2(x, YMin));
1333         lsquare** SquarePtr = &Map[x][YMin];
1334 
1335         for(int y = YMin; y < YMax; ++y, ++SquarePtr, BlitData.Dest.Y += TILE_SIZE)
1336         {
1337           const lsquare* Square = *SquarePtr;
1338           culong LastSeen = Square->LastSeen;
1339 
1340           if(LastSeen == LOSTick)
1341             Square->Draw(BlitData);
1342           else if(Square->Flags & STRONG_BIT || LastSeen == LOSTick - 2)
1343             Square->DrawMemorized(BlitData);
1344         }
1345       }
1346     }
1347     else
1348     {
1349       for(int x = XMin; x < XMax; ++x)
1350       {
1351         BlitData.Dest = game::CalculateScreenCoordinates(v2(x, YMin));
1352         lsquare** SquarePtr = &Map[x][YMin];
1353 
1354         for(int y = YMin; y < YMax; ++y, ++SquarePtr, BlitData.Dest.Y += TILE_SIZE)
1355         {
1356           const lsquare* Square = *SquarePtr;
1357 
1358           if(Square->LastSeen == LOSTick)
1359             Square->Draw(BlitData);
1360           else if(Square->Flags & STRONG_BIT)
1361             Square->DrawMemorized(BlitData);
1362           else
1363           {
1364             ccharacter* C = Square->Character;
1365 
1366             if(C)
1367             {
1368               if(C->CanBeSeenByPlayer())
1369                 Square->DrawMemorizedCharacter(BlitData);
1370               else
1371                 Square->DrawMemorized(BlitData);
1372             }
1373           }
1374         }
1375       }
1376     }
1377   }
1378   else
1379   {
1380     for(int x = XMin; x < XMax; ++x)
1381     {
1382       BlitData.Dest = game::CalculateScreenCoordinates(v2(x, YMin));
1383       lsquare** SquarePtr = &Map[x][YMin];
1384 
1385       for(int y = YMin; y < YMax; ++y, ++SquarePtr, BlitData.Dest.Y += TILE_SIZE)
1386         (*SquarePtr)->Draw(BlitData);
1387     }
1388   }
1389 
1390   DrawHitEffects(XMin,XMax,YMin,YMax); // so it will be drawn above everything else
1391 }
1392 
GetEntryPos(ccharacter * Char,int I) const1393 v2 level::GetEntryPos(ccharacter* Char, int I) const
1394 {
1395   if(I == FOUNTAIN)
1396   {
1397     std::vector<v2> Fountains;
1398     for(int x = 0; x < XSize; ++x)
1399       for(int y = 0; y < YSize; ++y)
1400       {
1401         if(GetLSquare(x, y)->GetOLTerrain() && GetLSquare(x, y)->GetOLTerrain()->IsFountainWithWater())
1402           Fountains.push_back(v2(x, y));
1403       }
1404 
1405     if(Fountains.empty())
1406       return GetRandomSquare();
1407 
1408     return Fountains[RAND_N(Fountains.size())];
1409   }
1410   std::map<int, v2>::const_iterator i = EntryMap.find(I);
1411   return i == EntryMap.end() ? GetRandomSquare(Char) : i->second;
1412 }
1413 
GenerateRectangularRoom(std::vector<v2> & OKForDoor,std::vector<v2> & Inside,std::vector<v2> & Border,const roomscript * RoomScript,room * RoomClass,v2 Pos,v2 Size)1414 void level::GenerateRectangularRoom(std::vector<v2>& OKForDoor, std::vector<v2>& Inside, std::vector<v2>& Border,
1415                                     const roomscript* RoomScript, room* RoomClass, v2 Pos, v2 Size)
1416 {
1417   const contentscript<glterrain>* GTerrain;
1418   const contentscript<olterrain>* OTerrain;
1419 
1420   if(*RoomScript->UseFillSquareWalls())
1421   {
1422     GTerrain = LevelScript->GetFillSquare()->GetGTerrain();
1423     OTerrain = LevelScript->GetFillSquare()->GetOTerrain();
1424   }
1425   else
1426   {
1427     GTerrain = RoomScript->GetWallSquare()->GetGTerrain();
1428     OTerrain = RoomScript->GetWallSquare()->GetOTerrain();
1429   }
1430 
1431   int Room = RoomClass->GetIndex();
1432   long Counter = 0;
1433   truth AllowLanterns = *RoomScript->GenerateLanterns();
1434   truth AllowWindows = *RoomScript->GenerateWindows();
1435   int x, y;
1436   int Shape = *RoomScript->GetShape();
1437   int Flags = (GTerrain->IsInside() ? *GTerrain->IsInside() : *RoomScript->IsInside()) ? INSIDE : 0;
1438 
1439   if((Shape == ROUND_CORNERS || Shape == MAZE_ROOM) && (Size.X < 5 || Size.Y < 5)) /* No weird shapes this way. */
1440     Shape = RECTANGLE;
1441 
1442   if((Shape == MAZE_ROOM) && (!(Size.X % 2) || !(Size.Y % 2))) /* Alas, no even numbered maze rooms allowed. */
1443     Shape = RECTANGLE;
1444 
1445   maze MazeRoom(Size.X, Size.Y);
1446 
1447   if(Shape == MAZE_ROOM)
1448   {
1449     MazeRoom.CreateMaze();
1450   }
1451 
1452   for(x = Pos.X; x < Pos.X + Size.X; ++x, Counter += 2)
1453   {
1454     if(Shape == ROUND_CORNERS)
1455     {
1456       if(x == Pos.X)
1457       {
1458         CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x + 1, Pos.Y + 1, Room, Flags);
1459         CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x + 1, Pos.Y + Size.Y - 2, Room, Flags);
1460         Border.push_back(v2(x + 1, Pos.Y + 1));
1461         Border.push_back(v2(x + 1, Pos.Y + Size.Y - 2));
1462         continue;
1463       }
1464       else if(x == Pos.X + Size.X - 1)
1465       {
1466         CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x - 1, Pos.Y + 1, Room, Flags);
1467         CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x - 1, Pos.Y + Size.Y - 2, Room, Flags);
1468         Border.push_back(v2(x - 1, Pos.Y + 1));
1469         Border.push_back(v2(x - 1, Pos.Y + Size.Y - 2));
1470         continue;
1471       }
1472     }
1473 
1474     CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x, Pos.Y, Room, Flags);
1475     CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x, Pos.Y + Size.Y - 1, Room, Flags);
1476 
1477     if((Shape == RECTANGLE && x != Pos.X && x != Pos.X + Size.X - 1)
1478        || (Shape == ROUND_CORNERS && x > Pos.X + 1 && x < Pos.X + Size.X - 2))
1479     {
1480       OKForDoor.push_back(v2(x, Pos.Y));
1481       OKForDoor.push_back(v2(x, Pos.Y + Size.Y - 1));
1482 
1483       if((!AllowLanterns || !GenerateLanterns(x, Pos.Y, DOWN)) && AllowWindows)
1484         GenerateWindows(x, Pos.Y);
1485 
1486       if((!AllowLanterns || !GenerateLanterns(x, Pos.Y + Size.Y - 1, UP)) && AllowWindows)
1487         GenerateWindows(x, Pos.Y + Size.Y - 1);
1488     }
1489 
1490     Border.push_back(v2(x, Pos.Y));
1491     Border.push_back(v2(x, Pos.Y + Size.Y - 1));
1492   }
1493 
1494   game::BusyAnimation();
1495 
1496   for(y = Pos.Y + 1; y < Pos.Y + Size.Y - 1; ++y, Counter += 2)
1497   {
1498     CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), Pos.X, y, Room, Flags);
1499     CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), Pos.X + Size.X - 1, y, Room, Flags);
1500 
1501     if(Shape == RECTANGLE
1502        || (Shape == ROUND_CORNERS && y != Pos.Y + 1 && y != Pos.Y + Size.Y - 2))
1503     {
1504       OKForDoor.push_back(v2(Pos.X, y));
1505       OKForDoor.push_back(v2(Pos.X + Size.X - 1, y));
1506 
1507       if((!AllowLanterns || !GenerateLanterns(Pos.X, y, RIGHT)) && AllowWindows)
1508         GenerateWindows(Pos.X, y);
1509 
1510       if((!AllowLanterns || !GenerateLanterns(Pos.X + Size.X - 1, y, LEFT)) && AllowWindows)
1511         GenerateWindows(Pos.X + Size.X - 1, y);
1512     }
1513 
1514     Border.push_back(v2(Pos.X, y));
1515     Border.push_back(v2(Pos.X + Size.X - 1, y));
1516   }
1517   // Maze rooms only: put in the doors, in the corners.
1518   if(Shape == MAZE_ROOM)
1519   {
1520     int MazeDoors = RAND() % 4;
1521     switch(MazeDoors)
1522     {
1523      case 0:
1524       OKForDoor.push_back(v2(Pos.X, Pos.Y + 1));
1525       OKForDoor.push_back(v2(Pos.X + Size.X - 1, Pos.Y + Size.Y - 2));
1526       break;
1527      case 1:
1528       OKForDoor.push_back(v2(Pos.X + 1, Pos.Y));
1529       OKForDoor.push_back(v2(Pos.X + Size.X - 2, Pos.Y + Size.Y - 1));
1530       break;
1531      case 2:
1532       OKForDoor.push_back(v2(Pos.X + 1, Pos.Y + Size.Y - 1));
1533       OKForDoor.push_back(v2(Pos.X + Size.X - 2, Pos.Y));
1534       break;
1535      case 3:
1536       OKForDoor.push_back(v2(Pos.X, Pos.Y + Size.Y - 2));
1537       OKForDoor.push_back(v2(Pos.X + Size.X - 1, Pos.Y + 1));
1538       break;
1539      default:
1540       break;
1541     }
1542   }
1543 
1544   GTerrain = RoomScript->GetFloorSquare()->GetGTerrain();
1545   OTerrain = RoomScript->GetFloorSquare()->GetOTerrain();
1546   Counter = 0;
1547   Flags = (GTerrain->IsInside() ? *GTerrain->IsInside() : *RoomScript->IsInside()) ? INSIDE : 0;
1548 
1549   for(x = Pos.X + 1; x < Pos.X + Size.X - 1; ++x)
1550     for(y = Pos.Y + 1; y < Pos.Y + Size.Y - 1; ++y, ++Counter)
1551     {
1552       /* if not in the corner */
1553 
1554       if(!(Shape == MAZE_ROOM) && !(Shape == ROUND_CORNERS
1555            && (x == Pos.X + 1 || x == Pos.X + Size.X - 2)
1556            && (y == Pos.Y + 1 || y == Pos.Y + Size.Y - 2)))
1557       {
1558         CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x, y, Room, Flags);
1559         Inside.push_back(v2(x, y));
1560       }
1561     }
1562   // Maze rooms only: put in the internal walls.
1563   for(y = Pos.Y + 1; y < Pos.Y + Size.Y - 1; ++y)
1564     for(x = Pos.X + 1; x < Pos.X + Size.X - 1; ++x, ++Counter)
1565     {
1566       // 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(?)).
1567       if((Shape == MAZE_ROOM) && !MazeRoom.MazeKernel[(y - Pos.Y - 1) * (Size.X - 2) + (x - Pos.X - 1)])
1568       {
1569         if(*RoomScript->UseFillSquareWalls())
1570         {
1571           GTerrain = LevelScript->GetFillSquare()->GetGTerrain();
1572           OTerrain = LevelScript->GetFillSquare()->GetOTerrain();
1573         }
1574         else
1575         {
1576           GTerrain = RoomScript->GetWallSquare()->GetGTerrain();
1577           OTerrain = RoomScript->GetWallSquare()->GetOTerrain();
1578         }
1579         CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x, y, Room, Flags);
1580       }
1581       else if((Shape == MAZE_ROOM) && MazeRoom.MazeKernel[(y - Pos.Y - 1) * (Size.X - 2) + (x - Pos.X - 1)]) // Put in a floor:
1582       {
1583         GTerrain = RoomScript->GetFloorSquare()->GetGTerrain();
1584         OTerrain = RoomScript->GetFloorSquare()->GetOTerrain();
1585         CreateRoomSquare(GTerrain->Instantiate(), OTerrain->Instantiate(), x, y, Room, Flags);
1586         Inside.push_back(v2(x, y));
1587       }
1588     }
1589 }
1590 
Reveal()1591 void level::Reveal()
1592 {
1593   ulong Tick = game::GetLOSTick();
1594 
1595   for(int x = 0; x < XSize; ++x)
1596     for(int y = 0; y < YSize; ++y)
1597       Map[x][y]->Reveal(Tick);
1598 }
1599 
ParticleBeam(beamdata & Beam)1600 void level::ParticleBeam(beamdata& Beam)
1601 {
1602   v2 CurrentPos = Beam.StartPos;
1603 
1604   if(Beam.Direction != YOURSELF)
1605   {
1606     for(int Length = 0; Length < Beam.Range; ++Length)
1607     {
1608       CurrentPos += game::GetMoveVector(Beam.Direction);
1609 
1610       if(!IsValidPos(CurrentPos))
1611         break;
1612 
1613       lsquare* CurrentSquare = GetLSquare(CurrentPos);
1614 
1615       if(!CurrentSquare->IsFlyable())
1616       {
1617         (CurrentSquare->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam);
1618         break;
1619       }
1620       else
1621       {
1622         CurrentSquare->DrawParticles(Beam.BeamColor);
1623 
1624         if((CurrentSquare->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam))
1625           break;
1626       }
1627     }
1628   }
1629   else
1630   {
1631     lsquare* Where = GetLSquare(CurrentPos);
1632     Where->DrawParticles(Beam.BeamColor);
1633     (Where->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam);
1634   }
1635 }
1636 
1637 /* Note: You will most likely need some help from supernatural entities to comprehend this code. Sorry. */
1638 
LightningBeam(beamdata & Beam)1639 void level::LightningBeam(beamdata& Beam)
1640 {
1641   v2 CurrentPos = Beam.StartPos;
1642 
1643   if(Beam.Direction == YOURSELF)
1644   {
1645     lsquare* Where = GetLSquare(CurrentPos);
1646 
1647     for(int c = 0; c < 4; ++c)
1648       Where->DrawLightning(v2(8, 8), Beam.BeamColor, YOURSELF);
1649 
1650     (Where->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam);
1651     return;
1652   }
1653 
1654   v2 StartPos;
1655 
1656   switch(Beam.Direction)
1657   {
1658    case NORTHWEST: StartPos = v2(15, 15); break;
1659    case NORTH: StartPos = v2(RAND() & 15, 15); break;
1660    case NORTHEAST: StartPos = v2(0, 15); break;
1661    case WEST: StartPos = v2(15, RAND() & 15); break;
1662    case EAST: StartPos = v2(0, RAND() & 15); break;
1663    case SOUTHWEST: StartPos = v2(15, 0); break;
1664    case SOUTH: StartPos = v2(RAND() & 15, 0); break;
1665    case SOUTHEAST: StartPos = v2(0, 0); break;
1666   }
1667 
1668   for(int Length = 0; Length < Beam.Range; ++Length)
1669   {
1670     CurrentPos += game::GetMoveVector(Beam.Direction);
1671 
1672     if(!IsValidPos(CurrentPos))
1673       break;
1674 
1675     lsquare* CurrentSquare = GetLSquare(CurrentPos);
1676 
1677     if(!CurrentSquare->IsFlyable())
1678     {
1679       if((CurrentSquare->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam))
1680         break;
1681 
1682       truth W1, W2;
1683 
1684       switch(Beam.Direction)
1685       {
1686        case NORTHWEST:
1687         W1 = GetLSquare(CurrentPos + v2(1, 0))->IsFlyable();
1688         W2 = GetLSquare(CurrentPos + v2(0, 1))->IsFlyable();
1689 
1690         if(W1 == W2)
1691           Beam.Direction = SOUTHEAST;
1692         else if(W1)
1693         {
1694           ++CurrentPos.Y;
1695           Beam.Direction = NORTHEAST;
1696         }
1697         else
1698         {
1699           ++CurrentPos.X;
1700           Beam.Direction = SOUTHWEST;
1701         }
1702 
1703         break;
1704        case NORTH: Beam.Direction = SOUTH; StartPos.Y = 0; break;
1705        case NORTHEAST:
1706         W1 = GetLSquare(CurrentPos + v2(-1, 0))->IsFlyable();
1707         W2 = GetLSquare(CurrentPos + v2(0, 1))->IsFlyable();
1708 
1709         if(W1 == W2)
1710           Beam.Direction = SOUTHWEST;
1711         else if(W1)
1712         {
1713           ++CurrentPos.Y;
1714           Beam.Direction = NORTHWEST;
1715         }
1716         else
1717         {
1718           --CurrentPos.X;
1719           Beam.Direction = SOUTHEAST;
1720         }
1721 
1722         break;
1723        case WEST: Beam.Direction = EAST; StartPos.X = 0; break;
1724        case EAST: Beam.Direction = WEST; StartPos.X = 15; break;
1725        case SOUTHWEST:
1726         W1 = GetLSquare(CurrentPos + v2(1, 0))->IsFlyable();
1727         W2 = GetLSquare(CurrentPos + v2(0, -1))->IsFlyable();
1728 
1729         if(W1 == W2)
1730           Beam.Direction = NORTHEAST;
1731         else if(W1)
1732         {
1733           --CurrentPos.Y;
1734           Beam.Direction = SOUTHEAST;
1735         }
1736         else
1737         {
1738           ++CurrentPos.X;
1739           Beam.Direction = NORTHWEST;
1740         }
1741 
1742         break;
1743        case SOUTH: Beam.Direction = NORTH; StartPos.Y = 15; break;
1744        case SOUTHEAST:
1745         W1 = GetLSquare(CurrentPos + v2(-1, 0))->IsFlyable();
1746         W2 = GetLSquare(CurrentPos + v2(0, -1))->IsFlyable();
1747 
1748         if(W1 == W2)
1749           Beam.Direction = NORTHWEST;
1750         else if(W1)
1751         {
1752           --CurrentPos.Y;
1753           Beam.Direction = SOUTHWEST;
1754         }
1755         else
1756         {
1757           --CurrentPos.X;
1758           Beam.Direction = NORTHEAST;
1759         }
1760 
1761         break;
1762       }
1763 
1764       switch(Beam.Direction)
1765       {
1766        case NORTHWEST: StartPos = v2(15, 15); break;
1767        case NORTHEAST: StartPos = v2(0, 15); break;
1768        case SOUTHWEST: StartPos = v2(15, 0); break;
1769        case SOUTHEAST: StartPos = v2(0, 0); break;
1770       }
1771     }
1772     else
1773     {
1774       StartPos = CurrentSquare->DrawLightning(StartPos, Beam.BeamColor, Beam.Direction);
1775 
1776       if((CurrentSquare->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam))
1777         break;
1778     }
1779   }
1780 }
1781 
ShieldBeam(beamdata & Beam)1782 void level::ShieldBeam(beamdata& Beam)
1783 {
1784   v2 Pos[3];
1785 
1786   switch(Beam.Direction)
1787   {
1788    case NORTHWEST:
1789     Pos[0] = v2(-1, 0);
1790     Pos[1] = v2(-1, -1);
1791     Pos[2] = v2(0, -1);
1792     break;
1793    case NORTH:
1794     Pos[0] = v2(-1, -1);
1795     Pos[1] = v2(0, -1);
1796     Pos[2] = v2(1, -1);
1797     break;
1798    case NORTHEAST:
1799     Pos[0] = v2(0, -1);
1800     Pos[1] = v2(1, -1);
1801     Pos[2] = v2(1, 0);
1802     break;
1803    case WEST:
1804     Pos[0] = v2(-1, 1);
1805     Pos[1] = v2(-1, 0);
1806     Pos[2] = v2(-1, -1);
1807     break;
1808    case EAST:
1809     Pos[0] = v2(1, -1);
1810     Pos[1] = v2(1, 0);
1811     Pos[2] = v2(1, 1);
1812     break;
1813    case SOUTHWEST:
1814     Pos[0] = v2(0, 1);
1815     Pos[1] = v2(-1, 1);
1816     Pos[2] = v2(-1, 0);
1817     break;
1818    case SOUTH:
1819     Pos[0] = v2(1, 1);
1820     Pos[1] = v2(0, 1);
1821     Pos[2] = v2(-1, 1);
1822     break;
1823    case SOUTHEAST:
1824     Pos[0] = v2(1, 0);
1825     Pos[1] = v2(1, 1);
1826     Pos[2] = v2(0, 1);
1827     break;
1828    case YOURSELF:
1829     GetLSquare(Beam.StartPos)->DrawParticles(Beam.BeamColor);
1830     (GetLSquare(Beam.StartPos)->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam);
1831     return;
1832   }
1833 
1834   for(int c = 0; c < 3; ++c)
1835     if(IsValidPos(Beam.StartPos + Pos[c]))
1836     {
1837       GetLSquare(Beam.StartPos + Pos[c])->DrawParticles(Beam.BeamColor);
1838       (GetLSquare(Beam.StartPos + Pos[c])->*lsquare::GetBeamEffect(Beam.BeamEffect))(Beam);
1839     }
1840 }
1841 
operator <<(outputfile & SaveFile,const level * Level)1842 outputfile& operator<<(outputfile& SaveFile, const level* Level)
1843 {
1844   Level->Save(SaveFile);
1845   return SaveFile;
1846 }
1847 
operator >>(inputfile & SaveFile,level * & Level)1848 inputfile& operator>>(inputfile& SaveFile, level*& Level)
1849 {
1850   Level = new level;
1851   Level->Load(SaveFile);
1852   return SaveFile;
1853 }
1854 
1855 void (level::*Beam[BEAM_STYLES])(beamdata&) =
1856 {
1857   &level::ParticleBeam,
1858   &level::LightningBeam,
1859   &level::ShieldBeam
1860 };
1861 
1862 void (level::*level::GetBeam(int I))(beamdata&)
1863 {
1864   return Beam[I];
1865 }
1866 
FreeSquareSeeker(ccharacter * Char,v2 StartPos,v2 Prohibited,int MaxDistance,truth AllowStartPos) const1867 v2 level::FreeSquareSeeker(ccharacter* Char, v2 StartPos, v2 Prohibited, int MaxDistance, truth AllowStartPos) const
1868 {
1869   int c;
1870 
1871   for(c = 0; c < 8; ++c)
1872   {
1873     v2 Pos = StartPos + game::GetMoveVector(c);
1874 
1875     if(IsValidPos(Pos) && Char->CanMoveOn(GetLSquare(Pos)) && Char->IsFreeForMe(GetLSquare(Pos))
1876        && Pos != Prohibited && (AllowStartPos || !Char->PlaceIsIllegal(Pos, Prohibited)))
1877       return Pos;
1878   }
1879 
1880   if(MaxDistance)
1881     for(c = 0; c < 8; ++c)
1882     {
1883       v2 Pos = StartPos + game::GetMoveVector(c);
1884 
1885       if(IsValidPos(Pos))
1886       {
1887         if(Char->CanMoveOn(GetLSquare(Pos)) && Pos != Prohibited)
1888         {
1889           Pos = FreeSquareSeeker(Char, Pos, Prohibited, MaxDistance - 1, AllowStartPos);
1890 
1891           if(Pos != ERROR_V2)
1892             return Pos;
1893         }
1894       }
1895     }
1896 
1897   return ERROR_V2;
1898 }
1899 
1900 /* Returns ERROR_V2 if no free square was found */
1901 
GetNearestFreeSquare(ccharacter * Char,v2 StartPos,truth AllowStartPos) const1902 v2 level::GetNearestFreeSquare(ccharacter* Char, v2 StartPos, truth AllowStartPos) const
1903 {
1904   if(AllowStartPos && Char->CanMoveOn(GetLSquare(StartPos)) && Char->IsFreeForMe(GetLSquare(StartPos)))
1905     return StartPos;
1906 
1907   int c;
1908 
1909   for(c = 0; c < 8; ++c)
1910   {
1911     v2 Pos = StartPos + game::GetMoveVector(c);
1912 
1913     if(IsValidPos(Pos) && Char->CanMoveOn(GetLSquare(Pos)) && Char->IsFreeForMe(GetLSquare(Pos))
1914        && (AllowStartPos || !Char->PlaceIsIllegal(Pos, StartPos)))
1915       return Pos;
1916   }
1917 
1918   for(int Dist = 0; Dist < 5; ++Dist)
1919     for(c = 0; c < 8; ++c)
1920     {
1921       v2 Pos = StartPos + game::GetMoveVector(c);
1922 
1923       if(IsValidPos(Pos) && Char->CanMoveOn(GetLSquare(Pos)))
1924       {
1925         Pos = FreeSquareSeeker(Char, Pos, StartPos, Dist, AllowStartPos);
1926 
1927         if(Pos != ERROR_V2)
1928           return Pos;
1929       }
1930     }
1931 
1932   return ERROR_V2;
1933 }
1934 
GetFreeAdjacentSquare(ccharacter * Char,v2 StartPos,truth AllowCharacter) const1935 v2 level::GetFreeAdjacentSquare(ccharacter* Char, v2 StartPos, truth AllowCharacter) const
1936 {
1937   int PossibleDir[8];
1938   int Index = 0;
1939   lsquare* Origo = GetLSquare(StartPos);
1940 
1941   for(int d = 0; d < 8; ++d)
1942   {
1943     lsquare* Square = Origo->GetNeighbourLSquare(d);
1944 
1945     if(Square && Char->CanMoveOn(Square) && (AllowCharacter || Char->IsFreeForMe(Square)))
1946       PossibleDir[Index++] = d;
1947   }
1948 
1949   return Index ? StartPos + game::GetMoveVector(PossibleDir[RAND() % Index]) : ERROR_V2;
1950 }
1951 
1952 void (level::*level::GetBeamEffectVisualizer(int I))(const fearray<lsquare*>&, col16) const
1953 {
1954   static void (level::*Visualizer[BEAM_STYLES])(const fearray<lsquare*>&, col16) const =
1955   { &level::ParticleVisualizer, &level::LightningVisualizer, &level::ParticleVisualizer };
1956   return Visualizer[I];
1957 }
1958 
ParticleVisualizer(const fearray<lsquare * > & Stack,col16 BeamColor) const1959 void level::ParticleVisualizer(const fearray<lsquare*>& Stack, col16 BeamColor) const
1960 {
1961   clock_t StartTime = clock();
1962   game::DrawEverythingNoBlit();
1963 
1964   for(fearray<lsquare*>::sizetype c = 0; c < Stack.Size; ++c)
1965     Stack[c]->DrawParticles(BeamColor, false);
1966 
1967   graphics::BlitDBToScreen();
1968   while(clock() - StartTime < 0.05 * CLOCKS_PER_SEC);
1969 }
1970 
LightningVisualizer(const fearray<lsquare * > & Stack,col16 BeamColor) const1971 void level::LightningVisualizer(const fearray<lsquare*>& Stack, col16 BeamColor) const
1972 {
1973   clock_t StartTime = clock();
1974   game::DrawEverythingNoBlit();
1975 
1976   for(fearray<lsquare*>::sizetype c = 0; c < Stack.Size; ++c)
1977     Stack[c]->DrawLightning(v2(8, 8), BeamColor, YOURSELF, false);
1978 
1979   graphics::BlitDBToScreen();
1980   while(clock() - StartTime < 0.05 * CLOCKS_PER_SEC);
1981 }
1982 
PreProcessForBone()1983 truth level::PreProcessForBone()
1984 {
1985   if(!*LevelScript->CanGenerateBone())
1986     return false;
1987 
1988   /* Gum solution */
1989 
1990   game::SetQuestMonstersFound(0);
1991 
1992   for(int x = 0; x < XSize; ++x)
1993     for(int y = 0; y < YSize; ++y)
1994       Map[x][y]->PreProcessForBone();
1995 
1996   int DungeonIndex = GetDungeon()->GetIndex();
1997 
1998   return !(DungeonIndex == ELPURI_CAVE && Index == IVAN_LEVEL && game::GetQuestMonstersFound() < 5)
1999     && (game::GetQuestMonstersFound() || ((DungeonIndex != UNDER_WATER_TUNNEL || Index != VESANA_LEVEL)
2000                                           && (DungeonIndex != ELPURI_CAVE
2001                                               || (Index != ENNER_BEAST_LEVEL && Index != DARK_LEVEL))));
2002 }
2003 
PostProcessForBone()2004 truth level::PostProcessForBone()
2005 {
2006   game::SetTooGreatDangerFound(false);
2007   double DangerSum = 0;
2008   int Enemies = 0;
2009 
2010   for(int x = 0; x < XSize; ++x)
2011     for(int y = 0; y < YSize; ++y)
2012       Map[x][y]->PostProcessForBone(DangerSum, Enemies);
2013 
2014   if(game::TooGreatDangerFound() || (Enemies && DangerSum / Enemies > Difficulty * 10))
2015     return false;
2016 
2017   return true;
2018 }
2019 
FinalProcessForBone()2020 void level::FinalProcessForBone()
2021 {
2022   for(int x = 0; x < XSize; ++x)
2023     for(int y = 0; y < YSize; ++y)
2024       Map[x][y]->FinalProcessForBone();
2025 
2026   for(uint c = 1; c < Room.size(); ++c)
2027     Room[c]->FinalProcessForBone();
2028 }
2029 
GenerateDungeon(int Index)2030 void level::GenerateDungeon(int Index)
2031 {
2032   cfestring* Msg = LevelScript->GetLevelMessage();
2033 
2034   if(Msg)
2035     LevelMessage = *Msg;
2036 
2037   if(*LevelScript->GenerateMonsters())
2038   {
2039     MonsterGenerationInterval = *LevelScript->GetMonsterGenerationIntervalBase()
2040                               + *LevelScript->GetMonsterGenerationIntervalDelta() * Index;
2041     IdealPopulation = *LevelScript->GetMonsterAmountBase()
2042                     + *LevelScript->GetMonsterAmountDelta() * Index;
2043   }
2044 
2045   Difficulty = 0.001 * (*LevelScript->GetDifficultyBase()
2046                       + *LevelScript->GetDifficultyDelta() * Index);
2047   EnchantmentMinusChance = *LevelScript->GetEnchantmentMinusChanceBase()
2048                          + *LevelScript->GetEnchantmentMinusChanceDelta() * Index;
2049   EnchantmentPlusChance = *LevelScript->GetEnchantmentPlusChanceBase()
2050                         + *LevelScript->GetEnchantmentPlusChanceDelta() * Index;
2051   const contentscript<glterrain>* GTerrain = LevelScript->GetFillSquare()->GetGTerrain();
2052   const contentscript<olterrain>* OTerrain = LevelScript->GetFillSquare()->GetOTerrain();
2053   long Counter = 0;
2054   int x;
2055   game::BusyAnimation();
2056 
2057   for(x = 0; x < XSize; ++x)
2058     for(int y = 0; y < YSize; ++y, ++Counter)
2059       Map[x][y]->SetLTerrain(GTerrain->Instantiate(), OTerrain->Instantiate());
2060 
2061   uint c;
2062   uint Rooms = LevelScript->GetRooms()->Randomize();
2063   const std::list<roomscript>& RoomList = LevelScript->GetRoom();
2064   std::list<roomscript>::const_iterator Iterator = RoomList.begin();
2065 
2066   for(c = 0; c < Rooms; ++c)
2067   {
2068     game::BusyAnimation();
2069 
2070     if(c < RoomList.size())
2071     {
2072       int i;
2073 
2074       for(i = 0; i < 1000; ++i)
2075         if(MakeRoom(&*Iterator))
2076           break;
2077 
2078       if(i == 1000)
2079         ABORT("Failed to place special room #%d!", c);
2080 
2081       ++Iterator;
2082     }
2083     else
2084     {
2085       const roomscript* RoomScript = LevelScript->GetRoomDefault();
2086 
2087       for(int i = 0; i < 50; ++i)
2088         if(MakeRoom(RoomScript))
2089           break;
2090     }
2091   }
2092 
2093   game::BusyAnimation();
2094 
2095   if(!*LevelScript->IgnoreDefaultSpecialSquares())
2096   {
2097     /* Gum solution */
2098 
2099     const levelscript* LevelBase = static_cast<const levelscript*>(LevelScript->GetBase());
2100 
2101     if(LevelBase)
2102       for(const squarescript& Script : LevelBase->GetSquare())
2103       {
2104         game::BusyAnimation();
2105         ApplyLSquareScript(&Script);
2106       }
2107   }
2108 
2109   for(const squarescript& Script : LevelScript->GetSquare())
2110   {
2111     game::BusyAnimation();
2112     ApplyLSquareScript(&Script);
2113   }
2114 
2115   for(c = 0; c < AttachQueue.size(); ++c)
2116     AttachPos(AttachQueue[c].X, AttachQueue[c].Y);
2117 
2118   for(x = 0; x < XSize; ++x)
2119     for(int y = 0; y < YSize; ++y)
2120     {
2121       Map[x][y]->CalculateGroundBorderPartners();
2122       Map[x][y]->CalculateOverBorderPartners();
2123     }
2124 
2125   AttachQueue.clear();
2126   CreateItems(LevelScript->GetItems()->Randomize());
2127 }
2128 
GenerateJungle()2129 void level::GenerateJungle()
2130 {
2131   int x, y;
2132 
2133   for(x = 0; x < XSize; ++x)
2134     for(y = 0; y < YSize; ++y)
2135     {
2136       Map[x][y] = new lsquare(this, v2(x, y));
2137       Map[x][y]->SetLTerrain(solidterrain::Spawn(GRASS_TERRAIN), 0);
2138     }
2139 
2140   for(;;)
2141   {
2142     CreateTunnelNetwork(1, 4, 20, 120, v2(0, YSize / 2));
2143     CreateTunnelNetwork(1, 4, 20, 120, v2(XSize - 1, YSize / 2));
2144 
2145     for(int c = 0; c < 25; ++c)
2146     {
2147       v2 StartPos;
2148 
2149       switch(RAND_N(5))
2150       {
2151        case 0:
2152         StartPos = v2(RAND_N(XSize), 0);
2153         break;
2154        case 1:
2155         StartPos = v2(RAND_N(XSize), YSize - 1);
2156         break;
2157        case 2:
2158         StartPos = v2(0, RAND_N(YSize));
2159         break;
2160        case 3:
2161         StartPos = v2(XSize - 1, RAND_N(YSize));
2162         break;
2163        case 4:
2164         StartPos = v2(RAND_N(XSize), RAND_N(YSize));
2165       }
2166 
2167       CreateTunnelNetwork(1, 4, 20, 120, StartPos);
2168     }
2169 
2170     for(x = 0; x < XSize; ++x)
2171     {
2172       game::BusyAnimation();
2173 
2174       for(y = 0; y < YSize; ++y)
2175       {
2176         if(FlagMap[x][y] != PREFERRED)
2177           Map[x][y]->ChangeOLTerrain(wall::Spawn(BRICK_PROPAGANDA));
2178         else if(RAND_2)
2179           Map[x][y]->ChangeOLTerrain(decoration::Spawn(PALM));
2180       }
2181     }
2182   }
2183 }
2184 
CreateTunnelNetwork(int MinLength,int MaxLength,int MinNodes,int MaxNodes,v2 StartPos)2185 void level::CreateTunnelNetwork(int MinLength, int MaxLength, int MinNodes, int MaxNodes, v2 StartPos)
2186 {
2187   v2 Pos = StartPos, Direction;
2188   int Length;
2189   game::BusyAnimation();
2190   FlagMap[Pos.X][Pos.Y] = PREFERRED;
2191 
2192   for(int c1 = 0; c1 < MaxNodes; ++c1)
2193   {
2194     Direction = game::GetBasicMoveVector(RAND() % 4);
2195     Length = MinLength + RAND_N(MaxLength - MinLength + 1);
2196 
2197     for(int c2 = 0; c2 < Length; ++c2)
2198     {
2199       if(IsValidPos(Direction + Pos))
2200       {
2201         Pos += Direction;
2202         FlagMap[Pos.X][Pos.Y] = PREFERRED;
2203       }
2204       else
2205       {
2206         if(c1 >= MinNodes)
2207           return;
2208 
2209         break;
2210       }
2211     }
2212   }
2213 }
2214 
GenerateDesert()2215 void level::GenerateDesert()
2216 {
2217   for(int x = 0; x < XSize; ++x)
2218     for(int y = 0; y < YSize; ++y)
2219     {
2220       Map[x][y] = new lsquare(this, v2(x, y));
2221       Map[x][y]->SetLTerrain(solidterrain::Spawn(SAND_TERRAIN), 0);
2222     }
2223 
2224   game::BusyAnimation();
2225   int AmountOfCactuses = RAND_N(10);
2226   int c;
2227 
2228   for(c = 0; c < AmountOfCactuses; ++c)
2229     Map[RAND_N(XSize)][RAND_N(YSize)]->ChangeOLTerrain(decoration::Spawn(CACTUS));
2230 
2231   int AmountOfBoulders = RAND_N(10);
2232 
2233   for(c = 0; c < AmountOfBoulders; ++c)
2234     Map[RAND_N(XSize)][RAND_N(YSize)]->ChangeOLTerrain(boulder::Spawn(1 + RAND_2));
2235 }
2236 
GenerateSteppe()2237 void level::GenerateSteppe()
2238 {
2239   for(int x = 0; x < XSize; ++x)
2240     for(int y = 0; y < YSize; ++y)
2241     {
2242       Map[x][y] = new lsquare(this, v2(x, y));
2243       Map[x][y]->SetLTerrain(solidterrain::Spawn(GRASS_TERRAIN), 0);
2244     }
2245 
2246   game::BusyAnimation();
2247   int c;
2248 
2249   int AmountOfBoulders = RAND_N(20) + 5;
2250 
2251   for(c = 0; c < AmountOfBoulders; ++c)
2252     Map[RAND_N(XSize)][RAND_N(YSize)]->ChangeOLTerrain(boulder::Spawn(1 + RAND_2));
2253 }
2254 
GenerateLeafyForest()2255 void level::GenerateLeafyForest()
2256 {
2257   for(int x = 0; x < XSize; ++x)
2258     for(int y = 0; y < YSize; ++y)
2259     {
2260       Map[x][y] = new lsquare(this, v2(x, y));
2261       olterrain* OLTerrain;
2262 
2263       switch(RAND_4)
2264       {
2265        case 0:
2266         if(RAND_8)
2267           OLTerrain = decoration::Spawn(OAK);
2268         else
2269           OLTerrain = decoration::Spawn(TEAK);
2270         break;
2271        case 1:
2272         OLTerrain = decoration::Spawn(BIRCH);
2273         break;
2274        case 2:
2275         OLTerrain = 0;
2276         if(!RAND_4)
2277           OLTerrain = boulder::Spawn(1 + RAND_2);
2278 
2279         if(!RAND_4)
2280           OLTerrain = boulder::Spawn(3);
2281         break;
2282        default:
2283         OLTerrain = 0;
2284       }
2285 
2286       Map[x][y]->SetLTerrain(solidterrain::Spawn(GRASS_TERRAIN), OLTerrain);
2287     }
2288 }
2289 
GenerateEvergreenForest()2290 void level::GenerateEvergreenForest()
2291 {
2292   for(int x = 0; x < XSize; ++x)
2293     for(int y = 0; y < YSize; ++y)
2294     {
2295       Map[x][y] = new lsquare(this, v2(x, y));
2296       olterrain* OLTerrain = 0;
2297 
2298       switch(RAND_4)
2299       {
2300        case 0:
2301         if(RAND_2)
2302           OLTerrain = decoration::Spawn(PINE);
2303         break;
2304        case 1:
2305         OLTerrain = decoration::Spawn(FIR);
2306         break;
2307        case 2:
2308         if(!RAND_4)
2309           OLTerrain = boulder::Spawn(1 + RAND_2);
2310 
2311         if(!RAND_4)
2312           OLTerrain = boulder::Spawn(3);
2313         break;
2314       }
2315 
2316       Map[x][y]->SetLTerrain(solidterrain::Spawn(GRASS_TERRAIN), OLTerrain);
2317     }
2318 }
2319 
GenerateTundra()2320 void level::GenerateTundra()
2321 {
2322   for(int x = 0; x < XSize; ++x)
2323     for(int y = 0; y < YSize; ++y)
2324     {
2325       Map[x][y] = new lsquare(this, v2(x, y));
2326       Map[x][y]->SetLTerrain(solidterrain::Spawn(SNOW_TERRAIN), 0);
2327     }
2328 
2329   game::BusyAnimation();
2330   int c;
2331   int AmountOfBoulders = RAND_N(20) + 8;
2332 
2333   for(c = 0; c < AmountOfBoulders; ++c)
2334     Map[RAND_N(XSize)][RAND_N(YSize)]->ChangeOLTerrain(boulder::Spawn(SNOW_BOULDER));
2335 
2336   int AmountOfDwarfBirches = RAND_N(10);
2337 
2338   for(c = 0; c < AmountOfDwarfBirches; ++c)
2339     Map[RAND_N(XSize)][RAND_N(YSize)]->ChangeOLTerrain(decoration::Spawn(DWARF_BIRCH));
2340 }
2341 
GenerateGlacier()2342 void level::GenerateGlacier()
2343 {
2344   int x, y;
2345 
2346   for(x = 0; x < XSize; ++x)
2347     for(y = 0; y < YSize; ++y)
2348     {
2349       Map[x][y] = new lsquare(this, v2(x, y));
2350       Map[x][y]->SetLTerrain(solidterrain::Spawn(SNOW_TERRAIN), 0);
2351     }
2352 
2353   int AmountOfBoulders = RAND_N(20) + 5;
2354 
2355   for(int c = 0; c < AmountOfBoulders; ++c)
2356     Map[RAND_N(XSize)][RAND_N(YSize)]->ChangeOLTerrain(boulder::Spawn(SNOW_BOULDER));
2357 
2358   for(;;)
2359   {
2360     CreateTunnelNetwork(1, 4, 20, 120, v2(0, YSize / 2));
2361     CreateTunnelNetwork(1, 4, 20, 120, v2(XSize - 1, YSize / 2));
2362 
2363     for(int c = 0; c < 20; ++c)
2364     {
2365       v2 StartPos;
2366 
2367       switch(RAND_N(5))
2368       {
2369        case 0:
2370         StartPos = v2(RAND_N(XSize), 0);
2371         break;
2372        case 1:
2373         StartPos = v2(RAND_N(XSize), YSize - 1);
2374         break;
2375        case 2:
2376         StartPos = v2(0, RAND_N(YSize));
2377         break;
2378        case 3:
2379         StartPos = v2(XSize - 1, RAND_N(YSize));
2380         break;
2381        case 4:
2382         StartPos = v2(RAND_N(XSize), RAND_N(YSize));
2383       }
2384 
2385       CreateTunnelNetwork(1, 4, 20, 120, StartPos);
2386     }
2387 
2388     for(x = 0; x < XSize; ++x)
2389       for(y = 0; y < YSize; ++y)
2390         if(FlagMap[x][y] != PREFERRED)
2391           FlagMap[x][y] |= RAND_2 ? ICE_TERRAIN : STONE_TERRAIN;
2392 
2393     for(x = 0; x < XSize; ++x)
2394     {
2395       game::BusyAnimation();
2396 
2397       for(y = 0; y < YSize; ++y)
2398       {
2399         if(!(FlagMap[x][y] & PREFERRED))
2400         {
2401           int SquaresAround = 0;
2402           int IceAround = 0;
2403 
2404           for(int d = 0; d < 8; ++d)
2405           {
2406             v2 Pos = v2(x, y) + game::GetMoveVector(d);
2407             if(IsValidPos(Pos) && !(FlagMap[Pos.X][Pos.Y] & PREFERRED))
2408             {
2409               ++SquaresAround;
2410               if(FlagMap[Pos.X][Pos.Y] & ICE_TERRAIN)
2411                 ++IceAround;
2412             }
2413           }
2414 
2415           if(IceAround > SquaresAround / 2)
2416             FlagMap[x][y] = ICE_TERRAIN;
2417           else
2418             FlagMap[x][y] = STONE_TERRAIN;
2419         }
2420       }
2421     }
2422 
2423     for(x = 0; x < XSize; ++x)
2424       for(y = 0; y < YSize; ++y)
2425         if(!(FlagMap[x][y] & PREFERRED))
2426         {
2427           if(FlagMap[x][y] & ICE_TERRAIN)
2428             GetLSquare(x, y)->ChangeOLTerrain(wall::Spawn(ICE_WALL));
2429           else
2430             GetLSquare(x, y)->ChangeOLTerrain(wall::Spawn(STONE_WALL));
2431         }
2432 
2433     break; // Doesn't yet check path in any way
2434   }
2435 }
2436 
operator <(const nodepointerstorer & N) const2437 bool nodepointerstorer::operator<(const nodepointerstorer& N) const
2438 {
2439   /* In the non-euclidean geometry of IVAN, certain very curved paths are as long as straight ones.
2440      However, they are so ugly that it is best to prefer routes with as few diagonal moves as
2441      possible without lengthening the travel. */
2442 
2443   if(Node->TotalDistanceEstimate != N.Node->TotalDistanceEstimate)
2444     return Node->TotalDistanceEstimate > N.Node->TotalDistanceEstimate;
2445   else
2446     return Node->Diagonals > N.Node->Diagonals;
2447 }
2448 
CalculateNextNodes()2449 void node::CalculateNextNodes()
2450 {
2451   static int TryOrder[8] = { 1, 3, 4, 6, 0, 2, 5, 7 };
2452 
2453   for(int d = 0; d < 8; ++d)
2454   {
2455     v2 NodePos = Pos + game::GetMoveVector(TryOrder[d]);
2456 
2457     if(NodePos.X >= 0 && NodePos.Y >= 0 && NodePos.X < XSize && NodePos.Y < YSize)
2458     {
2459       node* Node = NodeMap[NodePos.X][NodePos.Y];
2460 
2461       if(!Node->Processed && ((!SpecialMover && RequiredWalkability & WalkabilityMap[NodePos.X][NodePos.Y])
2462                               || (SpecialMover && SpecialMover->CanTheoreticallyMoveOn(Node->Square))
2463                               || NodePos == To))
2464       {
2465         Node->Processed = true;
2466         Node->Distance = Distance + 1;
2467         Node->Diagonals = Diagonals;
2468 
2469         if(d >= 4)
2470           ++Node->Diagonals;
2471 
2472         Node->Last = this;
2473 
2474         /* We use the heuristic max(abs(distance.x), abs(distance.y)) here,
2475            which is exact in the current geometry if the path is open */
2476 
2477         long Remaining = To.X - NodePos.X;
2478 
2479         if(Remaining < NodePos.X - To.X)
2480           Remaining = NodePos.X - To.X;
2481 
2482         if(Remaining < NodePos.Y - To.Y)
2483           Remaining = NodePos.Y - To.Y;
2484 
2485         if(Remaining < To.Y - NodePos.Y)
2486           Remaining = To.Y - NodePos.Y;
2487 
2488         Node->Remaining = Remaining;
2489         Node->TotalDistanceEstimate = Node->Distance + Node->Remaining;
2490         NodeQueue->push(nodepointerstorer(Node));
2491       }
2492     }
2493   }
2494 }
2495 
2496 /* Finds the shortest (but possibly not the shortest-looking) path between From and To
2497    if such exists. Returns a pointer to the node associated with the last square or zero if
2498    a route can't be found. Calling FindRoute again may invalidate the node, so you must
2499    store the path in another format ASAP. */
2500 
FindRoute(v2 From,v2 To,const std::set<v2> & Illegal,int RequiredWalkability,ccharacter * SpecialMover)2501 node* level::FindRoute(v2 From, v2 To, const std::set<v2>& Illegal, int RequiredWalkability, ccharacter* SpecialMover)
2502 {
2503   node::NodeMap = NodeMap;
2504   node::RequiredWalkability = RequiredWalkability;
2505   node::SpecialMover = SpecialMover;
2506   node::To = To;
2507   node::WalkabilityMap = WalkabilityMap;
2508   node::XSize = XSize;
2509   node::YSize = YSize;
2510 
2511   if(!Illegal.empty() && Illegal.find(To) != Illegal.end())
2512     return 0;
2513 
2514   for(int x = 0; x < XSize; ++x)
2515     for(int y = 0; y < YSize; ++y)
2516       NodeMap[x][y]->Processed = false;
2517 
2518   node* Node = NodeMap[From.X][From.Y];
2519   Node->Last = 0;
2520   Node->Processed = true;
2521   Node->Distance = 0;
2522   Node->Diagonals = 0;
2523   nodequeue NodeQueue;
2524   NodeQueue.push(nodepointerstorer(Node));
2525   node::NodeQueue = &NodeQueue;
2526 
2527   while(!NodeQueue.empty())
2528   {
2529     Node = NodeQueue.top().Node;
2530     NodeQueue.pop();
2531 
2532     if(Node->Pos == To)
2533       return Node;
2534 
2535     if(Illegal.empty() || Illegal.find(Node->Pos) == Illegal.end())
2536       Node->CalculateNextNodes();
2537   }
2538 
2539   return 0;
2540 }
2541 
2542 /* All items on ground are moved to the IVector and all characters to CVector */
2543 
CollectEverything(itemvector & IVector,charactervector & CVector)2544 void level::CollectEverything(itemvector& IVector, charactervector& CVector)
2545 {
2546   for(int x = 0; x < XSize; ++x)
2547     for(int y = 0; y < YSize; ++y)
2548     {
2549       lsquare* LS = Map[x][y];
2550       LS->GetStack()->MoveItemsTo(IVector, CENTER);
2551       character* C = LS->GetCharacter();
2552 
2553       if(C && !C->IsPlayer())
2554       {
2555         C->Remove();
2556         CVector.push_back(C);
2557       }
2558     }
2559 }
2560 
CreateGlobalRain(liquid * Liquid,v2 Speed)2561 void level::CreateGlobalRain(liquid* Liquid, v2 Speed)
2562 {
2563   GlobalRainLiquid = Liquid;
2564   GlobalRainSpeed = Speed;
2565 
2566   for(int x = 0; x < XSize; ++x)
2567     for(int y = 0; y < YSize; ++y)
2568       if(!Map[x][y]->IsInside())
2569         Map[x][y]->AddRain(Liquid, Speed, MONSTER_TEAM, false);
2570 }
2571 
CheckSunLight()2572 void level::CheckSunLight()
2573 {
2574   if(Index == 0 && GetDungeon()->GetIndex() == NEW_ATTNAM)
2575   {
2576     double Cos = cos(FPI * (game::GetTick() % 48000) / 24000.);
2577 
2578     if(Cos > 0.01)
2579     {
2580       int E = int(100 + Cos * 30);
2581       SunLightEmitation = MakeRGB24(E, E, E);
2582       AmbientLuminance = MakeRGB24(E - 6, E - 6, E - 6);
2583     }
2584     else
2585     {
2586       SunLightEmitation = 0;
2587       AmbientLuminance = NightAmbientLuminance;
2588     }
2589   }
2590   else if(Index == 0 && GetDungeon()->GetIndex() == ATTNAM)
2591   {
2592     double Cos = cos(FPI * (game::GetTick() % 48000) / 24000.);
2593 
2594     if(Cos > 0.41)
2595     {
2596       int E = int(100 + (Cos - 0.40) * 40);
2597       SunLightEmitation = MakeRGB24(E, E, E);
2598       AmbientLuminance = MakeRGB24(E - 8, E - 8, E - 8);
2599     }
2600     else
2601     {
2602       SunLightEmitation = 0;
2603       AmbientLuminance = NightAmbientLuminance;
2604     }
2605   }
2606   else if(Index == 0 && GetDungeon()->GetIndex() == XINROCH_TOMB)
2607   {
2608     double Cos = cos(FPI * (game::GetTick() % 48000) / 24000.);
2609 
2610     if(Cos > 0.51)
2611     {
2612       int E = int(100 + (Cos - 0.50) * 20);
2613       SunLightEmitation = MakeRGB24(E + 20, E, E);
2614       AmbientLuminance = MakeRGB24(E + 15, E - 5, E - 5);
2615     }
2616     else
2617     {
2618       SunLightEmitation = 0;
2619       AmbientLuminance = NightAmbientLuminance;
2620     }
2621   }
2622   else if(IsOnGround())
2623   {
2624     double Cos = cos(FPI * (game::GetTick() % 48000) / 24000.);
2625 
2626     if(Cos > 0.31)
2627     {
2628       int E = int(100 + (Cos - 0.30) * 30);
2629       SunLightEmitation = MakeRGB24(E, E, E);
2630       AmbientLuminance = MakeRGB24(E - 8, E - 8, E - 8);
2631     }
2632     else
2633     {
2634       SunLightEmitation = 0;
2635       AmbientLuminance = NightAmbientLuminance;
2636     }
2637   }
2638   else
2639     return;
2640 
2641   SunLightDirection = game::GetSunLightDirectionVector();
2642   ChangeSunLight();
2643 }
2644 
ChangeSunLight()2645 void level::ChangeSunLight()
2646 {
2647   truth SunSet = game::IsDark(SunLightEmitation);
2648   ulong c;
2649 
2650   for(c = 0; c < XSizeTimesYSize; ++c)
2651     Map[0][c]->RemoveSunLight();
2652 
2653   if(!SunSet)
2654     EmitSunBeams();
2655 
2656   for(c = 0; c < XSizeTimesYSize; ++c)
2657   {
2658     lsquare* Square = Map[0][c];
2659 
2660     if(Square->Flags & IS_TRANSPARENT)
2661       Square->CalculateSunLightLuminance(EMITTER_SQUARE_PART_BITS);
2662 
2663     if(!Square->IsInside())
2664       Square->AmbientLuminance = AmbientLuminance;
2665 
2666     Square->SendSunLightSignals();
2667   }
2668 
2669   for(c = 0; c < XSizeTimesYSize; ++c)
2670     Map[0][c]->CheckIfIsSecondarySunLightEmitter();
2671 }
2672 
InitSquarePartEmitationTicks()2673 void level::InitSquarePartEmitationTicks()
2674 {
2675   for(int x = 0; x < XSize; ++x)
2676     for(int y = 0; y < YSize; ++y)
2677       Map[x][y]->SquarePartEmitationTick = 0;
2678 }
2679 
GenerateWindows(int X,int Y) const2680 truth level::GenerateWindows(int X, int Y) const
2681 {
2682   olterrain* Terrain = Map[X][Y]->GetOLTerrain();
2683 
2684   if(Terrain && Terrain->CreateWindowConfigurations() && !(RAND() % 6))
2685   {
2686     Terrain->SetConfig(Terrain->GetConfig() | WINDOW);
2687     Map[X][Y]->CalculateIsTransparent();
2688     return true;
2689   }
2690 
2691   return false;
2692 }
2693 
2694 struct sunbeamcontroller : public stackcontroller
2695 {
2696   static truth Handler(int, int);
2697   static void ProcessStack();
2698   static ulong ID;
2699   static int SunLightBlockHeight;
2700   static v2 SunLightBlockPos;
2701   static truth ReSunEmitation;
2702 };
2703 
2704 ulong sunbeamcontroller::ID;
2705 int sunbeamcontroller::SunLightBlockHeight;
2706 v2 sunbeamcontroller::SunLightBlockPos;
2707 truth sunbeamcontroller::ReSunEmitation;
2708 
ForceEmitterNoxify(const emittervector & Emitter) const2709 void level::ForceEmitterNoxify(const emittervector& Emitter) const
2710 {
2711   for(const emitter& e : Emitter)
2712   {
2713     ulong ID = e.ID;
2714     lsquare* Square = GetLSquare(ExtractPosFromEmitterID(ID));
2715 
2716     if(ID & SECONDARY_SUN_LIGHT)
2717       Square->Noxify(Square->SecondarySunLightEmitation, SECONDARY_SUN_LIGHT);
2718     else
2719       Square->Noxify(Square->Emitation);
2720   }
2721 }
2722 
ForceEmitterEmitation(const emittervector & Emitter,const sunemittervector & SunEmitter,ulong IDFlags) const2723 void level::ForceEmitterEmitation(const emittervector& Emitter,
2724                                   const sunemittervector& SunEmitter,
2725                                   ulong IDFlags) const
2726 {
2727   for(const emitter& e : Emitter)
2728   {
2729     ulong ID = e.ID;
2730     lsquare* Square = GetLSquare(ExtractPosFromEmitterID(ID));
2731 
2732     if(ID & SECONDARY_SUN_LIGHT)
2733       Square->Emitate(Square->SecondarySunLightEmitation, SECONDARY_SUN_LIGHT|IDFlags);
2734     else
2735       Square->Emitate(Square->Emitation, IDFlags);
2736   }
2737 
2738   {
2739     stackcontroller::Map = Map;
2740     stackcontroller::Stack = SquareStack;
2741     stackcontroller::StackIndex = 0;
2742     stackcontroller::LevelXSize = XSize;
2743     stackcontroller::LevelYSize = YSize;
2744     sunbeamcontroller::ReSunEmitation = true;
2745 
2746     for(sunemittervector::value_type SE : SunEmitter)
2747     {
2748       ulong ID = (SE & ~(EMITTER_SHADOW_BITS|EMITTER_SQUARE_PART_BITS)) | RE_SUN_EMITATED, SourceFlags;
2749       int X, Y;
2750 
2751       if(ID & ID_X_COORDINATE)
2752       {
2753         X = (ID & EMITTER_IDENTIFIER_BITS) - (XSize << 3);
2754         Y = ID & ID_BEGIN ? -1 : YSize;
2755         SourceFlags = ID & ID_BEGIN ? SP_BOTTOM : SP_TOP;
2756       }
2757       else
2758       {
2759         X = ID & ID_BEGIN ? -1 : XSize;
2760         Y = (ID & EMITTER_IDENTIFIER_BITS) - (YSize << 3);
2761         SourceFlags = ID & ID_BEGIN ? SP_RIGHT : SP_LEFT;
2762       }
2763 
2764       EmitSunBeam(v2(X, Y), ID, SourceFlags);
2765     }
2766 
2767     sunbeamcontroller::ProcessStack();
2768   }
2769 }
2770 
2771 struct loscontroller : public tickcontroller, public stackcontroller
2772 {
Handlerloscontroller2773   static truth Handler(int x, int y)
2774   {
2775     lsquare* Square = Map[x >> 1][y >> 1];
2776     culong SquareFlags = Square->Flags;
2777 
2778     if(SquareFlags & PERFECTLY_QUADRI_HANDLED)
2779       return true;
2780 
2781     if(!(SquareFlags & IN_SQUARE_STACK))
2782     {
2783       Square->Flags |= IN_SQUARE_STACK;
2784       Stack[StackIndex++] = Square;
2785     }
2786 
2787     if(SquareFlags & IS_TRANSPARENT)
2788     {
2789       Square->Flags |= PERFECTLY_QUADRI_HANDLED;
2790       return true;
2791     }
2792 
2793     cint SquarePartIndex = (x & 1) + ((y & 1) << 1);
2794     Square->SquarePartLastSeen = (Square->SquarePartLastSeen
2795                                  & ~SquarePartTickMask[SquarePartIndex])
2796                                  | ShiftedTick[SquarePartIndex];
2797     return false;
2798   }
GetTickReferenceloscontroller2799   static ulong& GetTickReference(int X, int Y)
2800   {
2801     return Map[X][Y]->SquarePartLastSeen;
2802   }
ProcessStackloscontroller2803   static void ProcessStack()
2804   {
2805     for(long c = 0; c < StackIndex; ++c)
2806       Stack[c]->SignalSeen(Tick);
2807   }
2808 };
2809 
UpdateLOS()2810 void level::UpdateLOS()
2811 {
2812   game::RemoveLOSUpdateRequest();
2813   stackcontroller::Map = Map;
2814   stackcontroller::Stack = SquareStack;
2815   stackcontroller::StackIndex = 0;
2816   tickcontroller::Tick = game::IncreaseLOSTick();
2817   tickcontroller::PrepareShiftedTick();
2818   int Radius = PLAYER->GetLOSRange();
2819 
2820   for(int c = 0; c < PLAYER->GetSquaresUnder(); ++c)
2821     mapmath<loscontroller>::DoQuadriArea(PLAYER->GetPos(c).X, PLAYER->GetPos(c).Y,
2822                                          Radius * Radius, XSize, YSize);
2823 
2824   loscontroller::ProcessStack();
2825 
2826   if(PLAYER->StateIsActivated(INFRA_VISION))
2827     for(int c = 0; c < game::GetTeams(); ++c)
2828       for(character* p : game::GetTeam(c)->GetMember())
2829         if(p->IsEnabled())
2830           p->SendNewDrawRequest();
2831 }
2832 
EnableGlobalRain()2833 void level::EnableGlobalRain()
2834 {
2835   for(int x = 0; x < XSize; ++x)
2836     for(int y = 0; y < YSize; ++y)
2837       Map[x][y]->EnableGlobalRain();
2838 }
2839 
DisableGlobalRain()2840 void level::DisableGlobalRain()
2841 {
2842   for(int x = 0; x < XSize; ++x)
2843     for(int y = 0; y < YSize; ++y)
2844       Map[x][y]->DisableGlobalRain();
2845 }
2846 
InitLastSeen()2847 void level::InitLastSeen()
2848 {
2849   for(int x = 0; x < XSize; ++x)
2850     for(int y = 0; y < YSize; ++y)
2851       Map[x][y]->InitLastSeen();
2852 }
2853 
EmitSunBeams()2854 void level::EmitSunBeams()
2855 {
2856   stackcontroller::Map = Map;
2857   stackcontroller::LevelXSize = XSize;
2858   stackcontroller::LevelYSize = YSize;
2859   sunbeamcontroller::ReSunEmitation = false;
2860   v2 Dir = SunLightDirection;
2861   int x, y, X = 0, Y = 0, SourceFlags;
2862   ulong IDFlags;
2863 
2864   /* Do not try to understand the logic behind the starting points of
2865      sunbeams. I determined the formulas by trial and error since all
2866      understandable loops produced strange shapes for the shadows of
2867      either small of large objects probably due to rounding errors
2868      made during line calculations. */
2869 
2870   if(!Dir.X || (Dir.Y && abs(Dir.Y) < abs(Dir.X)))
2871   {
2872     if(Dir.Y > 0)
2873     {
2874       Y = -1;
2875       SourceFlags = SP_BOTTOM;
2876       IDFlags = ID_X_COORDINATE|ID_BEGIN;
2877     }
2878     else
2879     {
2880       Y = YSize;
2881       SourceFlags = SP_TOP;
2882       IDFlags = ID_X_COORDINATE;
2883     }
2884   }
2885   else
2886   {
2887     if(Dir.X > 0)
2888     {
2889       X = -1;
2890       SourceFlags = SP_RIGHT;
2891       IDFlags = ID_BEGIN;
2892     }
2893     else
2894     {
2895       X = XSize;
2896       SourceFlags = SP_LEFT;
2897       IDFlags = 0;
2898     }
2899   }
2900 
2901   if(!Dir.X)
2902   {
2903     int Index = XSize << 3;
2904 
2905     for(x = 0; x < XSize; ++x, ++Index)
2906       EmitSunBeam(v2(x, Y), Index | IDFlags, SourceFlags);
2907   }
2908   else if(!Dir.Y)
2909   {
2910     int Index = YSize << 3;
2911 
2912     for(y = 0; y < YSize; ++y, ++Index)
2913       EmitSunBeam(v2(X, y), Index | IDFlags, SourceFlags);
2914   }
2915   else if(abs(Dir.Y) < abs(Dir.X))
2916   {
2917     int Index = Dir.X > 0 ? 0 : XSize << 3;
2918     int StartX = Dir.X > 0 ? -XSize << 3 : 0;
2919     int EndX = Dir.X > 0 ? XSize : (XSize << 3) + XSize;
2920 
2921     for(x = StartX; x < EndX; ++x, ++Index)
2922       EmitSunBeam(v2(x, Y), Index | IDFlags, SourceFlags);
2923   }
2924   else
2925   {
2926     int Index = Dir.Y > 0 ? 0 : YSize << 3;
2927     int StartY = Dir.Y > 0 ? -YSize << 3 : 0;
2928     int EndY = Dir.Y > 0 ? YSize : (YSize << 3) + YSize;
2929 
2930     for(y = StartY; y < EndY; ++y, ++Index)
2931       EmitSunBeam(v2(X, y), Index | IDFlags, SourceFlags);
2932   }
2933 }
2934 
EmitSunBeam(v2 S,ulong ID,int SourceFlags) const2935 void level::EmitSunBeam(v2 S, ulong ID, int SourceFlags) const
2936 {
2937   S <<= 1;
2938   v2 D = S + SunLightDirection;
2939   sunbeamcontroller::ID = ID;
2940 
2941   if(SourceFlags & SP_TOP_LEFT)
2942   {
2943     sunbeamcontroller::SunLightBlockHeight = 0;
2944     mapmath<sunbeamcontroller>::DoLine(S.X,     S.Y,     D.X,     D.Y, SKIP_FIRST);
2945   }
2946 
2947   if(SourceFlags & SP_TOP_RIGHT)
2948   {
2949     sunbeamcontroller::SunLightBlockHeight = 0;
2950     mapmath<sunbeamcontroller>::DoLine(S.X + 1, S.Y,     D.X + 1, D.Y, SKIP_FIRST);
2951   }
2952 
2953   if(SourceFlags & SP_BOTTOM_LEFT)
2954   {
2955     sunbeamcontroller::SunLightBlockHeight = 0;
2956     mapmath<sunbeamcontroller>::DoLine(S.X,     S.Y + 1, D.X,     D.Y + 1, SKIP_FIRST);
2957   }
2958 
2959   if(SourceFlags & SP_BOTTOM_RIGHT)
2960   {
2961     sunbeamcontroller::SunLightBlockHeight = 0;
2962     mapmath<sunbeamcontroller>::DoLine(S.X + 1, S.Y + 1, D.X + 1, D.Y + 1, SKIP_FIRST);
2963   }
2964 }
2965 
Handler(int x,int y)2966 truth sunbeamcontroller::Handler(int x, int y)
2967 {
2968   int X = x >> 1, Y = y >> 1;
2969 
2970   if(X < 0 || Y < 0 || X >= LevelXSize || Y >= LevelYSize)
2971     return (X >= -1 && X <= LevelXSize) || (Y >= -1 && Y <= LevelYSize);
2972 
2973   lsquare* Square = Map[X][Y];
2974   int SquarePartIndex = (x & 1) + ((y & 1) << 1);
2975 
2976   if(SunLightBlockHeight && !Square->IsInside()
2977      && HypotSquare(x - SunLightBlockPos.X, y - SunLightBlockPos.Y) > SunLightBlockHeight)
2978     SunLightBlockHeight = 0;
2979 
2980   if(!SunLightBlockHeight)
2981   {
2982     ulong Flag = 1 << EMITTER_SQUARE_PART_SHIFT << SquarePartIndex;
2983     Square->AddSunLightEmitter(ID | Flag);
2984   }
2985   else
2986   {
2987     ulong Flags = ((1 << EMITTER_SQUARE_PART_SHIFT)
2988                    | (1 << EMITTER_SHADOW_SHIFT))
2989                       << SquarePartIndex;
2990 
2991     Square->AddSunLightEmitter(ID | Flags);
2992   }
2993 
2994   if(ReSunEmitation)
2995   {
2996     if(!(Square->Flags & IN_SQUARE_STACK))
2997       Stack[StackIndex++] = Square;
2998 
2999     Square->Flags |= IN_SQUARE_STACK|CHECK_SUN_LIGHT_NEEDED;
3000 
3001     for(int d = 0; d < 8; ++d)
3002     {
3003       lsquare* Neighbour = Square->GetNeighbourLSquare(d);
3004 
3005       if(Neighbour && !(Neighbour->Flags & IN_SQUARE_STACK))
3006       {
3007         Neighbour->Flags |= IN_SQUARE_STACK;
3008         Stack[StackIndex++] = Neighbour;
3009       }
3010     }
3011   }
3012 
3013   if(!(Square->Flags & IS_TRANSPARENT) || (SunLightBlockHeight && Square->IsInside()))
3014   {
3015     /* This should depend on the square */
3016     SunLightBlockHeight = 81;
3017     SunLightBlockPos = v2(x, y);
3018   }
3019 
3020   return true;
3021 }
3022 
ProcessStack()3023 void sunbeamcontroller::ProcessStack()
3024 {
3025   long c;
3026 
3027   for(c = 0; c < StackIndex; ++c)
3028   {
3029     lsquare* Square = Stack[c];
3030 
3031     if(Square->Flags & CHECK_SUN_LIGHT_NEEDED)
3032     {
3033       if(Square->Flags & IS_TRANSPARENT)
3034         Square->CalculateSunLightLuminance(EMITTER_SQUARE_PART_BITS);
3035 
3036       Square->SendSunLightSignals();
3037       Square->ZeroReSunEmitatedFlags();
3038     }
3039 
3040     Square->Flags &= ~(IN_SQUARE_STACK|CHECK_SUN_LIGHT_NEEDED);
3041   }
3042 
3043   for(c = 0; c < StackIndex; ++c)
3044     Stack[c]->CheckIfIsSecondarySunLightEmitter();
3045 }
3046 
hasLight(col24 Light,int iLightBorder)3047 bool hasLight(col24 Light,int iLightBorder){ //based on lsquare::IsDark()
3048   if((Light & 0xFF0000) > (iLightBorder << 16))return true;//RED
3049   if((Light & 0x00FF00) > (iLightBorder <<  8))return true;//GREEN
3050   if((Light & 0x0000FF) > (iLightBorder      ))return true;//BLUE
3051   return false;
3052 }
RevealDistantLightsToPlayer()3053 int level::RevealDistantLightsToPlayer() //based on Draw() code
3054 {
3055   if(!ivanconfig::IsEnhancedLights())
3056     return 0;
3057 
3058   if(!PLAYER->GetSquareUnder()) //NULL may happen on player's death, was polymorphed when the crash happened
3059    return 0;
3060 
3061   if(!PLAYER->GetSquareUnder()) //NULL may happen on player's death, was polymorphed when the crash happened
3062     return 0;
3063 
3064   cint XMin = Max(game::GetCamera().X, 0);
3065   cint YMin = Max(game::GetCamera().Y, 0);
3066   cint XMax = Min(XSize, game::GetCamera().X + game::GetScreenXSize());
3067   cint YMax = Min(YSize, game::GetCamera().Y + game::GetScreenYSize());
3068   culong LOSTick = game::GetLOSTick();
3069 
3070   long lMaxDist = v2(PLAYER->GetAttribute(PERCEPTION),0).GetLengthSquare();
3071   int tot=0;
3072   for(int x = XMin; x < XMax; ++x){
3073     lsquare** SquarePtr = &Map[x][YMin];
3074     for(int y = YMin; y < YMax; ++y, ++SquarePtr){
3075       lsquare* Square = *SquarePtr;
3076 
3077       if(Square->CanBeSeenByPlayer())
3078         continue; //already seen
3079 
3080       if(Square->Luminance==0)
3081         continue;
3082 
3083       int iMultDist=1;
3084       bool bTryReveal=false;
3085       if(!bTryReveal && hasLight(Square->Emitation,LIGHT_BORDER)){ //EMMITERS
3086         bTryReveal=true;
3087         iMultDist=4;
3088       }
3089 
3090       if(!bTryReveal){
3091         // TODO the farer, less range around the emmiter should be seen
3092 
3093 //        if(Square->GetOLTerrain() && Square->GetOLTerrain()->IsWall()) //do not show far walls to look better
3094 //          continue;
3095 
3096         iMultDist=2;
3097         int iDist = (Square->GetPos() - PLAYER->GetPos()).GetLengthSquare();
3098         if(iDist <= lMaxDist*iMultDist){ //ground view limit, NON EMMITERS but still lighted up
3099           // TODO create a better generic formula to calc this?
3100           static float fLBorderLanternLOSM16=0.475; //based on tests with lantern
3101           static int LOSMdef=16; //LOSModifier=16 (default for most dungeon levels)
3102           static float fLBmax = 0.80; //the bigger, less squares will be visible around emitters
3103           static float fLBStep = (fLBmax-fLBorderLanternLOSM16)/LOSMdef;
3104           float fLBorder=fLBorderLanternLOSM16;
3105           if(GetLOSModifier()<LOSMdef){ //the idea (for now) is just to darken the environment if needed, as it is already very well litten up everywhere else
3106             int LOSMdelta=LOSMdef-GetLOSModifier();
3107             fLBorder = fLBorderLanternLOSM16 + fLBStep*LOSMdelta;
3108           }
3109           if(!bTryReveal && hasLight(Square->Luminance,0xFF*fLBorder))
3110             bTryReveal=true;
3111         }
3112       }
3113 
3114       if(bTryReveal){
3115         if(Square->CanBeSeenFrom(PLAYER->GetPos(),lMaxDist*iMultDist)){
3116           Square->Reveal(LOSTick,false);
3117           ++tot;
3118         }
3119       }
3120     }
3121   }
3122 
3123   return tot;
3124 }
3125 
DetectMaterial(cmaterial * Material)3126 int level::DetectMaterial(cmaterial* Material)
3127 {
3128   ulong Tick = game::IncreaseLOSTick();
3129   int Squares = 0;
3130 
3131   for(int x = 0; x < XSize; ++x)
3132     for(int y = 0; y < YSize; ++y)
3133     {
3134       lsquare* Square = Map[x][y];
3135 
3136       if(Square->DetectMaterial(Material))
3137       {
3138         Square->Reveal(Tick, true);
3139         Square->bMaterialDetected=true;
3140         ++Squares;
3141       }
3142     }
3143 
3144   return Squares;
3145 }
3146 
BlurMemory()3147 void level::BlurMemory()
3148 {
3149   int x, y, SquareStackSize = 0;
3150 
3151   for(x = 0; x < XSize; ++x)
3152     for(y = 0; y < YSize; ++y)
3153     {
3154       lsquare* Square = Map[x][y];
3155 
3156       if(Square->HasNoBorderPartners())
3157         SquareStack[SquareStackSize++] = Square;
3158     }
3159 
3160   for(x = 0; x < XSize; ++x)
3161     for(y = 0; y < YSize; ++y)
3162     {
3163       lsquare* Square = Map[x][y];
3164       Square->Flags |= STRONG_NEW_DRAW_REQUEST
3165                        | MEMORIZED_UPDATE_REQUEST
3166                        | DESCRIPTION_CHANGE;
3167 
3168       if(Square->HasNoBorderPartners()
3169          && RAND() & 1
3170          && SquareStackSize)
3171         Square->SwapMemorized(SquareStack[RAND() % SquareStackSize]);
3172       else if(RAND() & 1)
3173         Square->DestroyMemorized();
3174     }
3175 }
3176 
CalculateLuminances()3177 void level::CalculateLuminances()
3178 {
3179   for(int x = 0; x < XSize; ++x)
3180     for(int y = 0; y < YSize; ++y)
3181     {
3182       lsquare* Square = Map[x][y];
3183       Square->CalculateLuminance();
3184       Square->Flags |= MEMORIZED_UPDATE_REQUEST
3185                        | DESCRIPTION_CHANGE;
3186       Square->bMaterialDetected=false; //detection is about luminance anyway
3187     }
3188 }
3189 
3190 struct areacontroller : public stackcontroller
3191 {
Handlerareacontroller3192   static truth Handler(int x, int y)
3193   {
3194     if(x >= 0 && y >= 0 && x < LevelXSize && y < LevelYSize
3195        && HypotSquare(x - Center.X, y - Center.Y) <= RadiusSquare)
3196     {
3197       lsquare* Square = Map[x][y];
3198 
3199       if(!(Square->Flags & IN_SQUARE_STACK))
3200       {
3201         Stack[StackIndex++] = Square;
3202         Square->Flags |= IN_SQUARE_STACK;
3203         return Square->IsFlyable();
3204       }
3205     }
3206 
3207     return false;
3208   }
GetStartXareacontroller3209   static int GetStartX(int) { return Center.X; }
GetStartYareacontroller3210   static int GetStartY(int) { return Center.Y; }
3211   static long RadiusSquare;
3212 };
3213 
3214 long areacontroller::RadiusSquare;
3215 
AddRadiusToSquareStack(v2 Center,long RadiusSquare) const3216 int level::AddRadiusToSquareStack(v2 Center, long RadiusSquare) const
3217 {
3218   stackcontroller::Map = Map;
3219   stackcontroller::Stack = SquareStack;
3220   SquareStack[0] = GetLSquare(Center);
3221   stackcontroller::StackIndex = 1;
3222   stackcontroller::LevelXSize = XSize;
3223   stackcontroller::LevelYSize = YSize;
3224   stackcontroller::Center = Center;
3225   areacontroller::RadiusSquare = RadiusSquare;
3226   mapmath<areacontroller>::DoArea();
3227   return stackcontroller::StackIndex;
3228 }
3229 
3230 /* Any fountain is good that is not dry and is NOT Except */
3231 
GetRandomFountainWithWater(olterrain * Except) const3232 olterrain* level::GetRandomFountainWithWater(olterrain* Except) const
3233 {
3234   std::vector<olterrain*> Found;
3235   olterrain* OLTerrain;
3236   for(int x = 0; x < XSize; ++x)
3237     for(int y = 0; y < YSize; ++y)
3238     {
3239       OLTerrain = GetLSquare(x, y)->GetOLTerrain();
3240       if(OLTerrain && OLTerrain != Except && OLTerrain->IsFountainWithWater())
3241         Found.push_back(OLTerrain);
3242     }
3243 
3244   if(Found.empty())
3245     return 0;
3246 
3247   return Found[RAND_N(Found.size())];
3248 }
3249 
Amnesia(int Percentile)3250 void level::Amnesia(int Percentile)
3251 {
3252   for(int x = 0; x < XSize; ++x)
3253     for(int y = 0; y < YSize; ++y)
3254     {
3255       lsquare* Square = Map[x][y];
3256 
3257       if(Square->HasNoBorderPartners() && RAND_N(100) < Percentile)
3258       {
3259         Square->Flags |= STRONG_NEW_DRAW_REQUEST
3260                          | MEMORIZED_UPDATE_REQUEST
3261                          | DESCRIPTION_CHANGE;
3262 
3263         Square->DestroyMemorized();
3264       }
3265     }
3266 }
3267 
3268 /* Returns how many of the monsters were seen */
3269 
SpawnMonsters(characterspawner Spawner,team * Team,v2 Pos,int Config,int Amount,truth IgnoreWalkability)3270 spawnresult level::SpawnMonsters(characterspawner Spawner, team* Team,
3271                                  v2 Pos, int Config, int Amount,
3272                                  truth IgnoreWalkability)
3273 {
3274   spawnresult SR = { 0, 0 };
3275 
3276   for(int c = 0; c < Amount; ++c)
3277   {
3278     character* Char = Spawner(Config, 0);
3279 
3280     if(!c)
3281       SR.Pioneer = Char;
3282 
3283     Char->SetTeam(Team);
3284 
3285     if(IgnoreWalkability)
3286       Char->ForcePutNear(Pos);
3287     else
3288       Char->PutNear(Pos);
3289 
3290     if(Char->CanBeSeenByPlayer())
3291       ++SR.Seen;
3292   }
3293 
3294   return SR;
3295 }
3296 
AddSpecialCursors()3297 void level::AddSpecialCursors()
3298 {
3299   for(int x = 0; x < XSize; ++x)
3300     for(int y = 0; y < YSize; ++y)
3301       Map[x][y]->AddSpecialCursors();
3302 }
3303 
GasExplosion(gas * GasMaterial,lsquare * Square,character * Terrorist)3304 void level::GasExplosion(gas* GasMaterial, lsquare* Square, character* Terrorist)
3305 {
3306   for(int d = 0; d < 9; ++d)
3307   {
3308     lsquare* Neighbour = Square->GetNeighbourLSquare(d);
3309 
3310     if(Neighbour && Neighbour->IsFlyable())
3311       Neighbour->AddSmoke(static_cast<gas*>(GasMaterial->SpawnMore(1000)));
3312 
3313     if(Neighbour)
3314     {
3315       character* Victim = Neighbour->GetCharacter();
3316 
3317       if(Victim && Terrorist)
3318         Terrorist->Hostility(Victim);
3319     }
3320   }
3321 }
3322