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