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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  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. // This might look weird, but it reduces an otherwise complex series of operations
  13. // including conditional branches into like four x64 instructions.
  14. // Basically, the problem is that we want 'floor(pos.x / CHUNK_WIDTH)', but
  15. // integer division rounds towards zero, it doesn't round down.
  16. // Solution: Move the position far to the right in the number line, do the math there
  17. // with integer division which always rounds down (because the numbers are always >0),
  18. // then move the result back to something hovering around 0 again.
  19. return ChunkPos(
  20. ((long long)pos.x + (LLONG_MAX / 2) + 1) / CHUNK_WIDTH - ((LLONG_MAX / 2) / CHUNK_WIDTH) - 1,
  21. ((long long)pos.y + (LLONG_MAX / 2) + 1) / CHUNK_HEIGHT - ((LLONG_MAX / 2) / CHUNK_HEIGHT) - 1);
  22. }
  23. static Chunk::RelPos relPos(TilePos pos) {
  24. // This uses a similar trick to chunkPos to turn a mess of conditional moves
  25. // and math instructions into literally one movabs and one 'and'
  26. return Chunk::RelPos(
  27. (pos.x + (long long)CHUNK_WIDTH * ((LLONG_MAX / 2) / CHUNK_WIDTH)) % CHUNK_WIDTH,
  28. (pos.y + (long long)CHUNK_HEIGHT * ((LLONG_MAX / 2) / CHUNK_HEIGHT)) % CHUNK_HEIGHT);
  29. }
  30. Context WorldPlane::getContext() {
  31. return {
  32. .game = *world_->game_,
  33. .world = *world_,
  34. .plane = *this,
  35. .resources = world_->resources_
  36. };
  37. }
  38. WorldPlane::WorldPlane(
  39. ID id, World *world, std::unique_ptr<WorldGen> gen,
  40. std::vector<std::unique_ptr<EntityCollection>> &&colls):
  41. id_(id), world_(world), gen_(std::move(gen)), ent_colls_(std::move(colls)) {
  42. for (auto &coll: ent_colls_) {
  43. ent_colls_by_type_[coll->type()] = coll.get();
  44. ent_colls_by_name_[coll->name()] = coll.get();
  45. }
  46. }
  47. EntityRef WorldPlane::spawnEntity(const std::string &name, const Entity::PackObject &obj) {
  48. return ent_colls_by_name_.at(name)->spawn(getContext(), obj);
  49. }
  50. bool WorldPlane::hasChunk(ChunkPos pos) {
  51. return chunks_.find(pos) != chunks_.end();
  52. }
  53. // This function will be a bit weird because it's a really fucking hot function.
  54. Chunk &WorldPlane::getChunk(ChunkPos pos) {
  55. // First, look through all chunks which have been in use this tick
  56. for (auto [chpos, chunk]: tick_chunks_) {
  57. if (chpos == pos)
  58. return *chunk;
  59. }
  60. Chunk &chunk = slowGetChunk(pos);
  61. tick_chunks_.push_back({ pos, &chunk });
  62. return chunk;
  63. }
  64. Chunk &WorldPlane::slowGetChunk(ChunkPos pos) {
  65. ZoneScopedN("WorldPlane slowGetChunk");
  66. auto iter = chunks_.find(pos);
  67. // Create chunk if that turns out to be necessary
  68. if (iter == chunks_.end()) {
  69. iter = chunks_.emplace(pos, Chunk(pos)).first;
  70. Chunk &chunk = iter->second;
  71. gen_->genChunk(*this, chunk);
  72. active_chunks_.push_back(&chunk);
  73. chunk_init_list_.push_back(&chunk);
  74. // Otherwise, it might not be active, so let's activate it
  75. } else if (!iter->second.isActive()) {
  76. iter->second.keepActive();
  77. active_chunks_.push_back(&iter->second);
  78. chunk_init_list_.push_back(&iter->second);
  79. }
  80. return iter->second;
  81. }
  82. void WorldPlane::setTileID(TilePos pos, Tile::ID id) {
  83. Chunk &chunk = getChunk(chunkPos(pos));
  84. Chunk::RelPos rp = relPos(pos);
  85. Tile::ID old = chunk.getTileID(rp);
  86. if (id != old) {
  87. Tile &newTile = world_->getTileByID(id);
  88. Tile &oldTile = world_->getTileByID(old);
  89. chunk.setTileID(rp, id, newTile.image_.texture_.get());
  90. chunk.markModified();
  91. if (oldTile.light_level_ > 0) {
  92. removeLight(pos, oldTile.light_level_);
  93. }
  94. if (newTile.light_level_ > 0) {
  95. addLight(pos, newTile.light_level_);
  96. }
  97. }
  98. }
  99. void WorldPlane::setTile(TilePos pos, const std::string &name) {
  100. setTileID(pos, world_->getTileID(name));
  101. }
  102. Tile::ID WorldPlane::getTileID(TilePos pos) {
  103. return getChunk(chunkPos(pos)).getTileID(relPos(pos));
  104. }
  105. Tile &WorldPlane::getTile(TilePos pos) {
  106. return world_->getTileByID(getTileID(pos));
  107. }
  108. Iter<Entity *> WorldPlane::getEntsInArea(Vec2 center, float radius) {
  109. return Iter<Entity *>([] { return std::nullopt; });
  110. // TODO: this
  111. /*
  112. return mapFilter(entities_.begin(), entities_.end(), [=](std::unique_ptr<Entity> &ent)
  113. -> std::optional<Entity *> {
  114. // Filter out things which don't have bodies
  115. auto *has_body = dynamic_cast<BodyTrait::HasBody *>(ent.get());
  116. if (has_body == nullptr)
  117. return std::nullopt;
  118. // Filter out things which are too far away from 'center'
  119. auto &body = has_body->getBody();
  120. auto bounds = body.getBounds();
  121. Vec2 entcenter = bounds.pos + (bounds.size / 2);
  122. auto dist = (entcenter - center).length();
  123. if (dist > radius)
  124. return std::nullopt;
  125. return ent.get();
  126. });
  127. */
  128. }
  129. EntityRef WorldPlane::spawnPlayer() {
  130. return gen_->spawnPlayer(getContext());
  131. }
  132. void WorldPlane::breakTile(TilePos pos) {
  133. // If the block is already air, do nothing
  134. Tile::ID id = getTileID(pos);
  135. Tile::ID air = world_->getTileID("@::air");
  136. if (id == air)
  137. return;
  138. // Change tile to air and emit event
  139. setTileID(pos, air);
  140. world_->evt_tile_break_.emit(getContext(), pos, world_->getTileByID(id));
  141. }
  142. SDL_Color WorldPlane::backgroundColor() {
  143. return gen_->backgroundColor(world_->player_->pos);
  144. }
  145. void WorldPlane::draw(Win &win) {
  146. ZoneScopedN("WorldPlane draw");
  147. auto ctx = getContext();
  148. auto &pbody = *(world_->player_);
  149. gen_->drawBackground(ctx, win, pbody.pos);
  150. ChunkPos pcpos = ChunkPos(
  151. (int)floor(pbody.pos.x / CHUNK_WIDTH),
  152. (int)floor(pbody.pos.y / CHUNK_HEIGHT));
  153. // Just init one chunk per frame
  154. if (chunk_init_list_.size() > 0) {
  155. Chunk *chunk = chunk_init_list_.front();
  156. info << "render chunk " << chunk->pos_;
  157. chunk_init_list_.pop_front();
  158. chunk->render(ctx, win.renderer_);
  159. }
  160. for (int x = -1; x <= 1; ++x) {
  161. for (int y = -1; y <= 1; ++y) {
  162. auto iter = chunks_.find(pcpos + ChunkPos(x, y));
  163. if (iter != chunks_.end())
  164. iter->second.draw(ctx, win);
  165. }
  166. }
  167. for (auto &coll: ent_colls_)
  168. coll->draw(ctx, win);
  169. if (debug_boxes_.size() > 0) {
  170. for (auto &pos: debug_boxes_) {
  171. win.drawRect(pos, Vec2(1, 1));
  172. }
  173. }
  174. }
  175. void WorldPlane::update(float dt) {
  176. ZoneScopedN("WorldPlane update");
  177. auto ctx = getContext();
  178. debug_boxes_.clear();
  179. for (auto &coll: ent_colls_)
  180. coll->update(ctx, dt);
  181. }
  182. void WorldPlane::tick(float dt) {
  183. ZoneScopedN("WorldPlane tick");
  184. auto ctx = getContext();
  185. // Any chunk which has been in use since last tick should be kept alive
  186. for (std::pair<ChunkPos, Chunk *> &ch: tick_chunks_)
  187. ch.second->keepActive();
  188. tick_chunks_.clear();
  189. for (auto &coll: ent_colls_)
  190. coll->tick(ctx, dt);
  191. // Tick all chunks, figure out if any of them should be deleted or compressed
  192. auto iter = active_chunks_.begin();
  193. auto last = active_chunks_.end();
  194. while (iter != last) {
  195. auto &chunk = *iter;
  196. auto action = chunk->tick(dt);
  197. switch (action) {
  198. case Chunk::TickAction::DEACTIVATE:
  199. info << "Compressing inactive modified chunk " << chunk->pos_;
  200. chunk->compress();
  201. iter = active_chunks_.erase(iter);
  202. last = active_chunks_.end();
  203. break;
  204. case Chunk::TickAction::DELETE:
  205. info << "Deleting inactive unmodified chunk " << chunk->pos_;
  206. chunks_.erase(chunk->pos_);
  207. iter = active_chunks_.erase(iter);
  208. last = active_chunks_.end();
  209. break;
  210. case Chunk::TickAction::NOTHING:
  211. ++iter;
  212. break;
  213. }
  214. }
  215. }
  216. void WorldPlane::debugBox(TilePos pos) {
  217. debug_boxes_.push_back(pos);
  218. }
  219. void WorldPlane::addLight(TilePos pos, uint8_t level) {
  220. int sqrLevel = level * level;
  221. for (int y = -level; y <= level; ++y) {
  222. for (int x = -level; x <= level; ++x) {
  223. int sqrDist = x * x + y * y;
  224. if (sqrDist > sqrLevel) {
  225. continue;
  226. }
  227. int lightDiff = level - (int)sqrt(sqrDist);
  228. if (lightDiff <= 0) {
  229. continue;
  230. }
  231. TilePos tp = pos + Vec2i(x, y);
  232. ChunkPos cp = chunkPos(tp);
  233. Chunk::RelPos rp = relPos(tp);
  234. Chunk &ch = getChunk(cp);
  235. int light = (int)ch.getLightLevel(rp) + lightDiff;
  236. if (light > 255) {
  237. light = 255;
  238. }
  239. ch.setLightData(rp, light);
  240. }
  241. }
  242. }
  243. void WorldPlane::removeLight(TilePos pos, uint8_t level) {
  244. int sqrLevel = level * level;
  245. for (int y = -level; y <= level; ++y) {
  246. for (int x = -level; x <= level; ++x) {
  247. int sqrDist = x * x + y * y;
  248. if (sqrDist > sqrLevel) {
  249. continue;
  250. }
  251. int lightDiff = level - (int)sqrt(sqrDist);
  252. if (lightDiff <= 0) {
  253. continue;
  254. }
  255. TilePos tp = pos + Vec2i(x, y);
  256. ChunkPos cp = chunkPos(tp);
  257. Chunk::RelPos rp = relPos(tp);
  258. Chunk &ch = getChunk(cp);
  259. int light = (int)ch.getLightLevel(rp) - lightDiff;
  260. if (light < 0) {
  261. light = 0;
  262. }
  263. ch.setLightData(rp, light);
  264. }
  265. }
  266. }
  267. }