A 2D tile-based sandbox game.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

WorldPlane.cc 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. #include "WorldPlane.h"
  2. #include <math.h>
  3. #include <iostream>
  4. #include <algorithm>
  5. #include "log.h"
  6. #include "World.h"
  7. #include "Game.h"
  8. #include "Clock.h"
  9. #include "Win.h"
  10. namespace Swan {
  11. static ChunkPos chunkPos(TilePos pos) {
  12. int chx = pos.x / CHUNK_WIDTH;
  13. if (pos.x < 0 && pos.x % CHUNK_WIDTH != 0) chx -= 1;
  14. int chy = pos.y / CHUNK_HEIGHT;
  15. if (pos.y < 0 && pos.y % CHUNK_HEIGHT != 0) chy -= 1;
  16. return ChunkPos(chx, chy);
  17. }
  18. static Chunk::RelPos relPos(TilePos pos) {
  19. int rx = pos.x % CHUNK_WIDTH;
  20. if (rx < 0) rx += CHUNK_WIDTH;
  21. int ry = pos.y % CHUNK_HEIGHT;
  22. if (ry < 0) ry += CHUNK_HEIGHT;
  23. return Chunk::RelPos(rx, ry);
  24. }
  25. Context WorldPlane::getContext() {
  26. return {
  27. .game = *world_->game_,
  28. .world = *world_,
  29. .plane = *this,
  30. .resources = world_->resources_
  31. };
  32. }
  33. WorldPlane::WorldPlane(
  34. ID id, World *world, std::unique_ptr<WorldGen> gen,
  35. std::vector<std::unique_ptr<EntityCollection>> &&colls):
  36. id_(id), world_(world), gen_(std::move(gen)), ent_colls_(std::move(colls)) {
  37. for (auto &coll: ent_colls_) {
  38. ent_colls_by_type_[coll->type()] = coll.get();
  39. ent_colls_by_name_[coll->name()] = coll.get();
  40. }
  41. }
  42. EntityRef WorldPlane::spawnEntity(const std::string &name, const Entity::PackObject &obj) {
  43. return ent_colls_by_name_.at(name)->spawn(getContext(), obj);
  44. }
  45. void WorldPlane::despawnEntity(Entity &ent) {
  46. // TODO: this
  47. info << "Despawned entity.";
  48. }
  49. bool WorldPlane::hasChunk(ChunkPos pos) {
  50. return chunks_.find(pos) != chunks_.end();
  51. }
  52. // This function will be a bit weird because it's a really fucking hot function.
  53. Chunk &WorldPlane::getChunk(ChunkPos pos) {
  54. // First, look through all chunks which have been in use this tick
  55. for (auto [chpos, chunk]: tick_chunks_) {
  56. if (chpos == pos)
  57. return *chunk;
  58. }
  59. Chunk &chunk = slowGetChunk(pos);
  60. tick_chunks_.push_back({ pos, &chunk });
  61. return chunk;
  62. }
  63. Chunk &WorldPlane::slowGetChunk(ChunkPos pos) {
  64. auto iter = chunks_.find(pos);
  65. // Create chunk if that turns out to be necessary
  66. if (iter == chunks_.end()) {
  67. iter = chunks_.emplace(pos, Chunk(pos)).first;
  68. Chunk &chunk = iter->second;
  69. gen_->genChunk(*this, chunk);
  70. active_chunks_.push_back(&chunk);
  71. chunk_init_list_.push_back(&chunk);
  72. // Otherwise, it might not be active, so let's activate it
  73. } else if (!iter->second.isActive()) {
  74. iter->second.keepActive();
  75. active_chunks_.push_back(&iter->second);
  76. chunk_init_list_.push_back(&iter->second);
  77. }
  78. return iter->second;
  79. }
  80. void WorldPlane::setTileID(TilePos pos, Tile::ID id) {
  81. Chunk &chunk = getChunk(chunkPos(pos));
  82. Chunk::RelPos rp = relPos(pos);
  83. Tile::ID old = chunk.getTileID(rp);
  84. if (id != old) {
  85. chunk.setTileID(rp, id, world_->getTileByID(id).image_.texture_.get());
  86. chunk.markModified();
  87. }
  88. }
  89. void WorldPlane::setTile(TilePos pos, const std::string &name) {
  90. setTileID(pos, world_->getTileID(name));
  91. }
  92. Tile::ID WorldPlane::getTileID(TilePos pos) {
  93. return getChunk(chunkPos(pos)).getTileID(relPos(pos));
  94. }
  95. Tile &WorldPlane::getTile(TilePos pos) {
  96. return world_->getTileByID(getTileID(pos));
  97. }
  98. Iter<Entity *> WorldPlane::getEntsInArea(Vec2 center, float radius) {
  99. return Iter<Entity *>([] { return std::nullopt; });
  100. // TODO: this
  101. /*
  102. return mapFilter(entities_.begin(), entities_.end(), [=](std::unique_ptr<Entity> &ent)
  103. -> std::optional<Entity *> {
  104. // Filter out things which don't have bodies
  105. auto *has_body = dynamic_cast<BodyTrait::HasBody *>(ent.get());
  106. if (has_body == nullptr)
  107. return std::nullopt;
  108. // Filter out things which are too far away from 'center'
  109. auto &body = has_body->getBody();
  110. auto bounds = body.getBounds();
  111. Vec2 entcenter = bounds.pos + (bounds.size / 2);
  112. auto dist = (entcenter - center).length();
  113. if (dist > radius)
  114. return std::nullopt;
  115. return ent.get();
  116. });
  117. */
  118. }
  119. EntityRef WorldPlane::spawnPlayer() {
  120. return gen_->spawnPlayer(getContext());
  121. }
  122. void WorldPlane::breakTile(TilePos pos) {
  123. // If the block is already air, do nothing
  124. Tile::ID id = getTileID(pos);
  125. Tile::ID air = world_->getTileID("@::air");
  126. if (id == air)
  127. return;
  128. // Change tile to air and emit event
  129. setTileID(pos, air);
  130. world_->evt_tile_break_.emit(getContext(), pos, world_->getTileByID(id));
  131. }
  132. SDL_Color WorldPlane::backgroundColor() {
  133. return gen_->backgroundColor(world_->player_->getBody().getBounds().pos);
  134. }
  135. void WorldPlane::draw(Win &win) {
  136. auto ctx = getContext();
  137. auto pbounds = world_->player_->getBody().getBounds();
  138. gen_->drawBackground(ctx, win, pbounds.pos);
  139. ChunkPos pcpos = ChunkPos(
  140. (int)floor(pbounds.pos.x / CHUNK_WIDTH),
  141. (int)floor(pbounds.pos.y / CHUNK_HEIGHT));
  142. // Just init one chunk per frame
  143. if (chunk_init_list_.size() > 0) {
  144. Chunk *chunk = chunk_init_list_.front();
  145. info << "render chunk " << chunk->pos_;
  146. chunk_init_list_.pop_front();
  147. chunk->render(ctx, win.renderer_);
  148. }
  149. for (int x = -1; x <= 1; ++x) {
  150. for (int y = -1; y <= 1; ++y) {
  151. auto iter = chunks_.find(pcpos + ChunkPos(x, y));
  152. if (iter != chunks_.end())
  153. iter->second.draw(ctx, win);
  154. }
  155. }
  156. for (auto &coll: ent_colls_)
  157. coll->draw(ctx, win);
  158. if (debug_boxes_.size() > 0) {
  159. for (auto &pos: debug_boxes_) {
  160. win.drawRect(pos, Vec2(1, 1));
  161. }
  162. }
  163. }
  164. void WorldPlane::update(float dt) {
  165. auto ctx = getContext();
  166. debug_boxes_.clear();
  167. for (auto &coll: ent_colls_)
  168. coll->update(ctx, dt);
  169. }
  170. void WorldPlane::tick(float dt) {
  171. auto ctx = getContext();
  172. // Any chunk which has been in use since last tick should be kept alive
  173. for (std::pair<ChunkPos, Chunk *> &ch: tick_chunks_)
  174. ch.second->keepActive();
  175. tick_chunks_.clear();
  176. for (auto &coll: ent_colls_)
  177. coll->tick(ctx, dt);
  178. // Tick all chunks, figure out if any of them should be deleted or compressed
  179. auto iter = active_chunks_.begin();
  180. auto last = active_chunks_.end();
  181. while (iter != last) {
  182. auto &chunk = *iter;
  183. auto action = chunk->tick(dt);
  184. switch (action) {
  185. case Chunk::TickAction::DEACTIVATE:
  186. info << "Compressing inactive modified chunk " << chunk->pos_;
  187. chunk->compress();
  188. iter = active_chunks_.erase(iter);
  189. last = active_chunks_.end();
  190. break;
  191. case Chunk::TickAction::DELETE:
  192. info << "Deleting inactive unmodified chunk " << chunk->pos_;
  193. chunks_.erase(chunk->pos_);
  194. iter = active_chunks_.erase(iter);
  195. last = active_chunks_.end();
  196. break;
  197. case Chunk::TickAction::NOTHING:
  198. ++iter;
  199. break;
  200. }
  201. }
  202. }
  203. void WorldPlane::debugBox(TilePos pos) {
  204. debug_boxes_.push_back(pos);
  205. }
  206. }