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