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 5.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. #include "WorldPlane.h"
  2. #include <math.h>
  3. #include <iostream>
  4. #include "log.h"
  5. #include "World.h"
  6. #include "Game.h"
  7. #include "Clock.h"
  8. #include "Win.h"
  9. namespace Swan {
  10. static ChunkPos chunkPos(TilePos pos) {
  11. int chx = pos.x / CHUNK_WIDTH;
  12. if (pos.x < 0 && pos.x % CHUNK_WIDTH != 0) chx -= 1;
  13. int chy = pos.y / CHUNK_HEIGHT;
  14. if (pos.y < 0 && pos.y % CHUNK_HEIGHT != 0) chy -= 1;
  15. return ChunkPos(chx, chy);
  16. }
  17. static Chunk::RelPos relPos(TilePos pos) {
  18. int rx = pos.x % CHUNK_WIDTH;
  19. if (rx < 0) rx += CHUNK_WIDTH;
  20. int ry = pos.y % CHUNK_HEIGHT;
  21. if (ry < 0) ry += CHUNK_HEIGHT;
  22. return Chunk::RelPos(rx, ry);
  23. }
  24. Context WorldPlane::getContext() {
  25. return { .game = *world_->game_, .world = *world_, .plane = *this, .resources = world_->resources_ };
  26. }
  27. Entity *WorldPlane::spawnEntity(const std::string &name, const SRF &params) {
  28. if (world_->ents_.find(name) == world_->ents_.end()) {
  29. panic << "Tried to spawn a non-existant entity " << name << "!";
  30. abort();
  31. }
  32. Entity *ent = world_->ents_[name]->create(getContext(), params);
  33. if (auto has_body = dynamic_cast<BodyTrait::HasBody *>(ent); has_body) {
  34. BodyTrait::Body &body = has_body->getBody();
  35. BodyTrait::Bounds bounds = body.getBounds();
  36. body.move({ 0.5f - bounds.size.x / 2, 0 });
  37. }
  38. spawn_list_.push_back(std::unique_ptr<Entity>(ent));
  39. info << "Spawned " << name << ". SRF: " << params;
  40. return ent;
  41. }
  42. void WorldPlane::despawnEntity(Entity &ent) {
  43. info << "Despawned entity.";
  44. despawn_list_.push_back(&ent);
  45. }
  46. bool WorldPlane::hasChunk(ChunkPos pos) {
  47. return chunks_.find(pos) != chunks_.end();
  48. }
  49. Chunk &WorldPlane::getChunk(ChunkPos pos) {
  50. auto iter = chunks_.find(pos);
  51. if (iter == chunks_.end()) {
  52. iter = chunks_.emplace(pos, Chunk(pos)).first;
  53. Chunk &chunk = iter->second;
  54. gen_->genChunk(*this, chunk);
  55. active_chunks_.insert(&chunk);
  56. chunk_init_list_.push_back(&chunk);
  57. } else if (iter->second.keepActive()) {
  58. active_chunks_.insert(&iter->second);
  59. chunk_init_list_.push_back(&iter->second);
  60. }
  61. return iter->second;
  62. }
  63. void WorldPlane::setTileID(TilePos pos, Tile::ID id) {
  64. Chunk &chunk = getChunk(chunkPos(pos));
  65. Chunk::RelPos rp = relPos(pos);
  66. chunk.setTileID(rp, id, world_->getTileByID(id).image_.texture_.get());
  67. if (active_chunks_.find(&chunk) == active_chunks_.end()) {
  68. active_chunks_.insert(&chunk);
  69. chunk_init_list_.push_back(&chunk);
  70. }
  71. }
  72. void WorldPlane::setTile(TilePos pos, const std::string &name) {
  73. setTileID(pos, world_->getTileID(name));
  74. }
  75. Tile::ID WorldPlane::getTileID(TilePos pos) {
  76. Chunk &chunk = getChunk(chunkPos(pos));
  77. if (active_chunks_.find(&chunk) == active_chunks_.end())
  78. active_chunks_.insert(&chunk);
  79. return chunk.getTileID(relPos(pos));
  80. }
  81. Tile &WorldPlane::getTile(TilePos pos) {
  82. return world_->getTileByID(getTileID(pos));
  83. }
  84. Iter<Entity *> WorldPlane::getEntsInArea(Vec2 center, float radius) {
  85. // TODO: Optimize this using fancy data structures.
  86. return mapFilter(entities_.begin(), entities_.end(), [=](std::unique_ptr<Entity> &ent)
  87. -> std::optional<Entity *> {
  88. // Filter out things which don't have bodies
  89. auto *has_body = dynamic_cast<BodyTrait::HasBody *>(ent.get());
  90. if (has_body == nullptr)
  91. return std::nullopt;
  92. // Filter out things which are too far away from 'center'
  93. auto &body = has_body->getBody();
  94. auto bounds = body.getBounds();
  95. Vec2 entcenter = bounds.pos + (bounds.size / 2);
  96. auto dist = (entcenter - center).length();
  97. if (dist > radius)
  98. return std::nullopt;
  99. return ent.get();
  100. });
  101. }
  102. BodyTrait::HasBody *WorldPlane::spawnPlayer() {
  103. return gen_->spawnPlayer(*this);
  104. }
  105. void WorldPlane::breakBlock(TilePos pos) {
  106. // If the block is already air, do nothing
  107. Tile::ID id = getTileID(pos);
  108. Tile::ID air = world_->getTileID("core::air");
  109. if (id == air)
  110. return;
  111. // Change the block to air...
  112. setTileID(pos, air);
  113. // Then spawn an item stack entity.
  114. Tile &t = world_->getTileByID(id);
  115. if (t.dropped_item_ != std::nullopt) {
  116. spawnEntity("core::item-stack", SRFArray{
  117. new SRFFloatArray{ (float)pos.x, (float)pos.y },
  118. new SRFString{ *t.dropped_item_ },
  119. });
  120. }
  121. }
  122. void WorldPlane::draw(Win &win) {
  123. auto pbounds = world_->player_->getBody().getBounds();
  124. ChunkPos pcpos = ChunkPos(
  125. (int)floor(pbounds.pos.x / CHUNK_WIDTH),
  126. (int)floor(pbounds.pos.y / CHUNK_HEIGHT));
  127. // Just init one chunk per frame
  128. if (chunk_init_list_.size() > 0) {
  129. Chunk *chunk = chunk_init_list_.front();
  130. info << "render chunk " << chunk->pos_;
  131. chunk_init_list_.pop_front();
  132. chunk->render(getContext(), win.renderer_);
  133. }
  134. for (int x = -1; x <= 1; ++x) {
  135. for (int y = -1; y <= 1; ++y) {
  136. auto iter = chunks_.find(pcpos + ChunkPos(x, y));
  137. if (iter != chunks_.end())
  138. iter->second.draw(getContext(), win);
  139. }
  140. }
  141. for (auto &ent: entities_)
  142. ent->draw(getContext(), win);
  143. if (debug_boxes_.size() > 0) {
  144. for (auto &pos: debug_boxes_) {
  145. win.drawRect(pos, Vec2(1, 1));
  146. }
  147. }
  148. }
  149. void WorldPlane::update(float dt) {
  150. debug_boxes_.clear();
  151. for (auto &ent: entities_)
  152. ent->update(getContext(), dt);
  153. for (auto &ent: spawn_list_)
  154. entities_.push_back(std::move(ent));
  155. spawn_list_.clear();
  156. for (auto entptr: despawn_list_) {
  157. for (auto it = entities_.begin(); it != entities_.end(); ++it) {
  158. if (it->get() == entptr) {
  159. entities_.erase(it);
  160. break;
  161. }
  162. }
  163. }
  164. despawn_list_.clear();
  165. }
  166. void WorldPlane::tick(float dt) {
  167. for (auto &ent: entities_)
  168. ent->tick(getContext(), dt);
  169. for (auto &chunk: active_chunks_)
  170. chunk->tick(dt);
  171. std::erase_if(active_chunks_, [](Chunk *chunk) { return !chunk->isActive(); });
  172. }
  173. void WorldPlane::debugBox(TilePos pos) {
  174. debug_boxes_.push_back(pos);
  175. }
  176. }