1 /* Copyright (C) 2013-2014 Michal Brzozowski (rusolis@poczta.fm)
2
3 This file is part of KeeperRL.
4
5 KeeperRL is free software; you can redistribute it and/or modify it under the terms of the
6 GNU General Public License as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 KeeperRL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10 even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along with this program.
14 If not, see http://www.gnu.org/licenses/ . */
15
16 #include "stdafx.h"
17
18 #include "level.h"
19 #include "model.h"
20 #include "item.h"
21 #include "creature.h"
22 #include "square.h"
23 #include "collective_builder.h"
24 #include "progress_meter.h"
25 #include "level_maker.h"
26 #include "movement_type.h"
27 #include "attack.h"
28 #include "player_message.h"
29 #include "vision.h"
30 #include "bucket_map.h"
31 #include "creature_name.h"
32 #include "sunlight_info.h"
33 #include "game.h"
34 #include "creature_attributes.h"
35 #include "square_array.h"
36 #include "view_object.h"
37 #include "field_of_view.h"
38 #include "furniture.h"
39 #include "furniture_array.h"
40
41 template <class Archive>
serialize(Archive & ar,const unsigned int version)42 void Level::serialize(Archive& ar, const unsigned int version) {
43 ar & SUBCLASS(OwnedObject<Level>);
44 ar(squares, oldSquares, landingSquares, tickingSquares, creatures, model, fieldOfView);
45 ar(name, sunlight, bucketMap, sectors, lightAmount, unavailable);
46 ar(levelId, noDiagonalPassing, lightCapAmount, creatureIds, memoryUpdates);
47 ar(furniture, tickingFurniture, covered);
48 }
49
50 SERIALIZABLE(Level);
51
52 SERIALIZATION_CONSTRUCTOR_IMPL(Level);
53
~Level()54 Level::~Level() {}
55
Level(Private,SquareArray s,FurnitureArray f,WModel m,const string & n,Table<double> sun,LevelId id,Table<bool> cover)56 Level::Level(Private, SquareArray s, FurnitureArray f, WModel m, const string& n,
57 Table<double> sun, LevelId id, Table<bool> cover)
58 : squares(std::move(s)), oldSquares(squares->getBounds()), furniture(std::move(f)),
59 memoryUpdates(squares->getBounds(), true), model(m),
60 name(n), sunlight(sun), covered(cover), bucketMap(squares->getBounds().width(), squares->getBounds().height(),
61 FieldOfView::sightRange), lightAmount(squares->getBounds(), 0), lightCapAmount(squares->getBounds(), 1),
62 levelId(id) {
63 }
64
create(SquareArray s,FurnitureArray f,WModel m,const string & n,Table<double> sun,LevelId id,Table<bool> cover)65 PLevel Level::create(SquareArray s, FurnitureArray f, WModel m, const string& n,
66 Table<double> sun, LevelId id, Table<bool> cover) {
67 auto ret = makeOwner<Level>(Private{}, std::move(s), std::move(f), m, n, sun, id, cover);
68 for (Vec2 pos : ret->squares->getBounds()) {
69 auto square = ret->squares->getReadonly(pos);
70 square->onAddedToLevel(Position(pos, ret.get()));
71 if (optional<StairKey> link = square->getLandingLink())
72 ret->landingSquares[*link].push_back(Position(pos, ret.get()));
73 for (auto layer : ENUM_ALL(FurnitureLayer))
74 if (auto f = ret->furniture->getBuilt(layer).getReadonly(pos))
75 if (f->isTicking())
76 ret->addTickingFurniture(pos);
77 }
78 for (VisionId vision : ENUM_ALL(VisionId))
79 (*ret->fieldOfView)[vision] = FieldOfView(ret.get(), vision);
80 for (auto pos : ret->getAllPositions())
81 ret->addLightSource(pos.getCoord(), pos.getLightEmission(), 1);
82 return ret;
83 }
84
getUniqueId() const85 LevelId Level::getUniqueId() const {
86 return levelId;
87 }
88
getMaxBounds()89 Rectangle Level::getMaxBounds() {
90 return Rectangle(360, 360);
91 }
92
getSplashBounds()93 Rectangle Level::getSplashBounds() {
94 return Rectangle(80, 40);
95 }
96
getSplashVisibleBounds()97 Rectangle Level::getSplashVisibleBounds() {
98 Vec2 sz(40, 20);
99 return Rectangle(getSplashBounds().middle() - sz / 2, getSplashBounds().middle() + sz / 2);
100 }
101
102 const static double darknessRadius = 3.5;
103
putCreature(Vec2 position,WCreature c)104 void Level::putCreature(Vec2 position, WCreature c) {
105 CHECK(inBounds(position));
106 creatures.push_back(c);
107 creatureIds.insert(c);
108 CHECK(getSafeSquare(position)->getCreature() == nullptr)
109 << "Square occupied by " << getSafeSquare(position)->getCreature()->getName().bare();
110 placeCreature(c, position);
111 }
112
addLightSource(Vec2 pos,double radius)113 void Level::addLightSource(Vec2 pos, double radius) {
114 addLightSource(pos, radius, 1);
115 }
116
removeLightSource(Vec2 pos,double radius)117 void Level::removeLightSource(Vec2 pos, double radius) {
118 addLightSource(pos, radius, -1);
119 }
120
addLightSource(Vec2 pos,double radius,int numLight)121 void Level::addLightSource(Vec2 pos, double radius, int numLight) {
122 if (radius > 0) {
123 for (Vec2 v : getVisibleTilesNoDarkness(pos, VisionId::NORMAL)) {
124 double dist = (v - pos).lengthD();
125 if (dist <= radius) {
126 lightAmount[v] += min(1.0, 1 - (dist) / radius) * numLight;
127 setNeedsRenderUpdate(v, true);
128 }
129 }
130 }
131 }
132
addDarknessSource(Vec2 pos,double radius,int numDarkness)133 void Level::addDarknessSource(Vec2 pos, double radius, int numDarkness) {
134 if (radius > 0) {
135 for (Vec2 v : getVisibleTilesNoDarkness(pos, VisionId::NORMAL)) {
136 double dist = (v - pos).lengthD();
137 if (dist <= radius) {
138 lightCapAmount[v] -= min(1.0, 1 - (dist) / radius) * numDarkness;
139 setNeedsRenderUpdate(v, true);
140 }
141 // updateConnectivity(v);
142 }
143 }
144 }
145
updateVisibility(Vec2 changedSquare)146 void Level::updateVisibility(Vec2 changedSquare) {
147 for (Vec2 pos : getVisibleTilesNoDarkness(changedSquare, VisionId::NORMAL)) {
148 addLightSource(pos, Position(pos, this).getLightEmission(), -1);
149 auto square = squares->getReadonly(pos);
150 CHECK(square) << pos << " " << getBounds();
151 if (WCreature c = square->getCreature())
152 if (c->isDarknessSource())
153 addDarknessSource(pos, darknessRadius, -1);
154 }
155 for (VisionId vision : ENUM_ALL(VisionId))
156 getFieldOfView(vision).squareChanged(changedSquare);
157 for (Vec2 pos : getVisibleTilesNoDarkness(changedSquare, VisionId::NORMAL)) {
158 addLightSource(pos, Position(pos, this).getLightEmission(), 1);
159 auto square = squares->getReadonly(pos);
160 CHECK(square) << pos << " " << getBounds();
161 if (WCreature c = square->getCreature())
162 if (c->isDarknessSource())
163 addDarknessSource(pos, darknessRadius, 1);
164 }
165 for (Vec2 pos : getVisibleTilesNoDarkness(changedSquare, VisionId::NORMAL))
166 getModel()->addEvent(EventInfo::VisibilityChanged{Position(pos, this)});
167 }
168
getPlayers() const169 vector<WCreature> Level::getPlayers() const {
170 if (auto game = model->getGame())
171 return game->getPlayerCreatures().filter([this](const WCreature& c) { return c->getLevel() == this; });
172 return {};
173 }
174
getModel() const175 const WModel Level::getModel() const {
176 return model;
177 }
178
getModel()179 WModel Level::getModel() {
180 return model;
181 }
182
getGame() const183 WGame Level::getGame() const {
184 return model->getGame();
185 }
186
isInSunlight(Vec2 pos) const187 bool Level::isInSunlight(Vec2 pos) const {
188 return !covered[pos] && lightCapAmount[pos] == 1 &&
189 getGame()->getSunlightInfo().getState() == SunlightState::DAY;
190 }
191
getLight(Vec2 pos) const192 double Level::getLight(Vec2 pos) const {
193 return max(0.0, min(covered[pos] ? 1 : lightCapAmount[pos], lightAmount[pos] +
194 sunlight[pos] * getGame()->getSunlightInfo().getLightAmount()));
195 }
196
getLandingSquares(StairKey key) const197 vector<Position> Level::getLandingSquares(StairKey key) const {
198 if (landingSquares.count(key))
199 return landingSquares.at(key);
200 else
201 return vector<Position>();
202 }
203
getAllStairKeys() const204 vector<StairKey> Level::getAllStairKeys() const {
205 return getKeys(landingSquares);
206 }
207
hasStairKey(StairKey key) const208 bool Level::hasStairKey(StairKey key) const {
209 return landingSquares.count(key);
210 }
211
getStairsTo(WConstLevel level)212 optional<Position> Level::getStairsTo(WConstLevel level) {
213 return model->getStairs(this, level);
214 }
215
landCreature(StairKey key,WCreature creature)216 bool Level::landCreature(StairKey key, WCreature creature) {
217 vector<Position> landing = landingSquares.at(key);
218 return landCreature(landing, creature);
219 }
220
landCreature(StairKey key,PCreature creature)221 bool Level::landCreature(StairKey key, PCreature creature) {
222 if (landCreature(key, creature.get())) {
223 model->addCreature(std::move(creature));
224 return true;
225 } else
226 return false;
227 }
228
landCreature(StairKey key,PCreature creature,Vec2 travelDir)229 bool Level::landCreature(StairKey key, PCreature creature, Vec2 travelDir) {
230 if (landCreature(key, creature.get(), travelDir)) {
231 model->addCreature(std::move(creature));
232 return true;
233 } else
234 return false;
235 }
236
projectOnBorders(Rectangle area,Vec2 d)237 static Vec2 projectOnBorders(Rectangle area, Vec2 d) {
238 Vec2 center = Vec2((area.left() + area.right()) / 2, (area.top() + area.bottom()) / 2);
239 if (d.x == 0) {
240 return Vec2(center.x, d.y > 0 ? area.bottom() - 1 : area.top());
241 }
242 int cy = d.y * area.width() / 2 / abs(d.x);
243 if (center.y + cy >= area.top() && center.y + cy < area.bottom())
244 return Vec2(d.x > 0 ? area.right() - 1 : area.left(), center.y + cy);
245 int cx = d.x * area.height() / 2 / abs(d.y);
246 return Vec2(center.x + cx, d.y > 0 ? area.bottom() - 1: area.top());
247 }
248
getLandingSquare(StairKey key,Vec2 travelDir) const249 Position Level::getLandingSquare(StairKey key, Vec2 travelDir) const {
250 vector<Position> landing = landingSquares.at(key);
251 Vec2 entryPos = projectOnBorders(getBounds(), travelDir);
252 Position target = landing[0];
253 for (Position p : landing)
254 if (p.getCoord().distD(entryPos) < target.getCoord().distD(entryPos))
255 target = p;
256 return target;
257 }
258
landCreature(StairKey key,WCreature creature,Vec2 travelDir)259 bool Level::landCreature(StairKey key, WCreature creature, Vec2 travelDir) {
260 Position bestLanding = getLandingSquare(key, travelDir);
261 return landCreature({bestLanding}, creature) ||
262 landCreature(bestLanding.getRectangle(Rectangle::centered(Vec2(0, 0), 10)), creature) ||
263 landCreature(landingSquares.at(key), creature) ||
264 landCreature(getAllPositions(), creature);
265 }
266
landCreature(vector<Position> landing,PCreature creature)267 bool Level::landCreature(vector<Position> landing, PCreature creature) {
268 if (landCreature(landing, creature.get())) {
269 model->addCreature(std::move(creature));
270 return true;
271 } else
272 return false;
273 }
274
landCreature(vector<Position> landing,WCreature creature)275 bool Level::landCreature(vector<Position> landing, WCreature creature) {
276 CHECK(creature);
277 queue<Position> q;
278 set<Position> marked;
279 for (Position pos : Random.permutation(landing)) {
280 q.push(pos);
281 marked.insert(pos);
282 }
283 while (!q.empty()) {
284 Position v = q.front();
285 q.pop();
286 if (v.canEnter(creature)) {
287 v.putCreature(creature);
288 return true;
289 } else
290 for (Position next : v.neighbors8(Random))
291 if (!marked.count(next) && next.canEnterEmpty(creature)) {
292 q.push(next);
293 marked.insert(next);
294 }
295 }
296 return false;
297 }
298
throwItem(PItem item,const Attack & attack,int maxDist,Vec2 position,Vec2 direction,VisionId vision)299 void Level::throwItem(PItem item, const Attack& attack, int maxDist, Vec2 position, Vec2 direction, VisionId vision) {
300 vector<PItem> v;
301 v.push_back(std::move(item));
302 throwItem(std::move(v), attack, maxDist, position, direction, vision);
303 }
304
throwItem(vector<PItem> item,const Attack & attack,int maxDist,Vec2 position,Vec2 direction,VisionId vision)305 void Level::throwItem(vector<PItem> item, const Attack& attack, int maxDist, Vec2 position, Vec2 direction,
306 VisionId vision) {
307 CHECK(!item.empty());
308 CHECK(direction.length8() == 1);
309 int cnt = 1;
310 vector<Vec2> trajectory;
311 for (Vec2 v = position + direction; inBounds(v); v += direction) {
312 trajectory.push_back(v);
313 Position pos(v, this);
314 if (pos.stopsProjectiles(vision)) {
315 item[0]->onHitSquareMessage(Position(v, this), item.size());
316 trajectory.pop_back();
317 getGame()->addEvent(
318 EventInfo::Projectile{item[0]->getViewObject().id(), Position(position, this), pos.minus(direction)});
319 if (!item[0]->isDiscarded())
320 pos.minus(direction).dropItems(std::move(item));
321 return;
322 }
323 if (++cnt > maxDist || getSafeSquare(v)->getCreature()) {
324 getGame()->addEvent(
325 EventInfo::Projectile{item[0]->getViewObject().id(), Position(position, this), pos});
326 modSafeSquare(v)->onItemLands(Position(v, this), std::move(item), attack, maxDist - cnt - 1, direction,
327 vision);
328 return;
329 }
330 }
331 }
332
killCreature(WCreature creature)333 void Level::killCreature(WCreature creature) {
334 eraseCreature(creature, creature->getPosition().getCoord());
335 getModel()->killCreature(creature);
336 }
337
removeCreature(WCreature creature)338 void Level::removeCreature(WCreature creature) {
339 eraseCreature(creature, creature->getPosition().getCoord());
340 }
341
changeLevel(StairKey key,WCreature c)342 void Level::changeLevel(StairKey key, WCreature c) {
343 Vec2 oldPos = c->getPosition().getCoord();
344 WLevel otherLevel = model->getLinkedLevel(this, key);
345 if (otherLevel->landCreature(key, c))
346 eraseCreature(c, oldPos);
347 else {
348 Position otherPos = Random.choose(otherLevel->landingSquares.at(key));
349 if (WCreature other = otherPos.getCreature()) {
350 if (!other->isPlayer() && c->getPosition().canEnterEmpty(other) && otherPos.canEnterEmpty(c)) {
351 otherLevel->eraseCreature(other, otherPos.getCoord());
352 eraseCreature(c, oldPos);
353 putCreature(oldPos, other);
354 otherLevel->putCreature(otherPos.getCoord(), c);
355 c->secondPerson("You switch levels with " + other->getName().a());
356 }
357 }
358 }
359 }
360
changeLevel(Position destination,WCreature c)361 void Level::changeLevel(Position destination, WCreature c) {
362 Vec2 oldPos = c->getPosition().getCoord();
363 if (destination.isValid() && destination.getLevel()->landCreature({destination}, c))
364 eraseCreature(c, oldPos);
365 }
366
eraseCreature(WCreature c,Vec2 coord)367 void Level::eraseCreature(WCreature c, Vec2 coord) {
368 creatures.removeElement(c);
369 unplaceCreature(c, coord);
370 creatureIds.erase(c);
371 }
372
getAllCreatures() const373 const vector<WCreature>& Level::getAllCreatures() const {
374 return creatures;
375 }
376
getAllCreatures()377 vector<WCreature>& Level::getAllCreatures() {
378 return creatures;
379 }
380
getAllCreatures(Rectangle bounds) const381 vector<WCreature> Level::getAllCreatures(Rectangle bounds) const {
382 return bucketMap->getElements(bounds);
383 }
384
containsCreature(UniqueEntity<Creature>::Id id) const385 bool Level::containsCreature(UniqueEntity<Creature>::Id id) const {
386 return creatureIds.contains(id);
387 }
388
isWithinVision(Vec2 from,Vec2 to,const Vision & v) const389 bool Level::isWithinVision(Vec2 from, Vec2 to, const Vision& v) const {
390 return v.canSeeAt(getLight(to), from.distD(to));
391 }
392
getFieldOfView(VisionId vision) const393 FieldOfView& Level::getFieldOfView(VisionId vision) const {
394 return (*fieldOfView)[vision];
395 }
396
canSee(Vec2 from,Vec2 to,const Vision & vision) const397 bool Level::canSee(Vec2 from, Vec2 to, const Vision& vision) const {
398 return isWithinVision(from, to, vision) && getFieldOfView(vision.getId()).canSee(from, to);
399 }
400
canSee(WConstCreature c,Vec2 pos) const401 bool Level::canSee(WConstCreature c, Vec2 pos) const {
402 return canSee(c->getPosition().getCoord(), pos, c->getVision());
403 }
404
moveCreature(WCreature creature,Vec2 direction)405 void Level::moveCreature(WCreature creature, Vec2 direction) {
406 // CHECK(canMoveCreature(creature, direction));
407 Vec2 position = creature->getPosition().getCoord();
408 unplaceCreature(creature, position);
409 placeCreature(creature, position + direction);
410 }
411
unplaceCreature(WCreature creature,Vec2 pos)412 void Level::unplaceCreature(WCreature creature, Vec2 pos) {
413 bucketMap->removeElement(pos, creature);
414 modSafeSquare(pos)->removeCreature(Position(pos, this));
415 if (creature->isDarknessSource())
416 addDarknessSource(pos, darknessRadius, -1);
417 }
418
placeCreature(WCreature creature,Vec2 pos)419 void Level::placeCreature(WCreature creature, Vec2 pos) {
420 Position position(pos, this);
421 creature->setPosition(position);
422 bucketMap->addElement(pos, creature);
423 modSafeSquare(pos)->putCreature(creature);
424 if (creature->isDarknessSource())
425 addDarknessSource(pos, darknessRadius, 1);
426 position.onEnter(creature);
427 }
428
swapCreatures(WCreature c1,WCreature c2)429 void Level::swapCreatures(WCreature c1, WCreature c2) {
430 Vec2 pos1 = c1->getPosition().getCoord();
431 Vec2 pos2 = c2->getPosition().getCoord();
432 unplaceCreature(c1, pos1);
433 unplaceCreature(c2, pos2);
434 placeCreature(c1, pos2);
435 placeCreature(c2, pos1);
436 }
437
getVisibleTilesNoDarkness(Vec2 pos,VisionId vision) const438 const vector<Vec2>& Level::getVisibleTilesNoDarkness(Vec2 pos, VisionId vision) const {
439 return getFieldOfView(vision).getVisibleTiles(pos);
440 }
441
getVisibleTiles(Vec2 pos,const Vision & vision) const442 vector<Vec2> Level::getVisibleTiles(Vec2 pos, const Vision& vision) const {
443 return getFieldOfView(vision.getId()).getVisibleTiles(pos).filter(
444 [&](Vec2 v) { return isWithinVision(pos, v, vision); });
445 }
446
getSafeSquare(Vec2 pos) const447 WConstSquare Level::getSafeSquare(Vec2 pos) const {
448 CHECK(inBounds(pos));
449 return squares->getReadonly(pos);
450 }
451
modSafeSquare(Vec2 pos)452 WSquare Level::modSafeSquare(Vec2 pos) {
453 CHECK(inBounds(pos));
454 return squares->getWritable(pos);
455 }
456
getAllPositions() const457 vector<Position> Level::getAllPositions() const {
458 vector<Position> ret;
459 for (Vec2 v : getBounds())
460 ret.emplace_back(v, getThis().removeConst());
461 return ret;
462 }
463
addTickingSquare(Vec2 pos)464 void Level::addTickingSquare(Vec2 pos) {
465 tickingSquares.insert(pos);
466 }
467
addTickingFurniture(Vec2 pos)468 void Level::addTickingFurniture(Vec2 pos) {
469 tickingFurniture.insert(pos);
470 }
471
tick()472 void Level::tick() {
473 for (Vec2 pos : tickingSquares)
474 squares->getWritable(pos)->tick(Position(pos, this));
475 for (Vec2 pos : tickingFurniture)
476 for (auto layer : ENUM_ALL(FurnitureLayer))
477 if (auto f = furniture->getBuilt(layer).getWritable(pos))
478 f->tick(Position(pos, this));
479 }
480
inBounds(Vec2 pos) const481 bool Level::inBounds(Vec2 pos) const {
482 return pos.inRectangle(getBounds());
483 }
484
getBounds() const485 const Rectangle& Level::getBounds() const {
486 return squares->getBounds();
487 }
488
getName() const489 const string& Level::getName() const {
490 return name;
491 }
492
areConnected(Vec2 p1,Vec2 p2,const MovementType & movement) const493 bool Level::areConnected(Vec2 p1, Vec2 p2, const MovementType& movement) const {
494 return inBounds(p1) && inBounds(p2) && getSectors(movement).same(p1, p2);
495 }
496
getSectors(const MovementType & movement) const497 Sectors& Level::getSectors(const MovementType& movement) const {
498 if (!sectors.count(movement)) {
499 sectors[movement] = Sectors(getBounds());
500 Sectors& newSectors = sectors.at(movement);
501 for (Position pos : getAllPositions())
502 if (pos.canNavigate(movement))
503 newSectors.add(pos.getCoord());
504 }
505 return sectors.at(movement);
506 }
507
isChokePoint(Vec2 pos,const MovementType & movement) const508 bool Level::isChokePoint(Vec2 pos, const MovementType& movement) const {
509 return getSectors(movement).isChokePoint(pos);
510 }
511
updateSunlightMovement()512 void Level::updateSunlightMovement() {
513 sectors.clear();
514 }
515
getNumGeneratedSquares() const516 int Level::getNumGeneratedSquares() const {
517 return squares->getNumGenerated();
518 }
519
getNumTotalSquares() const520 int Level::getNumTotalSquares() const {
521 return squares->getNumTotal();
522 }
523
setNeedsMemoryUpdate(Vec2 pos,bool s)524 void Level::setNeedsMemoryUpdate(Vec2 pos, bool s) {
525 if (pos.inRectangle(getBounds()))
526 memoryUpdates[pos] = s;
527 }
528
needsRenderUpdate(Vec2 pos) const529 bool Level::needsRenderUpdate(Vec2 pos) const {
530 return renderUpdates[pos];
531 }
532
setNeedsRenderUpdate(Vec2 pos,bool s)533 void Level::setNeedsRenderUpdate(Vec2 pos, bool s) {
534 renderUpdates[pos] = s;
535 setNeedsMemoryUpdate(pos, s);
536 }
537
needsMemoryUpdate(Vec2 pos) const538 bool Level::needsMemoryUpdate(Vec2 pos) const {
539 return memoryUpdates[pos];
540 }
541
isUnavailable(Vec2 pos) const542 bool Level::isUnavailable(Vec2 pos) const {
543 return unavailable[pos];
544 }
545
setFurniture(Vec2 pos,PFurniture f)546 void Level::setFurniture(Vec2 pos, PFurniture f) {
547 auto layer = f->getLayer();
548 furniture->getConstruction(pos, layer).reset();
549 if (f->isTicking())
550 addTickingFurniture(pos);
551 furniture->getBuilt(layer).putElem(pos, std::move(f));
552 }
553