``` C++ | ``` C++ | ||||
void someLongFunctionName( | void someLongFunctionName( | ||||
int first_argument, | |||||
int second_argument) { | |||||
int firstArgument, | |||||
int secondArgument) { | |||||
whatever; | whatever; | ||||
} | } | ||||
``` | ``` | ||||
* Classes and structs are PascalCase. | * Classes and structs are PascalCase. | ||||
* Methods and free functions are camelCase. | * Methods and free functions are camelCase. | ||||
* Local variables are snake_case. | |||||
* Class member variables are snake_case_ (with the trailing underscore). | |||||
* Struct member variables are snake_case (without the trailing underscore). | |||||
* Local variables are camelCase. | |||||
* Class member variables are camelCase\_ (with the trailing underscore). | |||||
* Struct member variables are camelCase (without the trailing underscore). | |||||
* Constants are UPPER\_SNAKE\_CASE (no trailing underscore) everywhere. | |||||
## Structure | ## Structure | ||||
#include "entities/PlayerEntity.h" | #include "entities/PlayerEntity.h" | ||||
static int grassLevel(const siv::PerlinNoise &perlin, int x) { | |||||
static int getGrassLevel(const siv::PerlinNoise &perlin, int x) { | |||||
return (int)(perlin.noise(x / 50.0, 0) * 13); | return (int)(perlin.noise(x / 50.0, 0) * 13); | ||||
} | } | ||||
static int stoneLevel(const siv::PerlinNoise &perlin, int x) { | |||||
static int getStoneLevel(const siv::PerlinNoise &perlin, int x) { | |||||
return (int)(perlin.noise(x / 50.0, 10) * 10) + 10; | return (int)(perlin.noise(x / 50.0, 10) * 10) + 10; | ||||
} | } | ||||
} | } | ||||
Swan::Tile::ID DefaultWorldGen::genTile(Swan::TilePos pos) { | Swan::Tile::ID DefaultWorldGen::genTile(Swan::TilePos pos) { | ||||
int grass_level = grassLevel(perlin_, pos.x); | |||||
int stone_level = stoneLevel(perlin_, pos.x); | |||||
int grassLevel = getGrassLevel(perlin_, pos.x); | |||||
int stoneLevel = getStoneLevel(perlin_, pos.x); | |||||
// Caves | // Caves | ||||
if (pos.y > grass_level + 7 && perlin_.noise(pos.x / 43.37, pos.y / 16.37) > 0.2) | |||||
if (pos.y > grassLevel + 7 && perlin_.noise(pos.x / 43.37, pos.y / 16.37) > 0.2) | |||||
return tAir_; | return tAir_; | ||||
if (pos.y > stone_level) | |||||
if (pos.y > stoneLevel) | |||||
return tStone_; | return tStone_; | ||||
else if (pos.y > grass_level) | |||||
else if (pos.y > grassLevel) | |||||
return tDirt_; | return tDirt_; | ||||
else if (pos.y == grass_level) | |||||
else if (pos.y == grassLevel) | |||||
return tGrass_; | return tGrass_; | ||||
else | else | ||||
return tAir_; | return tAir_; | ||||
Swan::EntityRef DefaultWorldGen::spawnPlayer(const Swan::Context &ctx) { | Swan::EntityRef DefaultWorldGen::spawnPlayer(const Swan::Context &ctx) { | ||||
int x = 0; | int x = 0; | ||||
return ctx.plane.spawnEntity<PlayerEntity>( | return ctx.plane.spawnEntity<PlayerEntity>( | ||||
ctx, Swan::Vec2{ (float)x, (float)grassLevel(perlin_, x) - 4 }); | |||||
ctx, Swan::Vec2{ (float)x, (float)getGrassLevel(perlin_, x) - 4 }); | |||||
} | } |
} | } | ||||
void ItemStackEntity::tick(const Swan::Context &ctx, float dt) { | void ItemStackEntity::tick(const Swan::Context &ctx, float dt) { | ||||
despawn_timer_ -= dt; | |||||
if (despawn_timer_ <= 0) | |||||
despawnTimer_ -= dt; | |||||
if (despawnTimer_ <= 0) | |||||
despawn(ctx); | despawn(ctx); | ||||
} | } | ||||
ItemStackEntity(): PhysicsEntity(SIZE) {} | ItemStackEntity(): PhysicsEntity(SIZE) {} | ||||
float despawn_timer_ = DESPAWN_TIME; | |||||
float despawnTimer_ = DESPAWN_TIME; | |||||
Swan::Item *item_ = NULL; | Swan::Item *item_ = NULL; | ||||
}; | }; |
State oldState = state_; | State oldState = state_; | ||||
state_ = State::IDLE; | state_ = State::IDLE; | ||||
mouse_tile_ = ctx.game.getMouseTile(); | |||||
ctx.plane.debugBox(mouse_tile_); | |||||
jump_timer_.tick(dt); | |||||
place_timer_.tick(dt); | |||||
mouseTile_ = ctx.game.getMouseTile(); | |||||
ctx.plane.debugBox(mouseTile_); | |||||
jumpTimer_.tick(dt); | |||||
placeTimer_.tick(dt); | |||||
// Break block | // Break block | ||||
if (ctx.game.isMousePressed(SDL_BUTTON_LEFT)) | if (ctx.game.isMousePressed(SDL_BUTTON_LEFT)) | ||||
ctx.plane.breakTile(mouse_tile_); | |||||
ctx.plane.breakTile(mouseTile_); | |||||
// Place block | // Place block | ||||
if (ctx.game.isMousePressed(SDL_BUTTON_RIGHT) && place_timer_.periodic(0.50)) { | |||||
if (ctx.plane.getTileID(mouse_tile_) == ctx.world.getTileID("@::air")) { | |||||
ctx.plane.setTile(mouse_tile_, "core::torch"); | |||||
if (ctx.game.isMousePressed(SDL_BUTTON_RIGHT) && placeTimer_.periodic(0.50)) { | |||||
if (ctx.plane.getTileID(mouseTile_) == ctx.world.getTileID("@::air")) { | |||||
ctx.plane.setTile(mouseTile_, "core::torch"); | |||||
} | } | ||||
} | } | ||||
state_ = State::RUNNING_R; | state_ = State::RUNNING_R; | ||||
} | } | ||||
bool jump_pressed = ctx.game.isKeyPressed(SDL_SCANCODE_SPACE); | |||||
bool jumpPressed = ctx.game.isKeyPressed(SDL_SCANCODE_SPACE); | |||||
// Jump | // Jump | ||||
if (physics_.on_ground && jump_pressed && jump_timer_.periodic(0.5)) { | |||||
if (physics_.onGround && jumpPressed && jumpTimer_.periodic(0.5)) { | |||||
physics_.vel.y = -JUMP_VEL; | physics_.vel.y = -JUMP_VEL; | ||||
} | } | ||||
// Fall down faster than we went up | // Fall down faster than we went up | ||||
if (!physics_.on_ground && (!jump_pressed || physics_.vel.y > 0)) | |||||
if (!physics_.on_ground && (!jumpPressed || physics_.vel.y > 0)) | |||||
physics_.force += Swan::Vec2(0, DOWN_FORCE); | physics_.force += Swan::Vec2(0, DOWN_FORCE); | ||||
if (state_ != oldState) | if (state_ != oldState) | ||||
// Do this after moving so that it's not behind | // Do this after moving so that it's not behind | ||||
Swan::Vec2 headPos = body_.topMid() + Swan::Vec2(0, 0.5); | Swan::Vec2 headPos = body_.topMid() + Swan::Vec2(0, 0.5); | ||||
Swan::TilePos tilePos = Swan::Vec2i(floor(headPos.x), floor(headPos.y)); | Swan::TilePos tilePos = Swan::Vec2i(floor(headPos.x), floor(headPos.y)); | ||||
if (!placed_light_) { | |||||
if (!placedLight_) { | |||||
ctx.plane.addLight(tilePos, LIGHT_LEVEL); | ctx.plane.addLight(tilePos, LIGHT_LEVEL); | ||||
placed_light_ = true; | |||||
light_tile_ = tilePos; | |||||
} else if (tilePos != light_tile_) { | |||||
ctx.plane.removeLight(light_tile_, LIGHT_LEVEL); | |||||
placedLight_ = true; | |||||
lightTile_ = tilePos; | |||||
} else if (tilePos != lightTile_) { | |||||
ctx.plane.removeLight(lightTile_, LIGHT_LEVEL); | |||||
ctx.plane.addLight(tilePos, LIGHT_LEVEL); | ctx.plane.addLight(tilePos, LIGHT_LEVEL); | ||||
light_tile_ = tilePos; | |||||
lightTile_ = tilePos; | |||||
} | } | ||||
} | } | ||||
State state_ = State::IDLE; | State state_ = State::IDLE; | ||||
std::array<Swan::Animation, (int)State::COUNT> anims_; | std::array<Swan::Animation, (int)State::COUNT> anims_; | ||||
Swan::Clock jump_timer_; | |||||
Swan::Clock place_timer_; | |||||
Swan::TilePos mouse_tile_; | |||||
Swan::Clock jumpTimer_; | |||||
Swan::Clock placeTimer_; | |||||
Swan::TilePos mouseTile_; | |||||
Swan::TilePos light_tile_; | |||||
bool placed_light_ = false; | |||||
Swan::TilePos lightTile_; | |||||
bool placedLight_ = false; | |||||
BasicInventory inventory_{INVENTORY_SIZE}; | BasicInventory inventory_{INVENTORY_SIZE}; | ||||
}; | }; |
class CoreMod: public Swan::Mod { | class CoreMod: public Swan::Mod { | ||||
public: | public: | ||||
CoreMod(Swan::World &world): Swan::Mod("core") { | CoreMod(Swan::World &world): Swan::Mod("core") { | ||||
break_listener_ = world.evt_tile_break_.subscribe( | |||||
breakListener_ = world.evtTileBreak_.subscribe( | |||||
std::bind_front(&CoreMod::onTileBreak, this)); | std::bind_front(&CoreMod::onTileBreak, this)); | ||||
registerImage("tile/stone"); | registerImage("tile/stone"); | ||||
} | } | ||||
void onTileBreak(const Swan::Context &ctx, Swan::TilePos pos, Swan::Tile &tile) { | void onTileBreak(const Swan::Context &ctx, Swan::TilePos pos, Swan::Tile &tile) { | ||||
if (tile.dropped_item_) { | |||||
if (tile.droppedItem_) { | |||||
ctx.plane.spawnEntity<ItemStackEntity>( | ctx.plane.spawnEntity<ItemStackEntity>( | ||||
ctx, (Swan::Vec2)pos + Swan::Vec2{0.5, 0.5}, *tile.dropped_item_); | |||||
ctx, (Swan::Vec2)pos + Swan::Vec2{0.5, 0.5}, *tile.droppedItem_); | |||||
} | } | ||||
} | } | ||||
Swan::EventListener break_listener_; | |||||
Swan::EventListener breakListener_; | |||||
}; | }; | ||||
extern "C" Swan::Mod *mod_create(Swan::World &world) { | extern "C" Swan::Mod *mod_create(Swan::World &world) { |
std::unique_ptr<RendererState> state_; | std::unique_ptr<RendererState> state_; | ||||
std::vector<DrawChunk> draw_chunks_; | |||||
std::vector<DrawSprite> draw_sprites_; | |||||
std::vector<DrawChunk> drawChunks_; | |||||
std::vector<DrawSprite> drawSprites_; | |||||
}; | }; | ||||
inline void Renderer::drawChunk(RenderChunk chunk, SwanCommon::Vec2 pos) { | inline void Renderer::drawChunk(RenderChunk chunk, SwanCommon::Vec2 pos) { | ||||
draw_chunks_.emplace_back(pos, chunk); | |||||
drawChunks_.emplace_back(pos, chunk); | |||||
} | } | ||||
inline void Renderer::drawSprite(RenderSprite sprite, Mat3gf mat, int frame) { | inline void Renderer::drawSprite(RenderSprite sprite, Mat3gf mat, int frame) { | ||||
draw_sprites_.emplace_back(mat, frame, sprite); | |||||
drawSprites_.emplace_back(mat, frame, sprite); | |||||
} | } | ||||
inline void Renderer::drawSprite(RenderSprite sprite, SwanCommon::Vec2 pos, int frame) { | inline void Renderer::drawSprite(RenderSprite sprite, SwanCommon::Vec2 pos, int frame) { | ||||
draw_sprites_.emplace_back(Mat3gf{}.translate(pos), frame, sprite); | |||||
drawSprites_.emplace_back(Mat3gf{}.translate(pos), frame, sprite); | |||||
} | } | ||||
inline void Renderer::drawSpriteFlipped(RenderSprite sprite, SwanCommon::Vec2 pos, int frame) { | inline void Renderer::drawSpriteFlipped(RenderSprite sprite, SwanCommon::Vec2 pos, int frame) { | ||||
draw_sprites_.emplace_back(Mat3gf{}.translate(pos).scale({ -1, 1 }), frame, sprite); | |||||
drawSprites_.emplace_back(Mat3gf{}.translate(pos).scale({ -1, 1 }), frame, sprite); | |||||
} | } | ||||
} | } |
private: | private: | ||||
Renderer &rnd_; | Renderer &rnd_; | ||||
std::unordered_map<std::string, RenderSprite> sprites_; | std::unordered_map<std::string, RenderSprite> sprites_; | ||||
std::vector<ResourceTileAnimation> tile_anims_; | |||||
std::vector<ResourceTileAnimation> tileAnims_; | |||||
TileAtlas atlas_; | TileAtlas atlas_; | ||||
friend ResourceManager; | friend ResourceManager; | ||||
Renderer &rnd_; | Renderer &rnd_; | ||||
std::unordered_map<std::string, RenderSprite> sprites_; | std::unordered_map<std::string, RenderSprite> sprites_; | ||||
std::unordered_map<std::string, RenderTile> tiles_; | std::unordered_map<std::string, RenderTile> tiles_; | ||||
std::vector<ResourceTileAnimation> tile_anims_; | |||||
std::vector<ResourceTileAnimation> tileAnims_; | |||||
}; | }; | ||||
inline RenderSprite ResourceBuilder::addSprite( | inline RenderSprite ResourceBuilder::addSprite( | ||||
inline void ResourceBuilder::addTile(Renderer::TileID id, std::unique_ptr<unsigned char[]> data, int frames) { | inline void ResourceBuilder::addTile(Renderer::TileID id, std::unique_ptr<unsigned char[]> data, int frames) { | ||||
atlas_.addTile(id, data.get()); | atlas_.addTile(id, data.get()); | ||||
if (frames > 1) { | if (frames > 1) { | ||||
tile_anims_.push_back({ | |||||
tileAnims_.push_back({ | |||||
.id = id, | .id = id, | ||||
.frames = frames, | .frames = frames, | ||||
.index = 0, | .index = 0, |
glCheck(); | glCheck(); | ||||
glActiveTexture(GL_TEXTURE1); | glActiveTexture(GL_TEXTURE1); | ||||
for (auto [pos, chunk]: draw_chunks_) { | |||||
for (auto [pos, chunk]: drawChunks_) { | |||||
glUniform2f(chunkProg.pos, pos.x, pos.y); | glUniform2f(chunkProg.pos, pos.x, pos.y); | ||||
glBindTexture(GL_TEXTURE_2D, chunk.tex); | glBindTexture(GL_TEXTURE_2D, chunk.tex); | ||||
glDrawArrays(GL_TRIANGLES, 0, 6); | glDrawArrays(GL_TRIANGLES, 0, 6); | ||||
glCheck(); | glCheck(); | ||||
} | } | ||||
draw_chunks_.clear(); | |||||
drawChunks_.clear(); | |||||
chunkProg.disable(); | chunkProg.disable(); | ||||
} | } | ||||
glCheck(); | glCheck(); | ||||
glActiveTexture(GL_TEXTURE0); | glActiveTexture(GL_TEXTURE0); | ||||
for (auto [mat, frame, sprite]: draw_sprites_) { | |||||
for (auto [mat, frame, sprite]: drawSprites_) { | |||||
mat.scale(sprite.scale); | mat.scale(sprite.scale); | ||||
glUniformMatrix3fv(spriteProg.transform, 1, GL_TRUE, mat.data()); | glUniformMatrix3fv(spriteProg.transform, 1, GL_TRUE, mat.data()); | ||||
glUniform3f(spriteProg.frameInfo, sprite.scale.y, sprite.frameCount, frame); | glUniform3f(spriteProg.frameInfo, sprite.scale.y, sprite.frameCount, frame); | ||||
glCheck(); | glCheck(); | ||||
} | } | ||||
draw_sprites_.clear(); | |||||
drawSprites_.clear(); | |||||
spriteProg.disable(); | spriteProg.disable(); | ||||
} | } | ||||
} | } |
ResourceManager::ResourceManager(ResourceBuilder &&builder): | ResourceManager::ResourceManager(ResourceBuilder &&builder): | ||||
rnd_(builder.rnd_), sprites_(std::move(builder.sprites_)), | rnd_(builder.rnd_), sprites_(std::move(builder.sprites_)), | ||||
tile_anims_(std::move(builder.tile_anims_)) { | |||||
tileAnims_(std::move(builder.tileAnims_)) { | |||||
size_t width, height; | size_t width, height; | ||||
const unsigned char *data = builder.atlas_.getImage(&width, &height); | const unsigned char *data = builder.atlas_.getImage(&width, &height); | ||||
rnd_.uploadTileAtlas(data, width, height); | rnd_.uploadTileAtlas(data, width, height); | ||||
void ResourceManager::tick() { | void ResourceManager::tick() { | ||||
// TODO: Maybe do a GPU->GPU copy instead of an upload from the CPU? | // TODO: Maybe do a GPU->GPU copy instead of an upload from the CPU? | ||||
for (auto &anim: tile_anims_) { | |||||
for (auto &anim: tileAnims_) { | |||||
anim.index = (anim.index + 1) % anim.frames; | anim.index = (anim.index + 1) % anim.frames; | ||||
unsigned char *data = anim.data.get() + | unsigned char *data = anim.data.get() + | ||||
SwanCommon::TILE_SIZE * SwanCommon::TILE_SIZE * 4 * anim.index; | SwanCommon::TILE_SIZE * SwanCommon::TILE_SIZE * 4 * anim.index; |
void setTileID(RelPos pos, Tile::ID id, SDL_Texture *tex) { | void setTileID(RelPos pos, Tile::ID id, SDL_Texture *tex) { | ||||
getTileData()[pos.y * CHUNK_WIDTH + pos.x] = id; | getTileData()[pos.y * CHUNK_WIDTH + pos.x] = id; | ||||
draw_list_.push_back({ pos, tex }); | |||||
drawList_.push_back({ pos, tex }); | |||||
} | } | ||||
void setTileData(RelPos pos, Tile::ID id) { | void setTileData(RelPos pos, Tile::ID id) { | ||||
getTileData()[pos.y * CHUNK_WIDTH + pos.x] = id; | getTileData()[pos.y * CHUNK_WIDTH + pos.x] = id; | ||||
need_render_ = true; | |||||
needRender_ = true; | |||||
} | } | ||||
uint8_t getLightLevel(RelPos pos) { | uint8_t getLightLevel(RelPos pos) { | ||||
void setLightData(const uint8_t *data) { | void setLightData(const uint8_t *data) { | ||||
memcpy(getLightData(), data, CHUNK_WIDTH * CHUNK_HEIGHT); | memcpy(getLightData(), data, CHUNK_WIDTH * CHUNK_HEIGHT); | ||||
need_light_render_ = true; | |||||
needLightRender_ = true; | |||||
} | } | ||||
void render(const Context &ctx, SDL_Renderer *rnd); | void render(const Context &ctx, SDL_Renderer *rnd); | ||||
void draw(const Context &ctx, Win &win); | void draw(const Context &ctx, Win &win); | ||||
TickAction tick(float dt); | TickAction tick(float dt); | ||||
bool isActive() { return deactivate_timer_ > 0; } | |||||
bool isActive() { return deactivateTimer_ > 0; } | |||||
void keepActive(); | void keepActive(); | ||||
void markModified() { is_modified_ = true; } | |||||
void markModified() { isModified_ = true; } | |||||
ChunkPos pos_; | ChunkPos pos_; | ||||
void renderList(SDL_Renderer *rnd); | void renderList(SDL_Renderer *rnd); | ||||
bool isCompressed() { return compressed_size_ != -1; } | |||||
bool isCompressed() { return compressedSize_ != -1; } | |||||
std::unique_ptr<uint8_t[]> data_; | std::unique_ptr<uint8_t[]> data_; | ||||
std::vector<std::pair<RelPos, SDL_Texture *>> draw_list_; | |||||
std::vector<std::pair<RelPos, SDL_Texture *>> drawList_; | |||||
ssize_t compressed_size_ = -1; // -1 if not compressed, a positive number if compressed | |||||
bool need_render_ = false; | |||||
bool need_light_render_ = false; | |||||
float deactivate_timer_ = DEACTIVATE_INTERVAL; | |||||
bool is_modified_ = false; | |||||
ssize_t compressedSize_ = -1; // -1 if not compressed, a positive number if compressed | |||||
bool needRender_ = false; | |||||
bool needLightRender_ = false; | |||||
float deactivateTimer_ = DEACTIVATE_INTERVAL; | |||||
bool isModified_ = false; | |||||
CPtr<SDL_Texture, SDL_DestroyTexture> texture_; | CPtr<SDL_Texture, SDL_DestroyTexture> texture_; | ||||
CPtr<SDL_Texture, SDL_DestroyTexture> light_texture_; | |||||
CPtr<SDL_Texture, SDL_DestroyTexture> lightTexture_; | |||||
}; | }; | ||||
} | } |
template<typename Ent> | template<typename Ent> | ||||
inline Entity *EntityCollectionImpl<Ent>::get(size_t idx, size_t generation) { | inline Entity *EntityCollectionImpl<Ent>::get(size_t idx, size_t generation) { | ||||
if (idx >=entities_.size()) | |||||
if (idx >= entities_.size()) | |||||
return nullptr; | return nullptr; | ||||
auto &e = entities_[idx]; | auto &e = entities_[idx]; |
public: | public: | ||||
Game(Win &win): | Game(Win &win): | ||||
win_(win), | win_(win), | ||||
mouse_pos_(0, 0) {} | |||||
mousePos_(0, 0) {} | |||||
std::optional<ModWrapper> loadMod(std::string path, World &world); | std::optional<ModWrapper> loadMod(std::string path, World &world); | ||||
void createWorld(const std::string &worldgen, const std::vector<std::string> &mods); | void createWorld(const std::string &worldgen, const std::vector<std::string> &mods); | ||||
void onKeyDown(SDL_Keysym sym) { | void onKeyDown(SDL_Keysym sym) { | ||||
pressed_keys_[sym.scancode] = true; | |||||
did_press_keys_[sym.scancode] = true; | |||||
pressedKeys_[sym.scancode] = true; | |||||
didPressKeys_[sym.scancode] = true; | |||||
} | } | ||||
void onKeyUp(SDL_Keysym sym) { | void onKeyUp(SDL_Keysym sym) { | ||||
pressed_keys_[sym.scancode] = false; | |||||
did_release_keys_[sym.scancode] = true; | |||||
pressedKeys_[sym.scancode] = false; | |||||
didReleaseKeys_[sym.scancode] = true; | |||||
} | } | ||||
void onMouseMove(Sint32 x, Sint32 y) { | void onMouseMove(Sint32 x, Sint32 y) { | ||||
mouse_pos_ = { x, y }; | |||||
mousePos_ = { x, y }; | |||||
} | } | ||||
void onMouseDown(Sint32 x, Sint32 y, Uint8 button) { | void onMouseDown(Sint32 x, Sint32 y, Uint8 button) { | ||||
mouse_pos_ = { x, y }; | |||||
pressed_buttons_[button] = true; | |||||
did_press_buttons_[button] = true; | |||||
mousePos_ = { x, y }; | |||||
pressedButtons_[button] = true; | |||||
didPressButtons_[button] = true; | |||||
} | } | ||||
void onMouseUp(Sint32 x, Sint32 y, Uint8 button) { | void onMouseUp(Sint32 x, Sint32 y, Uint8 button) { | ||||
mouse_pos_ = { x, y }; | |||||
pressed_buttons_[button] = false; | |||||
did_release_buttons_[button] = true; | |||||
mousePos_ = { x, y }; | |||||
pressedButtons_[button] = false; | |||||
didReleaseButtons_[button] = true; | |||||
} | } | ||||
void onScrollWheel(Sint32 y) { | void onScrollWheel(Sint32 y) { | ||||
did_scroll_ = (y > 0 ? 1 : -1 ); | |||||
didScroll_ = (y > 0 ? 1 : -1 ); | |||||
} | } | ||||
bool isKeyPressed(SDL_Scancode code) { return pressed_keys_[code]; } | |||||
bool wasKeyPressed(SDL_Scancode code) { return did_press_keys_[code]; } | |||||
bool wasKeyReleased(SDL_Scancode code) { return did_release_keys_[code]; } | |||||
Vec2i getMousePos() { return mouse_pos_; } | |||||
bool isMousePressed(Uint8 button) { return pressed_buttons_[button]; } | |||||
bool wasMousePressed(Uint8 button) { return did_press_buttons_[button]; } | |||||
bool wasMouseReleased(Uint8 button) { return did_release_buttons_[button]; } | |||||
int wasWheelScrolled() { return did_scroll_; } | |||||
bool isKeyPressed(SDL_Scancode code) { return pressedKeys_[code]; } | |||||
bool wasKeyPressed(SDL_Scancode code) { return didPressKeys_[code]; } | |||||
bool wasKeyReleased(SDL_Scancode code) { return didReleaseKeys_[code]; } | |||||
Vec2i getMousePos() { return mousePos_; } | |||||
bool isMousePressed(Uint8 button) { return pressedButtons_[button]; } | |||||
bool wasMousePressed(Uint8 button) { return didPressButtons_[button]; } | |||||
bool wasMouseReleased(Uint8 button) { return didReleaseButtons_[button]; } | |||||
int wasWheelScrolled() { return didScroll_; } | |||||
TilePos getMouseTile(); | TilePos getMouseTile(); | ||||
void tick(float dt); | void tick(float dt); | ||||
std::unique_ptr<World> world_ = NULL; | std::unique_ptr<World> world_ = NULL; | ||||
std::unique_ptr<ImageResource> invalid_image_ = NULL; | |||||
std::unique_ptr<Tile> invalid_tile_ = NULL; | |||||
std::unique_ptr<Item> invalid_item_ = NULL; | |||||
std::unique_ptr<ImageResource> invalidImage_ = NULL; | |||||
std::unique_ptr<Tile> invalidTile_ = NULL; | |||||
std::unique_ptr<Item> invalidItem_ = NULL; | |||||
Win &win_; | Win &win_; | ||||
private: | private: | ||||
std::bitset<SDL_NUM_SCANCODES> pressed_keys_; | |||||
std::bitset<SDL_NUM_SCANCODES> did_press_keys_; | |||||
std::bitset<SDL_NUM_SCANCODES> did_release_keys_; | |||||
std::bitset<SDL_NUM_SCANCODES> pressedKeys_; | |||||
std::bitset<SDL_NUM_SCANCODES> didPressKeys_; | |||||
std::bitset<SDL_NUM_SCANCODES> didReleaseKeys_; | |||||
Vec2i mouse_pos_; | |||||
std::bitset<SDL_BUTTON_X2> pressed_buttons_; | |||||
std::bitset<SDL_BUTTON_X2> did_press_buttons_; | |||||
std::bitset<SDL_BUTTON_X2> did_release_buttons_; | |||||
Vec2i mousePos_; | |||||
std::bitset<SDL_BUTTON_X2> pressedButtons_; | |||||
std::bitset<SDL_BUTTON_X2> didPressButtons_; | |||||
std::bitset<SDL_BUTTON_X2> didReleaseButtons_; | |||||
int did_scroll_ = 0; | |||||
int didScroll_ = 0; | |||||
}; | }; | ||||
} | } |
struct Builder { | struct Builder { | ||||
std::string name; | std::string name; | ||||
std::string image; | std::string image; | ||||
int max_stack = 64; | |||||
int maxStack = 64; | |||||
}; | }; | ||||
Item(const ResourceManager &resources, const Builder &builder): | Item(const ResourceManager &resources, const Builder &builder): | ||||
name_(builder.name), image_(resources.getImage(builder.image)), | name_(builder.name), image_(resources.getImage(builder.image)), | ||||
max_stack_(builder.max_stack) {} | |||||
maxStack_(builder.maxStack) {} | |||||
const std::string name_; | const std::string name_; | ||||
const ImageResource &image_; | const ImageResource &image_; | ||||
const int max_stack_; | |||||
const int maxStack_; | |||||
static std::unique_ptr<Item> createInvalid(Context &ctx); | static std::unique_ptr<Item> createInvalid(Context &ctx); | ||||
protected: | protected: | ||||
Item(const Builder &builder): | Item(const Builder &builder): | ||||
name_(builder.name), image_(*(ImageResource *)this), | name_(builder.name), image_(*(ImageResource *)this), | ||||
max_stack_(builder.max_stack) {} | |||||
maxStack_(builder.maxStack) {} | |||||
}; | }; | ||||
} | } |
struct NewLightChunk { | struct NewLightChunk { | ||||
std::bitset<CHUNK_WIDTH * CHUNK_HEIGHT> blocks; | std::bitset<CHUNK_WIDTH * CHUNK_HEIGHT> blocks; | ||||
std::map<std::pair<int, int>, float> light_sources; | |||||
std::map<std::pair<int, int>, float> lightSources; | |||||
}; | }; | ||||
struct LightChunk { | struct LightChunk { | ||||
LightChunk(NewLightChunk &&ch); | LightChunk(NewLightChunk &&ch); | ||||
std::bitset<CHUNK_WIDTH * CHUNK_HEIGHT> blocks; | std::bitset<CHUNK_WIDTH * CHUNK_HEIGHT> blocks; | ||||
uint8_t light_levels[CHUNK_WIDTH * CHUNK_HEIGHT] = { 0 }; | |||||
float light_buffers[CHUNK_WIDTH * CHUNK_HEIGHT * 2] = { 0 }; | |||||
uint8_t lightLevels[CHUNK_WIDTH * CHUNK_HEIGHT] = { 0 }; | |||||
float lightBuffers[CHUNK_WIDTH * CHUNK_HEIGHT * 2] = { 0 }; | |||||
int buffer = 0; | int buffer = 0; | ||||
uint8_t blocks_line[CHUNK_WIDTH] = { 0 }; | |||||
std::map<std::pair<int, int>, float> light_sources; | |||||
uint8_t blocksLine[CHUNK_WIDTH] = { 0 }; | |||||
std::map<std::pair<int, int>, float> lightSources; | |||||
std::vector<std::pair<TilePos, float>> bounces; | std::vector<std::pair<TilePos, float>> bounces; | ||||
float *light_buffer() { return light_buffers + CHUNK_WIDTH * CHUNK_HEIGHT * buffer; } | |||||
float *lightBuffer() { return lightBuffers + CHUNK_WIDTH * CHUNK_HEIGHT * buffer; } | |||||
bool was_updated = false; | |||||
bool wasUpdated = false; | |||||
}; | }; | ||||
class LightCallback { | class LightCallback { | ||||
LightCallback &cb_; | LightCallback &cb_; | ||||
bool running_ = true; | bool running_ = true; | ||||
std::map<std::pair<int, int>, LightChunk> chunks_; | std::map<std::pair<int, int>, LightChunk> chunks_; | ||||
std::set<std::pair<int, int>> updated_chunks_; | |||||
LightChunk *cached_chunk_ = nullptr; | |||||
Vec2i cached_chunk_pos_; | |||||
std::set<std::pair<int, int>> updatedChunks_; | |||||
LightChunk *cachedChunk_ = nullptr; | |||||
Vec2i cachedChunkPos_; | |||||
int buffer_ = 0; | int buffer_ = 0; | ||||
std::vector<Event> buffers_[2] = { {}, {} }; | std::vector<Event> buffers_[2] = { {}, {} }; | ||||
std::vector<NewLightChunk> new_chunk_buffers_[2] = { {}, {} }; | |||||
std::vector<NewLightChunk> newChunkBuffers_[2] = { {}, {} }; | |||||
std::thread thread_; | std::thread thread_; | ||||
std::condition_variable cond_; | std::condition_variable cond_; | ||||
std::mutex mut_; | std::mutex mut_; | ||||
inline void LightServer::onChunkAdded(Vec2i pos, NewLightChunk &&chunk) { | inline void LightServer::onChunkAdded(Vec2i pos, NewLightChunk &&chunk) { | ||||
std::lock_guard<std::mutex> lock(mut_); | std::lock_guard<std::mutex> lock(mut_); | ||||
buffers_[buffer_].push_back({ Event::Tag::CHUNK_ADDED, pos, | buffers_[buffer_].push_back({ Event::Tag::CHUNK_ADDED, pos, | ||||
{ .i = (int)new_chunk_buffers_[buffer_].size() } }); | |||||
new_chunk_buffers_[buffer_].push_back(std::move(chunk)); | |||||
{ .i = (int)newChunkBuffers_[buffer_].size() } }); | |||||
newChunkBuffers_[buffer_].push_back(std::move(chunk)); | |||||
cond_.notify_one(); | cond_.notify_one(); | ||||
} | } | ||||
struct Builder { | struct Builder { | ||||
std::string name; | std::string name; | ||||
std::string image; | std::string image; | ||||
bool is_solid = true; | |||||
float light_level = 0; | |||||
std::optional<std::string> dropped_item = std::nullopt; | |||||
bool isSolid = true; | |||||
float lightLevel = 0; | |||||
std::optional<std::string> droppedItem = std::nullopt; | |||||
}; | }; | ||||
Tile(const ResourceManager &resources, const Builder &builder): | Tile(const ResourceManager &resources, const Builder &builder): | ||||
name_(builder.name), image_(resources.getImage(builder.image)), | name_(builder.name), image_(resources.getImage(builder.image)), | ||||
is_solid_(builder.is_solid), light_level_(builder.light_level), | |||||
dropped_item_(builder.dropped_item) {} | |||||
isSolid_(builder.isSolid), lightLevel_(builder.lightLevel), | |||||
droppedItem_(builder.droppedItem) {} | |||||
const std::string name_; | const std::string name_; | ||||
const ImageResource &image_; | const ImageResource &image_; | ||||
const bool is_solid_; | |||||
const float light_level_; | |||||
const std::optional<std::string> dropped_item_; | |||||
const bool isSolid_; | |||||
const float lightLevel_; | |||||
const std::optional<std::string> droppedItem_; | |||||
static std::unique_ptr<Tile> createInvalid(const ResourceManager &ctx); | static std::unique_ptr<Tile> createInvalid(const ResourceManager &ctx); | ||||
static std::unique_ptr<Tile> createAir(const ResourceManager &ctx); | static std::unique_ptr<Tile> createAir(const ResourceManager &ctx); |
void setCurrentPlane(WorldPlane &plane); | void setCurrentPlane(WorldPlane &plane); | ||||
WorldPlane &addPlane(const std::string &gen); | WorldPlane &addPlane(const std::string &gen); | ||||
WorldPlane &addPlane() { return addPlane(default_world_gen_); } | |||||
WorldPlane &addPlane() { return addPlane(defaultWorldGen_); } | |||||
Tile &getTileByID(Tile::ID id) { return *tiles_[id]; } | Tile &getTileByID(Tile::ID id) { return *tiles_[id]; } | ||||
Tile::ID getTileID(const std::string &name); | Tile::ID getTileID(const std::string &name); | ||||
// Event emitters | // Event emitters | ||||
EventEmitter<const Context &, TilePos, Tile &> | EventEmitter<const Context &, TilePos, Tile &> | ||||
evt_tile_break_; | |||||
evtTileBreak_; | |||||
// World owns all mods | // World owns all mods | ||||
std::vector<ModWrapper> mods_; | std::vector<ModWrapper> mods_; | ||||
// World owns tiles and items, the mod just has Builder objects | // World owns tiles and items, the mod just has Builder objects | ||||
std::vector<std::unique_ptr<Tile>> tiles_; | std::vector<std::unique_ptr<Tile>> tiles_; | ||||
std::unordered_map<std::string, Tile::ID> tiles_map_; | |||||
std::unordered_map<std::string, Tile::ID> tilesMap_; | |||||
std::unordered_map<std::string, std::unique_ptr<Item>> items_; | std::unordered_map<std::string, std::unique_ptr<Item>> items_; | ||||
// Mods give us factories to create new world gens and new entity collections | // Mods give us factories to create new world gens and new entity collections | ||||
std::unordered_map<std::string, WorldGen::Factory> worldgen_factories_; | |||||
std::vector<EntityCollection::Factory> ent_coll_factories_; | |||||
std::unordered_map<std::string, WorldGen::Factory> worldgenFactories_; | |||||
std::vector<EntityCollection::Factory> entCollFactories_; | |||||
BodyTrait::Body *player_; | BodyTrait::Body *player_; | ||||
Game *game_; | Game *game_; | ||||
void tick(WorldPlane &plane, ChunkPos abspos); | void tick(WorldPlane &plane, ChunkPos abspos); | ||||
}; | }; | ||||
ChunkRenderer chunk_renderer_; | |||||
WorldPlane::ID current_plane_; | |||||
ChunkRenderer chunkRenderer_; | |||||
WorldPlane::ID currentPlane_; | |||||
std::vector<std::unique_ptr<WorldPlane>> planes_; | std::vector<std::unique_ptr<WorldPlane>> planes_; | ||||
std::string default_world_gen_; | |||||
std::string defaultWorldGen_; | |||||
}; | }; | ||||
} | } |
std::unique_ptr<LightServer> lighting_; | std::unique_ptr<LightServer> lighting_; | ||||
std::map<std::pair<int, int>, Chunk> chunks_; | std::map<std::pair<int, int>, Chunk> chunks_; | ||||
std::vector<Chunk *> active_chunks_; | |||||
std::vector<std::pair<ChunkPos, Chunk *>> tick_chunks_; | |||||
std::vector<std::unique_ptr<EntityCollection>> ent_colls_; | |||||
std::unordered_map<std::type_index, EntityCollection *> ent_colls_by_type_; | |||||
std::unordered_map<std::string, EntityCollection *> ent_colls_by_name_; | |||||
std::deque<Chunk *> chunk_init_list_; | |||||
std::vector<TilePos> debug_boxes_; | |||||
std::vector<Chunk *> activeChunks_; | |||||
std::vector<std::pair<ChunkPos, Chunk *>> tickChunks_; | |||||
std::vector<std::unique_ptr<EntityCollection>> entColls_; | |||||
std::unordered_map<std::type_index, EntityCollection *> entCollsByType_; | |||||
std::unordered_map<std::string, EntityCollection *> entCollsByName_; | |||||
std::deque<Chunk *> chunkInitList_; | |||||
std::vector<TilePos> debugBoxes_; | |||||
}; | }; | ||||
/* | /* | ||||
template<typename Ent> | template<typename Ent> | ||||
inline EntityCollection &WorldPlane::getCollectionOf() { | inline EntityCollection &WorldPlane::getCollectionOf() { | ||||
return *ent_colls_by_type_.at(typeid(Ent)); | |||||
return *entCollsByType_.at(typeid(Ent)); | |||||
} | } | ||||
inline EntityCollection &WorldPlane::getCollectionOf(std::string name) { | inline EntityCollection &WorldPlane::getCollectionOf(std::string name) { | ||||
return *ent_colls_by_name_.at(name); | |||||
return *entCollsByName_.at(name); | |||||
} | } | ||||
inline EntityCollection &WorldPlane::getCollectionOf(std::type_index type) { | inline EntityCollection &WorldPlane::getCollectionOf(std::type_index type) { | ||||
return *ent_colls_by_type_.at(type); | |||||
return *entCollsByType_.at(type); | |||||
} | } | ||||
} | } |
class RenderTarget: NonCopyable { | class RenderTarget: NonCopyable { | ||||
public: | public: | ||||
RenderTarget(SDL_Renderer *rnd, SDL_Texture *tex): rnd_(rnd) { | RenderTarget(SDL_Renderer *rnd, SDL_Texture *tex): rnd_(rnd) { | ||||
prev_target_ = SDL_GetRenderTarget(rnd_); | |||||
prevTarget_ = SDL_GetRenderTarget(rnd_); | |||||
SDL_SetRenderTarget(rnd_, tex); | SDL_SetRenderTarget(rnd_, tex); | ||||
} | } | ||||
~RenderTarget() { | ~RenderTarget() { | ||||
SDL_SetRenderTarget(rnd_, prev_target_); | |||||
SDL_SetRenderTarget(rnd_, prevTarget_); | |||||
} | } | ||||
private: | private: | ||||
SDL_Renderer *rnd_; | SDL_Renderer *rnd_; | ||||
SDL_Texture *prev_target_; | |||||
SDL_Texture *prevTarget_; | |||||
}; | }; | ||||
class TexLock: NonCopyable { | class TexLock: NonCopyable { |
}; | }; | ||||
Logger(std::ostream &os, std::string name, bool use_color = false, std::string color = ""): | Logger(std::ostream &os, std::string name, bool use_color = false, std::string color = ""): | ||||
os_(os), name_(std::move(name)), use_color_(use_color), color_(std::move(color)) {} | |||||
os_(os), name_(std::move(name)), useColor_(use_color), color_(std::move(color)) {} | |||||
template<typename T> | template<typename T> | ||||
NewlineStream operator<<(const T &val) { | NewlineStream operator<<(const T &val) { | ||||
if (use_color_) | |||||
if (useColor_) | |||||
os_ << color_ << name_ << "\033[0m: " << val; | os_ << color_ << name_ << "\033[0m: " << val; | ||||
else | else | ||||
os_ << name_ << ": " << val; | os_ << name_ << ": " << val; | ||||
private: | private: | ||||
std::ostream &os_; | std::ostream &os_; | ||||
std::string name_; | std::string name_; | ||||
bool use_color_; | |||||
bool useColor_; | |||||
std::string color_; | std::string color_; | ||||
}; | }; | ||||
struct Physics { | struct Physics { | ||||
Vec2 vel{}; | Vec2 vel{}; | ||||
Vec2 force{}; | Vec2 force{}; | ||||
bool on_ground = false; | |||||
bool onGround = false; | |||||
void friction(Vec2 coef = Vec2(400, 50)); | void friction(Vec2 coef = Vec2(400, 50)); | ||||
void gravity(float mass, Vec2 g = Vec2(0, 20)); | void gravity(float mass, Vec2 g = Vec2(0, 20)); |
memcpy(data_.get(), dest, destlen); | memcpy(data_.get(), dest, destlen); | ||||
texture_.reset(); | texture_.reset(); | ||||
compressed_size_ = destlen; | |||||
compressedSize_ = destlen; | |||||
info | info | ||||
<< "Compressed chunk " << pos_ << " from " | << "Compressed chunk " << pos_ << " from " | ||||
uLongf destlen = DATA_SIZE; | uLongf destlen = DATA_SIZE; | ||||
int ret = uncompress( | int ret = uncompress( | ||||
dest.get(), &destlen, | dest.get(), &destlen, | ||||
(Bytef *)data_.get(), compressed_size_); | |||||
(Bytef *)data_.get(), compressedSize_); | |||||
if (ret != Z_OK) { | if (ret != Z_OK) { | ||||
panic << "Decompressing chunk failed: " << ret; | panic << "Decompressing chunk failed: " << ret; | ||||
} | } | ||||
data_ = std::move(dest); | data_ = std::move(dest); | ||||
need_render_ = true; | |||||
needRender_ = true; | |||||
info | info | ||||
<< "Decompressed chunk " << pos_ << " from " | << "Decompressed chunk " << pos_ << " from " | ||||
<< compressed_size_ << " bytes to " | |||||
<< compressedSize_ << " bytes to " | |||||
<< DATA_SIZE << " bytes."; | << DATA_SIZE << " bytes."; | ||||
compressed_size_ = -1; | |||||
compressedSize_ = -1; | |||||
} | } | ||||
void Chunk::renderLight(const Context &ctx, SDL_Renderer *rnd) { | void Chunk::renderLight(const Context &ctx, SDL_Renderer *rnd) { | ||||
std::optional<RenderTarget> target; | std::optional<RenderTarget> target; | ||||
// The texture might not be created yet | // The texture might not be created yet | ||||
if (!light_texture_) { | |||||
light_texture_.reset(SDL_CreateTexture( | |||||
if (!lightTexture_) { | |||||
lightTexture_.reset(SDL_CreateTexture( | |||||
ctx.game.win_.renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, | ctx.game.win_.renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, | ||||
CHUNK_WIDTH, CHUNK_HEIGHT)); | CHUNK_WIDTH, CHUNK_HEIGHT)); | ||||
SDL_SetTextureBlendMode(light_texture_.get(), SDL_BLENDMODE_BLEND); | |||||
SDL_SetTextureBlendMode(lightTexture_.get(), SDL_BLENDMODE_BLEND); | |||||
target.emplace(rnd, texture_.get()); | target.emplace(rnd, texture_.get()); | ||||
} else { | } else { | ||||
} | } | ||||
// Fill light texture | // Fill light texture | ||||
target.emplace(rnd, light_texture_.get()); | |||||
target.emplace(rnd, lightTexture_.get()); | |||||
RenderBlendMode mode(rnd, SDL_BLENDMODE_NONE); | RenderBlendMode mode(rnd, SDL_BLENDMODE_NONE); | ||||
RenderDrawColor color(rnd, 0, 0, 0, 0); | RenderDrawColor color(rnd, 0, 0, 0, 0); | ||||
for (int y = 0; y < CHUNK_HEIGHT; ++y) { | for (int y = 0; y < CHUNK_HEIGHT; ++y) { | ||||
} | } | ||||
} | } | ||||
need_light_render_ = false; | |||||
needLightRender_ = false; | |||||
} | } | ||||
void Chunk::render(const Context &ctx, SDL_Renderer *rnd) { | void Chunk::render(const Context &ctx, SDL_Renderer *rnd) { | ||||
// We're caching tiles so we don't have to world.getTileByID() every time | // We're caching tiles so we don't have to world.getTileByID() every time | ||||
Tile::ID prevID = Tile::INVALID_ID; | Tile::ID prevID = Tile::INVALID_ID; | ||||
Tile *tile = ctx.game.invalid_tile_.get(); | |||||
Tile *tile = ctx.game.invalidTile_.get(); | |||||
// Fill tile texture | // Fill tile texture | ||||
for (int y = 0; y < CHUNK_HEIGHT; ++y) { | for (int y = 0; y < CHUNK_HEIGHT; ++y) { | ||||
} | } | ||||
} | } | ||||
need_render_ = false; | |||||
needRender_ = false; | |||||
renderLight(ctx, rnd); | renderLight(ctx, rnd); | ||||
} | } | ||||
// When we FillRect, we must fill transparency. | // When we FillRect, we must fill transparency. | ||||
RenderDrawColor color(rnd, 0, 0, 0, 0); | RenderDrawColor color(rnd, 0, 0, 0, 0); | ||||
for (auto &[pos, tex]: draw_list_) { | |||||
for (auto &[pos, tex]: drawList_) { | |||||
SDL_Rect dest{pos.x * TILE_SIZE, pos.y * TILE_SIZE, TILE_SIZE, TILE_SIZE}; | SDL_Rect dest{pos.x * TILE_SIZE, pos.y * TILE_SIZE, TILE_SIZE, TILE_SIZE}; | ||||
SDL_RenderFillRect(rnd, &dest); | SDL_RenderFillRect(rnd, &dest); | ||||
SDL_RenderCopy(rnd, tex, nullptr, &dest); | SDL_RenderCopy(rnd, tex, nullptr, &dest); | ||||
return; | return; | ||||
// The world plane is responsible for managing initial renders | // The world plane is responsible for managing initial renders | ||||
if (need_render_) | |||||
if (needRender_) | |||||
return; | return; | ||||
// We're responsible for the light level rendering though | // We're responsible for the light level rendering though | ||||
if (need_light_render_) | |||||
if (needLightRender_) | |||||
renderLight(ctx, win.renderer_); | renderLight(ctx, win.renderer_); | ||||
if (draw_list_.size() > 0) { | |||||
if (drawList_.size() > 0) { | |||||
renderList(win.renderer_); | renderList(win.renderer_); | ||||
draw_list_.clear(); | |||||
drawList_.clear(); | |||||
} | } | ||||
auto chunkpos = pos_ * Vec2i(CHUNK_WIDTH, CHUNK_HEIGHT); | auto chunkpos = pos_ * Vec2i(CHUNK_WIDTH, CHUNK_HEIGHT); | ||||
win.showTexture(chunkpos, texture_.get(), &rect); | win.showTexture(chunkpos, texture_.get(), &rect); | ||||
SDL_Rect texrect{ 0, 0, CHUNK_WIDTH, CHUNK_HEIGHT }; | SDL_Rect texrect{ 0, 0, CHUNK_WIDTH, CHUNK_HEIGHT }; | ||||
win.showTexture(chunkpos, light_texture_.get(), &texrect, &rect); | |||||
win.showTexture(chunkpos, lightTexture_.get(), &texrect, &rect); | |||||
} | } | ||||
Chunk::TickAction Chunk::tick(float dt) { | Chunk::TickAction Chunk::tick(float dt) { | ||||
assert(isActive()); | assert(isActive()); | ||||
deactivate_timer_ -= dt; | |||||
if (deactivate_timer_ <= 0) { | |||||
if (is_modified_) | |||||
deactivateTimer_ -= dt; | |||||
if (deactivateTimer_ <= 0) { | |||||
if (isModified_) | |||||
return TickAction::DEACTIVATE; | return TickAction::DEACTIVATE; | ||||
else | else | ||||
return TickAction::DELETE; | return TickAction::DELETE; | ||||
} | } | ||||
void Chunk::keepActive() { | void Chunk::keepActive() { | ||||
deactivate_timer_ = DEACTIVATE_INTERVAL; | |||||
deactivateTimer_ = DEACTIVATE_INTERVAL; | |||||
decompress(); | decompress(); | ||||
} | } | ||||
else if (win_.zoom_ < 0.3) | else if (win_.zoom_ < 0.3) | ||||
win_.zoom_ = 0.3; | win_.zoom_ = 0.3; | ||||
did_scroll_ = 0; | |||||
did_press_keys_.reset(); | |||||
did_release_keys_.reset(); | |||||
did_press_buttons_.reset(); | |||||
did_release_buttons_.reset(); | |||||
didScroll_ = 0; | |||||
didPressKeys_.reset(); | |||||
didReleaseKeys_.reset(); | |||||
didPressButtons_.reset(); | |||||
didReleaseButtons_.reset(); | |||||
} | } | ||||
void Game::tick(float dt) { | void Game::tick(float dt) { |
// Merge | // Merge | ||||
count_ += st.count_; | count_ += st.count_; | ||||
if (count_ > item_->max_stack_) { | |||||
st.count_ = count_ - item_->max_stack_; | |||||
count_ = item_->max_stack_; | |||||
if (count_ > item_->maxStack_) { | |||||
st.count_ = count_ - item_->maxStack_; | |||||
count_ = item_->maxStack_; | |||||
} else { | } else { | ||||
st.count_ = 0; | st.count_ = 0; | ||||
st.item_ = nullptr; | st.item_ = nullptr; |
} | } | ||||
LightChunk::LightChunk(NewLightChunk &&ch): | LightChunk::LightChunk(NewLightChunk &&ch): | ||||
blocks(std::move(ch.blocks)), light_sources(std::move(ch.light_sources)) { | |||||
blocks(std::move(ch.blocks)), lightSources(std::move(ch.lightSources)) { | |||||
for (int y = 0; y < CHUNK_HEIGHT; ++y) { | for (int y = 0; y < CHUNK_HEIGHT; ++y) { | ||||
for (int x = 0; x < CHUNK_WIDTH; ++x) { | for (int x = 0; x < CHUNK_WIDTH; ++x) { | ||||
if (blocks[y * CHUNK_WIDTH + x]) { | if (blocks[y * CHUNK_WIDTH + x]) { | ||||
blocks_line[x] += 1; | |||||
blocksLine[x] += 1; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
LightChunk *LightServer::getChunk(ChunkPos cpos) { | LightChunk *LightServer::getChunk(ChunkPos cpos) { | ||||
if (cached_chunk_ && cached_chunk_pos_ == cpos) { | |||||
return cached_chunk_; | |||||
if (cachedChunk_ && cachedChunkPos_ == cpos) { | |||||
return cachedChunk_; | |||||
} | } | ||||
auto it = chunks_.find(cpos); | auto it = chunks_.find(cpos); | ||||
if (it != chunks_.end()) { | if (it != chunks_.end()) { | ||||
cached_chunk_ = &it->second; | |||||
cached_chunk_pos_ = cpos; | |||||
cachedChunk_ = &it->second; | |||||
cachedChunkPos_ = cpos; | |||||
return &it->second; | return &it->second; | ||||
} | } | ||||
auto markAdjacentChunksModified = [&](ChunkPos cpos) { | auto markAdjacentChunksModified = [&](ChunkPos cpos) { | ||||
for (int y = -1; y <= 1; ++y) { | for (int y = -1; y <= 1; ++y) { | ||||
for (int x = -1; x <= 1; ++x) { | for (int x = -1; x <= 1; ++x) { | ||||
updated_chunks_.insert(cpos + Vec2i(x, y)); | |||||
updatedChunks_.insert(cpos + Vec2i(x, y)); | |||||
} | } | ||||
} | } | ||||
}; | }; | ||||
auto markChunks = [&](ChunkPos cpos, Vec2i rpos, bool l, bool r, bool t, bool b) { | auto markChunks = [&](ChunkPos cpos, Vec2i rpos, bool l, bool r, bool t, bool b) { | ||||
updated_chunks_.insert(cpos); | |||||
if (l) updated_chunks_.insert(cpos + Vec2i(-1, 0)); | |||||
if (r) updated_chunks_.insert(cpos + Vec2i(1, 0)); | |||||
if (t) updated_chunks_.insert(cpos + Vec2i(0, -1)); | |||||
if (b) updated_chunks_.insert(cpos + Vec2i(0, 1)); | |||||
if (l && t) updated_chunks_.insert(cpos + Vec2i(-1, -1)); | |||||
if (r && t) updated_chunks_.insert(cpos + Vec2i(1, -1)); | |||||
if (l && b) updated_chunks_.insert(cpos + Vec2i(-1, 1)); | |||||
if (r && b) updated_chunks_.insert(cpos + Vec2i(1, 1)); | |||||
updatedChunks_.insert(cpos); | |||||
if (l) updatedChunks_.insert(cpos + Vec2i(-1, 0)); | |||||
if (r) updatedChunks_.insert(cpos + Vec2i(1, 0)); | |||||
if (t) updatedChunks_.insert(cpos + Vec2i(0, -1)); | |||||
if (b) updatedChunks_.insert(cpos + Vec2i(0, 1)); | |||||
if (l && t) updatedChunks_.insert(cpos + Vec2i(-1, -1)); | |||||
if (r && t) updatedChunks_.insert(cpos + Vec2i(1, -1)); | |||||
if (l && b) updatedChunks_.insert(cpos + Vec2i(-1, 1)); | |||||
if (r && b) updatedChunks_.insert(cpos + Vec2i(1, 1)); | |||||
}; | }; | ||||
auto markChunksModified = [&](ChunkPos cpos, Vec2i rpos, float light) { | auto markChunksModified = [&](ChunkPos cpos, Vec2i rpos, float light) { | ||||
switch (evt.tag) { | switch (evt.tag) { | ||||
case Event::Tag::BLOCK_ADDED: | case Event::Tag::BLOCK_ADDED: | ||||
ch->blocks.set(rpos.y * CHUNK_WIDTH + rpos.x, true); | ch->blocks.set(rpos.y * CHUNK_WIDTH + rpos.x, true); | ||||
ch->blocks_line[rpos.x] += 1; | |||||
ch->blocksLine[rpos.x] += 1; | |||||
markChunksModifiedRange(cpos, rpos, LIGHT_CUTOFF_DIST); | markChunksModifiedRange(cpos, rpos, LIGHT_CUTOFF_DIST); | ||||
break; | break; | ||||
case Event::Tag::BLOCK_REMOVED: | case Event::Tag::BLOCK_REMOVED: | ||||
ch->blocks.set(rpos.y * CHUNK_WIDTH + rpos.x, false); | ch->blocks.set(rpos.y * CHUNK_WIDTH + rpos.x, false); | ||||
ch->blocks_line[rpos.x] -= 1; | |||||
ch->blocksLine[rpos.x] -= 1; | |||||
markChunksModifiedRange(cpos, rpos, LIGHT_CUTOFF_DIST); | markChunksModifiedRange(cpos, rpos, LIGHT_CUTOFF_DIST); | ||||
break; | break; | ||||
case Event::Tag::LIGHT_ADDED: | case Event::Tag::LIGHT_ADDED: | ||||
info << cpos << ": Add " << evt.f << " light to " << rpos; | info << cpos << ": Add " << evt.f << " light to " << rpos; | ||||
ch->light_sources[rpos] += evt.f; | |||||
markChunksModified(cpos, rpos, ch->light_sources[rpos]); | |||||
ch->lightSources[rpos] += evt.f; | |||||
markChunksModified(cpos, rpos, ch->lightSources[rpos]); | |||||
break; | break; | ||||
case Event::Tag::LIGHT_REMOVED: | case Event::Tag::LIGHT_REMOVED: | ||||
info << cpos << ": Remove " << evt.f << " light to " << rpos; | info << cpos << ": Remove " << evt.f << " light to " << rpos; | ||||
markChunksModified(cpos, rpos, ch->light_sources[rpos]); | |||||
ch->light_sources[rpos] -= evt.f; | |||||
if (ch->light_sources[rpos] < LIGHT_CUTOFF) { | |||||
ch->light_sources.erase(rpos); | |||||
markChunksModified(cpos, rpos, ch->lightSources[rpos]); | |||||
ch->lightSources[rpos] -= evt.f; | |||||
if (ch->lightSources[rpos] < LIGHT_CUTOFF) { | |||||
ch->lightSources.erase(rpos); | |||||
} | } | ||||
break; | break; | ||||
} | } | ||||
for (int rx = 0; rx < CHUNK_WIDTH; ++rx) { | for (int rx = 0; rx < CHUNK_WIDTH; ++rx) { | ||||
bool lit = light > 0 && tc && tc->blocks_line[rx] == 0 && !line[rx]; | |||||
bool lit = light > 0 && tc && tc->blocksLine[rx] == 0 && !line[rx]; | |||||
if (lit) { | if (lit) { | ||||
chunk.light_buffer()[ry * CHUNK_WIDTH + rx] = light; | |||||
chunk.lightBuffer()[ry * CHUNK_WIDTH + rx] = light; | |||||
if (chunk.blocks[ry * CHUNK_WIDTH + rx]) { | if (chunk.blocks[ry * CHUNK_WIDTH + rx]) { | ||||
line[rx] = true; | line[rx] = true; | ||||
} | } | ||||
} else { | } else { | ||||
chunk.light_buffer()[ry * CHUNK_WIDTH + rx] = 0; | |||||
chunk.lightBuffer()[ry * CHUNK_WIDTH + rx] = 0; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
TilePos base = cpos * Vec2i(CHUNK_WIDTH, CHUNK_HEIGHT); | TilePos base = cpos * Vec2i(CHUNK_WIDTH, CHUNK_HEIGHT); | ||||
std::vector<std::pair<TilePos, float>> lights; | std::vector<std::pair<TilePos, float>> lights; | ||||
for (auto &[pos, level]: chunk.light_sources) { | |||||
for (auto &[pos, level]: chunk.lightSources) { | |||||
lights.emplace_back(Vec2i(pos) + base, level); | lights.emplace_back(Vec2i(pos) + base, level); | ||||
} | } | ||||
} | } | ||||
TilePos b = base + Vec2i(dx * CHUNK_WIDTH, dy * CHUNK_HEIGHT); | TilePos b = base + Vec2i(dx * CHUNK_WIDTH, dy * CHUNK_HEIGHT); | ||||
for (auto &[pos, level]: chunk->light_sources) { | |||||
for (auto &[pos, level]: chunk->lightSources) { | |||||
lights.emplace_back(TilePos(pos) + b, level); | lights.emplace_back(TilePos(pos) + b, level); | ||||
} | } | ||||
}; | }; | ||||
for (int y = 0; y < CHUNK_HEIGHT; ++y) { | for (int y = 0; y < CHUNK_HEIGHT; ++y) { | ||||
for (int x = 0; x < CHUNK_WIDTH; ++x) { | for (int x = 0; x < CHUNK_WIDTH; ++x) { | ||||
float light = recalcTile(chunk, cpos, Vec2i(x, y), base, lights); | float light = recalcTile(chunk, cpos, Vec2i(x, y), base, lights); | ||||
chunk.light_buffer()[y * CHUNK_WIDTH + x] += light; | |||||
chunk.lightBuffer()[y * CHUNK_WIDTH + x] += light; | |||||
if (light > 0 && chunk.blocks[y * CHUNK_WIDTH + x]) { | if (light > 0 && chunk.blocks[y * CHUNK_WIDTH + x]) { | ||||
chunk.bounces.emplace_back(base + Vec2i(x, y), light * 0.1); | chunk.bounces.emplace_back(base + Vec2i(x, y), light * 0.1); | ||||
for (int y = 0; y < CHUNK_HEIGHT; ++y) { | for (int y = 0; y < CHUNK_HEIGHT; ++y) { | ||||
for (int x = 0; x < CHUNK_WIDTH; ++x) { | for (int x = 0; x < CHUNK_WIDTH; ++x) { | ||||
float light = recalcTile(chunk, cpos, Vec2i(x, y), base, lights); | float light = recalcTile(chunk, cpos, Vec2i(x, y), base, lights); | ||||
float sum = chunk.light_buffer()[y * CHUNK_WIDTH + x] + light; | |||||
chunk.light_buffer()[y * CHUNK_WIDTH + x] = sum; | |||||
float sum = chunk.lightBuffer()[y * CHUNK_WIDTH + x] + light; | |||||
chunk.lightBuffer()[y * CHUNK_WIDTH + x] = sum; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
LightChunk *rc = getChunk(cpos + Vec2i(1, 0)); | LightChunk *rc = getChunk(cpos + Vec2i(1, 0)); | ||||
auto getLight = [&](LightChunk &chunk, int x, int y) { | auto getLight = [&](LightChunk &chunk, int x, int y) { | ||||
return chunk.light_buffer()[y * CHUNK_WIDTH + x]; | |||||
return chunk.lightBuffer()[y * CHUNK_WIDTH + x]; | |||||
}; | }; | ||||
auto calc = [&](int x1, int x2, int y1, int y2, auto tf, auto bf, auto lf, auto rf) { | auto calc = [&](int x1, int x2, int y1, int y2, auto tf, auto bf, auto lf, auto rf) { | ||||
float *dest = chunk.light_buffers + CHUNK_WIDTH * CHUNK_HEIGHT * ((chunk.buffer + 1) % 2); | |||||
float *dest = chunk.lightBuffers + CHUNK_WIDTH * CHUNK_HEIGHT * ((chunk.buffer + 1) % 2); | |||||
for (int y = y1; y < y2; ++y) { | for (int y = y1; y < y2; ++y) { | ||||
for (int x = x1; x < x2; ++x) { | for (int x = x1; x < x2; ++x) { | ||||
float t = tf(x, y); | float t = tf(x, y); | ||||
float b = bf(x, y); | float b = bf(x, y); | ||||
float l = lf(x, y); | float l = lf(x, y); | ||||
float r = rf(x, y); | float r = rf(x, y); | ||||
float light = chunk.light_buffer()[y * CHUNK_WIDTH + x]; | |||||
float light = chunk.lightBuffer()[y * CHUNK_WIDTH + x]; | |||||
int count = 1; | int count = 1; | ||||
if (t > light) { light += t; count += 1; } | if (t > light) { light += t; count += 1; } | ||||
if (b > light) { light += b; count += 1; } | if (b > light) { light += b; count += 1; } | ||||
if (tc) { | if (tc) { | ||||
calc(1, CHUNK_WIDTH - 1, 0, 1, | calc(1, CHUNK_WIDTH - 1, 0, 1, | ||||
[&](int x, int y) { return tc->light_buffer()[(CHUNK_HEIGHT - 1) * CHUNK_WIDTH + x]; }, | |||||
[&](int x, int y) { return tc->lightBuffer()[(CHUNK_HEIGHT - 1) * CHUNK_WIDTH + x]; }, | |||||
[&](int x, int y) { return getLight(chunk, x, y + 1); }, | [&](int x, int y) { return getLight(chunk, x, y + 1); }, | ||||
[&](int x, int y) { return getLight(chunk, x - 1, y); }, | [&](int x, int y) { return getLight(chunk, x - 1, y); }, | ||||
[&](int x, int y) { return getLight(chunk, x + 1, y); }); | [&](int x, int y) { return getLight(chunk, x + 1, y); }); | ||||
if (bc) { | if (bc) { | ||||
calc(1, CHUNK_WIDTH - 1, CHUNK_HEIGHT - 1, CHUNK_HEIGHT, | calc(1, CHUNK_WIDTH - 1, CHUNK_HEIGHT - 1, CHUNK_HEIGHT, | ||||
[&](int x, int y) { return getLight(chunk, x, y - 1); }, | [&](int x, int y) { return getLight(chunk, x, y - 1); }, | ||||
[&](int x, int y) { return bc->light_buffer()[x]; }, | |||||
[&](int x, int y) { return bc->lightBuffer()[x]; }, | |||||
[&](int x, int y) { return getLight(chunk, x - 1, y); }, | [&](int x, int y) { return getLight(chunk, x - 1, y); }, | ||||
[&](int x, int y) { return getLight(chunk, x + 1, y); }); | [&](int x, int y) { return getLight(chunk, x + 1, y); }); | ||||
} | } | ||||
calc(0, 1, 1, CHUNK_HEIGHT - 1, | calc(0, 1, 1, CHUNK_HEIGHT - 1, | ||||
[&](int x, int y) { return getLight(chunk, x, y - 1); }, | [&](int x, int y) { return getLight(chunk, x, y - 1); }, | ||||
[&](int x, int y) { return getLight(chunk, x, y + 1); }, | [&](int x, int y) { return getLight(chunk, x, y + 1); }, | ||||
[&](int x, int y) { return lc->light_buffer()[y * CHUNK_WIDTH + CHUNK_WIDTH - 1]; }, | |||||
[&](int x, int y) { return lc->lightBuffer()[y * CHUNK_WIDTH + CHUNK_WIDTH - 1]; }, | |||||
[&](int x, int y) { return getLight(chunk, x + 1, y); }); | [&](int x, int y) { return getLight(chunk, x + 1, y); }); | ||||
} | } | ||||
[&](int x, int y) { return getLight(chunk, x, y - 1); }, | [&](int x, int y) { return getLight(chunk, x, y - 1); }, | ||||
[&](int x, int y) { return getLight(chunk, x, y + 1); }, | [&](int x, int y) { return getLight(chunk, x, y + 1); }, | ||||
[&](int x, int y) { return getLight(chunk, x - 1, y); }, | [&](int x, int y) { return getLight(chunk, x - 1, y); }, | ||||
[&](int x, int y) { return rc->light_buffer()[y * CHUNK_WIDTH]; }); | |||||
[&](int x, int y) { return rc->lightBuffer()[y * CHUNK_WIDTH]; }); | |||||
} | } | ||||
if (tc && lc) { | if (tc && lc) { | ||||
calc(0, 1, 0, 1, | calc(0, 1, 0, 1, | ||||
[&](int x, int y) { return tc->light_buffer()[(CHUNK_HEIGHT - 1) * CHUNK_WIDTH + x]; }, | |||||
[&](int x, int y) { return tc->lightBuffer()[(CHUNK_HEIGHT - 1) * CHUNK_WIDTH + x]; }, | |||||
[&](int x, int y) { return getLight(chunk, x, y + 1); }, | [&](int x, int y) { return getLight(chunk, x, y + 1); }, | ||||
[&](int x, int y) { return lc->light_buffer()[y * CHUNK_WIDTH + CHUNK_WIDTH - 1]; }, | |||||
[&](int x, int y) { return lc->lightBuffer()[y * CHUNK_WIDTH + CHUNK_WIDTH - 1]; }, | |||||
[&](int x, int y) { return getLight(chunk, x + 1, y); }); | [&](int x, int y) { return getLight(chunk, x + 1, y); }); | ||||
} | } | ||||
if (tc && rc) { | if (tc && rc) { | ||||
calc(CHUNK_WIDTH - 1, CHUNK_WIDTH, 0, 1, | calc(CHUNK_WIDTH - 1, CHUNK_WIDTH, 0, 1, | ||||
[&](int x, int y) { return tc->light_buffer()[(CHUNK_HEIGHT - 1) * CHUNK_WIDTH + x]; }, | |||||
[&](int x, int y) { return tc->lightBuffer()[(CHUNK_HEIGHT - 1) * CHUNK_WIDTH + x]; }, | |||||
[&](int x, int y) { return getLight(chunk, x, y + 1); }, | [&](int x, int y) { return getLight(chunk, x, y + 1); }, | ||||
[&](int x, int y) { return getLight(chunk, x - 1, y); }, | [&](int x, int y) { return getLight(chunk, x - 1, y); }, | ||||
[&](int x, int y) { return rc->light_buffer()[y * CHUNK_WIDTH]; }); | |||||
[&](int x, int y) { return rc->lightBuffer()[y * CHUNK_WIDTH]; }); | |||||
} | } | ||||
if (bc && lc) { | if (bc && lc) { | ||||
calc(0, 1, CHUNK_HEIGHT - 1, CHUNK_HEIGHT, | calc(0, 1, CHUNK_HEIGHT - 1, CHUNK_HEIGHT, | ||||
[&](int x, int y) { return getLight(chunk, x, y - 1); }, | [&](int x, int y) { return getLight(chunk, x, y - 1); }, | ||||
[&](int x, int y) { return bc->light_buffer()[x]; }, | |||||
[&](int x, int y) { return lc->light_buffer()[y * CHUNK_WIDTH + CHUNK_WIDTH - 1]; }, | |||||
[&](int x, int y) { return bc->lightBuffer()[x]; }, | |||||
[&](int x, int y) { return lc->lightBuffer()[y * CHUNK_WIDTH + CHUNK_WIDTH - 1]; }, | |||||
[&](int x, int y) { return getLight(chunk, x + 1, y); }); | [&](int x, int y) { return getLight(chunk, x + 1, y); }); | ||||
} | } | ||||
if (bc && rc) { | if (bc && rc) { | ||||
calc(CHUNK_WIDTH - 1, CHUNK_WIDTH, CHUNK_HEIGHT - 1, CHUNK_HEIGHT, | calc(CHUNK_WIDTH - 1, CHUNK_WIDTH, CHUNK_HEIGHT - 1, CHUNK_HEIGHT, | ||||
[&](int x, int y) { return getLight(chunk, x, y - 1); }, | [&](int x, int y) { return getLight(chunk, x, y - 1); }, | ||||
[&](int x, int y) { return bc->light_buffer()[x]; }, | |||||
[&](int x, int y) { return bc->lightBuffer()[x]; }, | |||||
[&](int x, int y) { return getLight(chunk, x - 1, y); }, | [&](int x, int y) { return getLight(chunk, x - 1, y); }, | ||||
[&](int x, int y) { return rc->light_buffer()[y * CHUNK_WIDTH]; }); | |||||
[&](int x, int y) { return rc->lightBuffer()[y * CHUNK_WIDTH]; }); | |||||
} | } | ||||
} | } | ||||
void LightServer::finalizeChunk(LightChunk &chunk) { | void LightServer::finalizeChunk(LightChunk &chunk) { | ||||
for (int y = 0; y < CHUNK_HEIGHT; ++y) { | for (int y = 0; y < CHUNK_HEIGHT; ++y) { | ||||
for (int x = 0; x < CHUNK_WIDTH; ++x) { | for (int x = 0; x < CHUNK_WIDTH; ++x) { | ||||
chunk.light_levels[y * CHUNK_WIDTH + x] = | |||||
linToSRGB(chunk.light_buffer()[y * CHUNK_HEIGHT + x]); | |||||
chunk.lightLevels[y * CHUNK_WIDTH + x] = | |||||
linToSRGB(chunk.lightBuffer()[y * CHUNK_HEIGHT + x]); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
cond_.wait(lock, [&] { return buffers_[buffer_].size() > 0 || !running_; }); | cond_.wait(lock, [&] { return buffers_[buffer_].size() > 0 || !running_; }); | ||||
std::vector<Event> &buf = buffers_[buffer_]; | std::vector<Event> &buf = buffers_[buffer_]; | ||||
std::vector<NewLightChunk> &newChunks = new_chunk_buffers_[buffer_]; | |||||
std::vector<NewLightChunk> &newChunks = newChunkBuffers_[buffer_]; | |||||
buffer_ = (buffer_ + 1) % 2; | buffer_ = (buffer_ + 1) % 2; | ||||
lock.unlock(); | lock.unlock(); | ||||
updated_chunks_.clear(); | |||||
updatedChunks_.clear(); | |||||
for (auto &evt: buf) { | for (auto &evt: buf) { | ||||
processEvent(evt, newChunks); | processEvent(evt, newChunks); | ||||
} | } | ||||
auto start = std::chrono::steady_clock::now(); | auto start = std::chrono::steady_clock::now(); | ||||
for (auto &pos: updated_chunks_) { | |||||
for (auto &pos: updatedChunks_) { | |||||
auto ch = chunks_.find(pos); | auto ch = chunks_.find(pos); | ||||
if (ch != chunks_.end()) { | if (ch != chunks_.end()) { | ||||
processChunkSun(ch->second, ChunkPos(pos.first, pos.second)); | processChunkSun(ch->second, ChunkPos(pos.first, pos.second)); | ||||
} | } | ||||
} | } | ||||
for (auto &pos: updated_chunks_) { | |||||
for (auto &pos: updatedChunks_) { | |||||
auto ch = chunks_.find(pos); | auto ch = chunks_.find(pos); | ||||
if (ch != chunks_.end()) { | if (ch != chunks_.end()) { | ||||
processChunkLights(ch->second, ChunkPos(pos.first, pos.second)); | processChunkLights(ch->second, ChunkPos(pos.first, pos.second)); | ||||
} | } | ||||
} | } | ||||
for (auto &pos: updated_chunks_) { | |||||
for (auto &pos: updatedChunks_) { | |||||
auto ch = chunks_.find(pos); | auto ch = chunks_.find(pos); | ||||
if (ch != chunks_.end()) { | if (ch != chunks_.end()) { | ||||
processChunkBounces(ch->second, ChunkPos(pos.first, pos.second)); | processChunkBounces(ch->second, ChunkPos(pos.first, pos.second)); | ||||
} | } | ||||
for (int i = 0; i < 4; ++i) { | for (int i = 0; i < 4; ++i) { | ||||
for (auto &pos: updated_chunks_) { | |||||
for (auto &pos: updatedChunks_) { | |||||
auto ch = chunks_.find(pos); | auto ch = chunks_.find(pos); | ||||
if (ch != chunks_.end()) { | if (ch != chunks_.end()) { | ||||
processChunkSmoothing(ch->second, ChunkPos(pos.first, pos.second)); | processChunkSmoothing(ch->second, ChunkPos(pos.first, pos.second)); | ||||
} | } | ||||
} | } | ||||
for (auto &pos: updated_chunks_) { | |||||
for (auto &pos: updatedChunks_) { | |||||
auto ch = chunks_.find(pos); | auto ch = chunks_.find(pos); | ||||
if (ch != chunks_.end()) { | if (ch != chunks_.end()) { | ||||
finalizeChunk(ch->second); | finalizeChunk(ch->second); | ||||
auto end = std::chrono::steady_clock::now(); | auto end = std::chrono::steady_clock::now(); | ||||
auto dur = std::chrono::duration<double, std::milli>(end - start); | auto dur = std::chrono::duration<double, std::milli>(end - start); | ||||
info << "Generating light for " << updated_chunks_.size() | |||||
info << "Generating light for " << updatedChunks_.size() | |||||
<< " chunks took " << dur.count() << "ms"; | << " chunks took " << dur.count() << "ms"; | ||||
for (auto &pos: updated_chunks_) { | |||||
for (auto &pos: updatedChunks_) { | |||||
auto ch = chunks_.find(pos); | auto ch = chunks_.find(pos); | ||||
if (ch != chunks_.end()) { | if (ch != chunks_.end()) { | ||||
cb_.onLightChunkUpdated(ch->second, pos); | cb_.onLightChunkUpdated(ch->second, pos); |
return std::make_unique<Tile>(resources, Builder{ | return std::make_unique<Tile>(resources, Builder{ | ||||
.name = "@::air", | .name = "@::air", | ||||
.image = "@::air", | .image = "@::air", | ||||
.is_solid = false, | |||||
.isSolid = false, | |||||
}); | }); | ||||
} | } | ||||
World::World(Game *game, unsigned long rand_seed): | World::World(Game *game, unsigned long rand_seed): | ||||
game_(game), random_(rand_seed), resources_(game->win_) { | game_(game), random_(rand_seed), resources_(game->win_) { | ||||
std::unique_ptr<Tile> invalid_tile = Tile::createInvalid(resources_); | |||||
tiles_map_[invalid_tile->name_] = 0; | |||||
std::unique_ptr<Tile> invalidTile = Tile::createInvalid(resources_); | |||||
tilesMap_[invalidTile->name_] = 0; | |||||
// tiles_ is empty, so pushing back now will ensure invalid_tile | // tiles_ is empty, so pushing back now will ensure invalid_tile | ||||
// ends up at location 0 | // ends up at location 0 | ||||
tiles_.push_back(std::move(invalid_tile)); | |||||
tiles_.push_back(std::move(invalidTile)); | |||||
// We're also going to need an air tile at location 1 | // We're also going to need an air tile at location 1 | ||||
tiles_.push_back(Tile::createAir(resources_)); | tiles_.push_back(Tile::createAir(resources_)); | ||||
tiles_map_["@::air"] = 1; | |||||
tilesMap_["@::air"] = 1; | |||||
} | } | ||||
void World::ChunkRenderer::tick(WorldPlane &plane, ChunkPos abspos) { | void World::ChunkRenderer::tick(WorldPlane &plane, ChunkPos abspos) { | ||||
for (auto t: mod.buildTiles(resources_)) { | for (auto t: mod.buildTiles(resources_)) { | ||||
Tile::ID id = tiles_.size(); | Tile::ID id = tiles_.size(); | ||||
tiles_map_[t->name_] = id; | |||||
tilesMap_[t->name_] = id; | |||||
tiles_.push_back(std::move(t)); | tiles_.push_back(std::move(t)); | ||||
} | } | ||||
} | } | ||||
for (auto fact: mod.getWorldGens()) { | for (auto fact: mod.getWorldGens()) { | ||||
worldgen_factories_.emplace( | |||||
worldgenFactories_.emplace( | |||||
std::piecewise_construct, | std::piecewise_construct, | ||||
std::forward_as_tuple(fact.name), | std::forward_as_tuple(fact.name), | ||||
std::forward_as_tuple(fact)); | std::forward_as_tuple(fact)); | ||||
} | } | ||||
for (auto fact: mod.getEntities()) { | for (auto fact: mod.getEntities()) { | ||||
ent_coll_factories_.push_back(fact); | |||||
entCollFactories_.push_back(fact); | |||||
} | } | ||||
mods_.push_back(std::move(mod)); | mods_.push_back(std::move(mod)); | ||||
} | } | ||||
void World::setWorldGen(std::string gen) { | void World::setWorldGen(std::string gen) { | ||||
default_world_gen_ = std::move(gen); | |||||
defaultWorldGen_ = std::move(gen); | |||||
} | } | ||||
void World::spawnPlayer() { | void World::spawnPlayer() { | ||||
player_ = &((dynamic_cast<BodyTrait *>( | player_ = &((dynamic_cast<BodyTrait *>( | ||||
planes_[current_plane_]->spawnPlayer().get()))->get(BodyTrait::Tag{})); | |||||
planes_[currentPlane_]->spawnPlayer().get()))->get(BodyTrait::Tag{})); | |||||
} | } | ||||
void World::setCurrentPlane(WorldPlane &plane) { | void World::setCurrentPlane(WorldPlane &plane) { | ||||
current_plane_ = plane.id_; | |||||
currentPlane_ = plane.id_; | |||||
} | } | ||||
WorldPlane &World::addPlane(const std::string &gen) { | WorldPlane &World::addPlane(const std::string &gen) { | ||||
WorldPlane::ID id = planes_.size(); | WorldPlane::ID id = planes_.size(); | ||||
auto it = worldgen_factories_.find(gen); | |||||
if (it == worldgen_factories_.end()) { | |||||
auto it = worldgenFactories_.find(gen); | |||||
if (it == worldgenFactories_.end()) { | |||||
panic << "Tried to add plane with non-existant world gen " << gen << "!"; | panic << "Tried to add plane with non-existant world gen " << gen << "!"; | ||||
abort(); | abort(); | ||||
} | } | ||||
std::vector<std::unique_ptr<EntityCollection>> colls; | std::vector<std::unique_ptr<EntityCollection>> colls; | ||||
colls.reserve(ent_coll_factories_.size()); | |||||
for (auto &fact: ent_coll_factories_) { | |||||
colls.reserve(entCollFactories_.size()); | |||||
for (auto &fact: entCollFactories_) { | |||||
colls.emplace_back(fact.create(fact.name)); | colls.emplace_back(fact.create(fact.name)); | ||||
} | } | ||||
auto iter = items_.find(name); | auto iter = items_.find(name); | ||||
if (iter == items_.end()) { | if (iter == items_.end()) { | ||||
warn << "Tried to get non-existant item " << name << "!"; | warn << "Tried to get non-existant item " << name << "!"; | ||||
return *game_->invalid_item_; | |||||
return *game_->invalidItem_; | |||||
} | } | ||||
return *iter->second; | return *iter->second; | ||||
} | } | ||||
Tile::ID World::getTileID(const std::string &name) { | Tile::ID World::getTileID(const std::string &name) { | ||||
auto iter = tiles_map_.find(name); | |||||
if (iter == tiles_map_.end()) { | |||||
auto iter = tilesMap_.find(name); | |||||
if (iter == tilesMap_.end()) { | |||||
warn << "Tried to get non-existant item " << name << "!"; | warn << "Tried to get non-existant item " << name << "!"; | ||||
return Tile::INVALID_ID; | return Tile::INVALID_ID; | ||||
} | } | ||||
} | } | ||||
SDL_Color World::backgroundColor() { | SDL_Color World::backgroundColor() { | ||||
return planes_[current_plane_]->backgroundColor(); | |||||
return planes_[currentPlane_]->backgroundColor(); | |||||
} | } | ||||
void World::draw(Win &win) { | void World::draw(Win &win) { | ||||
ZoneScopedN("World draw"); | ZoneScopedN("World draw"); | ||||
win.cam_ = player_->pos - (win.getSize() / 2) + (player_->size / 2); | win.cam_ = player_->pos - (win.getSize() / 2) + (player_->size / 2); | ||||
planes_[current_plane_]->draw(win); | |||||
planes_[currentPlane_]->draw(win); | |||||
} | } | ||||
void World::update(float dt) { | void World::update(float dt) { | ||||
for (auto &plane: planes_) | for (auto &plane: planes_) | ||||
plane->tick(dt); | plane->tick(dt); | ||||
chunk_renderer_.tick( | |||||
*planes_[current_plane_], | |||||
chunkRenderer_.tick( | |||||
*planes_[currentPlane_], | |||||
ChunkPos((int)player_->pos.x / CHUNK_WIDTH, (int)player_->pos.y / CHUNK_HEIGHT)); | ChunkPos((int)player_->pos.x / CHUNK_WIDTH, (int)player_->pos.y / CHUNK_HEIGHT)); | ||||
} | } | ||||
std::vector<std::unique_ptr<EntityCollection>> &&colls): | std::vector<std::unique_ptr<EntityCollection>> &&colls): | ||||
id_(id), world_(world), gen_(std::move(gen)), | id_(id), world_(world), gen_(std::move(gen)), | ||||
lighting_(std::make_unique<LightServer>(*this)), | lighting_(std::make_unique<LightServer>(*this)), | ||||
ent_colls_(std::move(colls)) { | |||||
entColls_(std::move(colls)) { | |||||
for (auto &coll: ent_colls_) { | |||||
ent_colls_by_type_[coll->type()] = coll.get(); | |||||
ent_colls_by_name_[coll->name()] = coll.get(); | |||||
for (auto &coll: entColls_) { | |||||
entCollsByType_[coll->type()] = coll.get(); | |||||
entCollsByName_[coll->name()] = coll.get(); | |||||
} | } | ||||
} | } | ||||
EntityRef WorldPlane::spawnEntity(const std::string &name, const Entity::PackObject &obj) { | EntityRef WorldPlane::spawnEntity(const std::string &name, const Entity::PackObject &obj) { | ||||
return ent_colls_by_name_.at(name)->spawn(getContext(), obj); | |||||
return entCollsByName_.at(name)->spawn(getContext(), obj); | |||||
} | } | ||||
bool WorldPlane::hasChunk(ChunkPos pos) { | bool WorldPlane::hasChunk(ChunkPos pos) { | ||||
// This function will be a bit weird because it's a really fucking hot function. | // This function will be a bit weird because it's a really fucking hot function. | ||||
Chunk &WorldPlane::getChunk(ChunkPos pos) { | Chunk &WorldPlane::getChunk(ChunkPos pos) { | ||||
// First, look through all chunks which have been in use this tick | // First, look through all chunks which have been in use this tick | ||||
for (auto [chpos, chunk]: tick_chunks_) { | |||||
for (auto [chpos, chunk]: tickChunks_) { | |||||
if (chpos == pos) | if (chpos == pos) | ||||
return *chunk; | return *chunk; | ||||
} | } | ||||
Chunk &chunk = slowGetChunk(pos); | Chunk &chunk = slowGetChunk(pos); | ||||
tick_chunks_.push_back({ pos, &chunk }); | |||||
tickChunks_.push_back({ pos, &chunk }); | |||||
return chunk; | return chunk; | ||||
} | } | ||||
Chunk &chunk = iter->second; | Chunk &chunk = iter->second; | ||||
gen_->genChunk(*this, chunk); | gen_->genChunk(*this, chunk); | ||||
active_chunks_.push_back(&chunk); | |||||
chunk_init_list_.push_back(&chunk); | |||||
activeChunks_.push_back(&chunk); | |||||
chunkInitList_.push_back(&chunk); | |||||
// Need to tell the light engine too | // Need to tell the light engine too | ||||
NewLightChunk lc; | NewLightChunk lc; | ||||
for (int x = 0; x < CHUNK_WIDTH; ++x) { | for (int x = 0; x < CHUNK_WIDTH; ++x) { | ||||
Tile::ID id = chunk.getTileID({ x, y }); | Tile::ID id = chunk.getTileID({ x, y }); | ||||
Tile &tile = world_->getTileByID(id); | Tile &tile = world_->getTileByID(id); | ||||
if (tile.is_solid_) { | |||||
if (tile.isSolid_) { | |||||
lc.blocks[y * CHUNK_HEIGHT + x] = true; | lc.blocks[y * CHUNK_HEIGHT + x] = true; | ||||
} | } | ||||
if (tile.light_level_ > 0) { | |||||
lc.light_sources[{ x, y }] = tile.light_level_; | |||||
if (tile.lightLevel_ > 0) { | |||||
lc.light_sources[{ x, y }] = tile.lightLevel_; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
// Otherwise, it might not be active, so let's activate it | // Otherwise, it might not be active, so let's activate it | ||||
} else if (!iter->second.isActive()) { | } else if (!iter->second.isActive()) { | ||||
iter->second.keepActive(); | iter->second.keepActive(); | ||||
active_chunks_.push_back(&iter->second); | |||||
chunk_init_list_.push_back(&iter->second); | |||||
activeChunks_.push_back(&iter->second); | |||||
chunkInitList_.push_back(&iter->second); | |||||
} | } | ||||
return iter->second; | return iter->second; | ||||
chunk.setTileID(rp, id, newTile.image_.texture_.get()); | chunk.setTileID(rp, id, newTile.image_.texture_.get()); | ||||
chunk.markModified(); | chunk.markModified(); | ||||
if (!oldTile.is_solid_ && newTile.is_solid_) { | |||||
if (!oldTile.isSolid_ && newTile.isSolid_) { | |||||
lighting_->onSolidBlockAdded(pos); | lighting_->onSolidBlockAdded(pos); | ||||
} else if (oldTile.is_solid_ && !newTile.is_solid_) { | |||||
} else if (oldTile.isSolid_ && !newTile.isSolid_) { | |||||
lighting_->onSolidBlockRemoved(pos); | lighting_->onSolidBlockRemoved(pos); | ||||
} | } | ||||
if (newTile.light_level_ != oldTile.light_level_) { | |||||
if (oldTile.light_level_ > 0) { | |||||
removeLight(pos, oldTile.light_level_); | |||||
if (newTile.lightLevel_ != oldTile.lightLevel_) { | |||||
if (oldTile.lightLevel_ > 0) { | |||||
removeLight(pos, oldTile.lightLevel_); | |||||
} | } | ||||
if (newTile.light_level_ > 0) { | |||||
addLight(pos, newTile.light_level_); | |||||
if (newTile.lightLevel_ > 0) { | |||||
addLight(pos, newTile.lightLevel_); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
// Change tile to air and emit event | // Change tile to air and emit event | ||||
setTileID(pos, air); | setTileID(pos, air); | ||||
world_->evt_tile_break_.emit(getContext(), pos, world_->getTileByID(id)); | |||||
world_->evtTileBreak_.emit(getContext(), pos, world_->getTileByID(id)); | |||||
} | } | ||||
SDL_Color WorldPlane::backgroundColor() { | SDL_Color WorldPlane::backgroundColor() { | ||||
(int)floor(pbody.pos.y / CHUNK_HEIGHT)); | (int)floor(pbody.pos.y / CHUNK_HEIGHT)); | ||||
// Just init one chunk per frame | // Just init one chunk per frame | ||||
if (chunk_init_list_.size() > 0) { | |||||
Chunk *chunk = chunk_init_list_.front(); | |||||
chunk_init_list_.pop_front(); | |||||
if (chunkInitList_.size() > 0) { | |||||
Chunk *chunk = chunkInitList_.front(); | |||||
chunkInitList_.pop_front(); | |||||
chunk->render(ctx, win.renderer_); | chunk->render(ctx, win.renderer_); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
for (auto &coll: ent_colls_) | |||||
for (auto &coll: entColls_) | |||||
coll->draw(ctx, win); | coll->draw(ctx, win); | ||||
if (debug_boxes_.size() > 0) { | |||||
for (auto &pos: debug_boxes_) { | |||||
if (debugBoxes_.size() > 0) { | |||||
for (auto &pos: debugBoxes_) { | |||||
win.drawRect(pos, Vec2(1, 1)); | win.drawRect(pos, Vec2(1, 1)); | ||||
} | } | ||||
} | } | ||||
ZoneScopedN("WorldPlane update"); | ZoneScopedN("WorldPlane update"); | ||||
std::lock_guard<std::mutex> lock(mut_); | std::lock_guard<std::mutex> lock(mut_); | ||||
auto ctx = getContext(); | auto ctx = getContext(); | ||||
debug_boxes_.clear(); | |||||
debugBoxes_.clear(); | |||||
for (auto &coll: ent_colls_) | |||||
for (auto &coll: entColls_) | |||||
coll->update(ctx, dt); | coll->update(ctx, dt); | ||||
} | } | ||||
auto ctx = getContext(); | auto ctx = getContext(); | ||||
// Any chunk which has been in use since last tick should be kept alive | // Any chunk which has been in use since last tick should be kept alive | ||||
for (std::pair<ChunkPos, Chunk *> &ch: tick_chunks_) | |||||
for (std::pair<ChunkPos, Chunk *> &ch: tickChunks_) | |||||
ch.second->keepActive(); | ch.second->keepActive(); | ||||
tick_chunks_.clear(); | |||||
tickChunks_.clear(); | |||||
for (auto &coll: ent_colls_) | |||||
for (auto &coll: entColls_) | |||||
coll->tick(ctx, dt); | coll->tick(ctx, dt); | ||||
// Tick all chunks, figure out if any of them should be deleted or compressed | // Tick all chunks, figure out if any of them should be deleted or compressed | ||||
auto iter = active_chunks_.begin(); | |||||
auto last = active_chunks_.end(); | |||||
auto iter = activeChunks_.begin(); | |||||
auto last = activeChunks_.end(); | |||||
while (iter != last) { | while (iter != last) { | ||||
auto &chunk = *iter; | auto &chunk = *iter; | ||||
auto action = chunk->tick(dt); | auto action = chunk->tick(dt); | ||||
case Chunk::TickAction::DEACTIVATE: | case Chunk::TickAction::DEACTIVATE: | ||||
info << "Compressing inactive modified chunk " << chunk->pos_; | info << "Compressing inactive modified chunk " << chunk->pos_; | ||||
chunk->compress(); | chunk->compress(); | ||||
iter = active_chunks_.erase(iter); | |||||
last = active_chunks_.end(); | |||||
iter = activeChunks_.erase(iter); | |||||
last = activeChunks_.end(); | |||||
break; | break; | ||||
case Chunk::TickAction::DELETE: | case Chunk::TickAction::DELETE: | ||||
info << "Deleting inactive unmodified chunk " << chunk->pos_; | info << "Deleting inactive unmodified chunk " << chunk->pos_; | ||||
chunks_.erase(chunk->pos_); | chunks_.erase(chunk->pos_); | ||||
iter = active_chunks_.erase(iter); | |||||
last = active_chunks_.end(); | |||||
iter = activeChunks_.erase(iter); | |||||
last = activeChunks_.end(); | |||||
break; | break; | ||||
case Chunk::TickAction::NOTHING: | case Chunk::TickAction::NOTHING: | ||||
++iter; | ++iter; | ||||
} | } | ||||
void WorldPlane::debugBox(TilePos pos) { | void WorldPlane::debugBox(TilePos pos) { | ||||
debug_boxes_.push_back(pos); | |||||
debugBoxes_.push_back(pos); | |||||
} | } | ||||
void WorldPlane::addLight(TilePos pos, float level) { | void WorldPlane::addLight(TilePos pos, float level) { | ||||
void WorldPlane::onLightChunkUpdated(const LightChunk &chunk, ChunkPos pos) { | void WorldPlane::onLightChunkUpdated(const LightChunk &chunk, ChunkPos pos) { | ||||
std::lock_guard<std::mutex> lock(mut_); | std::lock_guard<std::mutex> lock(mut_); | ||||
Chunk &realChunk = getChunk(pos); | Chunk &realChunk = getChunk(pos); | ||||
realChunk.setLightData(chunk.light_levels); | |||||
realChunk.setLightData(chunk.lightLevels); | |||||
} | } | ||||
} | } |
for (int y = (int)floor(body.top() + epsilon); y <= (int)floor(body.bottom() - epsilon); ++y) { | for (int y = (int)floor(body.top() + epsilon); y <= (int)floor(body.bottom() - epsilon); ++y) { | ||||
int lx = (int)floor(body.left() + epsilon); | int lx = (int)floor(body.left() + epsilon); | ||||
Tile &left = plane.getTile({ lx, y }); | Tile &left = plane.getTile({ lx, y }); | ||||
if (left.is_solid_) { | |||||
if (left.isSolid_) { | |||||
body.pos.x = (float)lx + 1.0; | body.pos.x = (float)lx + 1.0; | ||||
collided = true; | collided = true; | ||||
break; | break; | ||||
int rx = (int)floor(body.right() - epsilon); | int rx = (int)floor(body.right() - epsilon); | ||||
Tile &right = plane.getTile({ rx, y }); | Tile &right = plane.getTile({ rx, y }); | ||||
if (right.is_solid_) { | |||||
if (right.isSolid_) { | |||||
body.pos.x = (float)rx - body.size.x; | body.pos.x = (float)rx - body.size.x; | ||||
collided = true; | collided = true; | ||||
break; | break; | ||||
PhysicsTrait::Physics &phys, BodyTrait::Body &body, | PhysicsTrait::Physics &phys, BodyTrait::Body &body, | ||||
WorldPlane &plane, const PhysicsTrait::PhysicsProps &props) { | WorldPlane &plane, const PhysicsTrait::PhysicsProps &props) { | ||||
bool collided = false; | bool collided = false; | ||||
phys.on_ground = false; | |||||
phys.onGround = false; | |||||
for (int x = (int)floor(body.left() + epsilon); x <= (int)floor(body.right() - epsilon); ++x) { | for (int x = (int)floor(body.left() + epsilon); x <= (int)floor(body.right() - epsilon); ++x) { | ||||
int ty = (int)floor(body.top() + epsilon); | int ty = (int)floor(body.top() + epsilon); | ||||
Tile &top = plane.getTile({ x, ty }); | Tile &top = plane.getTile({ x, ty }); | ||||
if (top.is_solid_) { | |||||
if (top.isSolid_) { | |||||
body.pos.y = (float)ty + 1.0; | body.pos.y = (float)ty + 1.0; | ||||
collided = true; | collided = true; | ||||
break; | break; | ||||
int by = (int)floor(body.bottom() - epsilon); | int by = (int)floor(body.bottom() - epsilon); | ||||
Tile &bottom = plane.getTile({ x, by }); | Tile &bottom = plane.getTile({ x, by }); | ||||
if (bottom.is_solid_) { | |||||
if (bottom.isSolid_) { | |||||
body.pos.y = (float)by - body.size.y; | body.pos.y = (float)by - body.size.y; | ||||
collided = true; | collided = true; | ||||
phys.on_ground = true; | |||||
phys.onGround = true; | |||||
break; | break; | ||||
} | } | ||||
} | } |
Swan::ItemStack s2(&item1, 40); | Swan::ItemStack s2(&item1, 40); | ||||
s2 = s1.insert(s2); | s2 = s1.insert(s2); | ||||
expecteq(s1.count(), item1.max_stack_); | |||||
expecteq(s2.count(), 80 - item1.max_stack_); | |||||
expecteq(s1.count(), item1.maxStack_); | |||||
expecteq(s2.count(), 80 - item1.maxStack_); | |||||
} | } | ||||
test("Insert respects max_stack_") { | test("Insert respects max_stack_") { | ||||
MockItem item1({ .name = "item1", .image = "no", .max_stack = 20 }); | |||||
MockItem item1({ .name = "item1", .image = "no", .maxStack = 20 }); | |||||
Swan::ItemStack s1(&item1, 15); | Swan::ItemStack s1(&item1, 15); | ||||
Swan::ItemStack s2(&item1, 19); | Swan::ItemStack s2(&item1, 19); |
namespace testlib { | namespace testlib { | ||||
static const std::string color_reset = "\033[0m"; | |||||
static const std::string color_highlight = "\033[1m"; | |||||
static const std::string color_testing = color_highlight; | |||||
static const std::string color_desc = "\033[33m"; | |||||
static const std::string color_maybe = "\033[35m"; | |||||
static const std::string color_success = "\033[32m"; | |||||
static const std::string color_fail = "\033[31m"; | |||||
static const std::string color_errormsg = "\033[95m"; | |||||
static const std::string COLOR_RESET = "\033[0m"; | |||||
static const std::string COLOR_HIGHLIGHT = "\033[1m"; | |||||
static const std::string COLOR_TESTING = COLOR_HIGHLIGHT; | |||||
static const std::string COLOR_DESC = "\033[33m"; | |||||
static const std::string COLOR_MAYBE = "\033[35m"; | |||||
static const std::string COLOR_SUCCESS = "\033[32m"; | |||||
static const std::string COLOR_FAIL = "\033[31m"; | |||||
static const std::string COLOR_ERRORMSG = "\033[95m"; | |||||
std::string color(const std::string &color, std::string_view str) { | std::string color(const std::string &color, std::string_view str) { | ||||
return std::string(color) + std::string(str) + std::string(color_reset); | |||||
return std::string(color) + std::string(str) + std::string(COLOR_RESET); | |||||
} | } | ||||
struct TestCase { | struct TestCase { | ||||
static std::stringstream printFailure(const std::string &msg) { | static std::stringstream printFailure(const std::string &msg) { | ||||
std::stringstream str; | std::stringstream str; | ||||
str | str | ||||
<< "\r" << color(color_highlight + color_fail, "✕ ") | |||||
<< color(color_fail, "Failed: ") << "\n" | |||||
<< "\r" << color(COLOR_HIGHLIGHT + COLOR_FAIL, "✕ ") | |||||
<< color(COLOR_FAIL, "Failed: ") << "\n" | |||||
<< " " << msg << "\n"; | << " " << msg << "\n"; | ||||
return str; | return str; | ||||
} | } | ||||
if (currfile != testcase.filename) { | if (currfile != testcase.filename) { | ||||
currfile = testcase.filename; | currfile = testcase.filename; | ||||
size_t lastslash = currfile.find_last_of('/'); | size_t lastslash = currfile.find_last_of('/'); | ||||
std::cout << '\n' << color(color_testing, currfile.substr(lastslash + 1)) << ":\n"; | |||||
std::cout << '\n' << color(COLOR_TESTING, currfile.substr(lastslash + 1)) << ":\n"; | |||||
} | } | ||||
std::cout | std::cout | ||||
<< color(color_highlight + color_maybe, "? ") | |||||
<< color(color_maybe, "Testing: ") | |||||
<< color(color_desc, testcase.description) << " " << std::flush; | |||||
<< color(COLOR_HIGHLIGHT + COLOR_MAYBE, "? ") | |||||
<< color(COLOR_MAYBE, "Testing: ") | |||||
<< color(COLOR_DESC, testcase.description) << " " << std::flush; | |||||
bool casefailed = false; | bool casefailed = false; | ||||
totaltests += 1; | totaltests += 1; | ||||
testcase.func(); | testcase.func(); | ||||
std::cout | std::cout | ||||
<< "\r" << color(color_highlight + color_success, "✓ ") | |||||
<< color(color_success, "Success: ") | |||||
<< color(color_desc, testcase.description) << "\n"; | |||||
<< "\r" << color(COLOR_HIGHLIGHT + COLOR_SUCCESS, "✓ ") | |||||
<< color(COLOR_SUCCESS, "Success: ") | |||||
<< color(COLOR_DESC, testcase.description) << "\n"; | |||||
totalsuccess += 1; | totalsuccess += 1; | ||||
} catch (const TestFailure &failure) { | } catch (const TestFailure &failure) { | ||||
casefailed = true; | casefailed = true; |
png::image<png::rgb_pixel> image(Swan::CHUNK_WIDTH, Swan::CHUNK_HEIGHT); | png::image<png::rgb_pixel> image(Swan::CHUNK_WIDTH, Swan::CHUNK_HEIGHT); | ||||
for (int y = 0; y < Swan::CHUNK_HEIGHT; ++y) { | for (int y = 0; y < Swan::CHUNK_HEIGHT; ++y) { | ||||
for (int x = 0; x < Swan::CHUNK_WIDTH; ++x) { | for (int x = 0; x < Swan::CHUNK_WIDTH; ++x) { | ||||
uint8_t light = cb.chunk_.light_levels[y * Swan::CHUNK_WIDTH + x]; | |||||
uint8_t light = cb.chunk_.lightLevels[y * Swan::CHUNK_WIDTH + x]; | |||||
bool block = false; | bool block = false; | ||||
if (cb.chunk_.blocks[y * Swan::CHUNK_WIDTH + x]) { | if (cb.chunk_.blocks[y * Swan::CHUNK_WIDTH + x]) { | ||||
block = true; | block = true; |
} | } | ||||
int main(int argc, char **argv) { | int main(int argc, char **argv) { | ||||
uint32_t winflags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; | |||||
uint32_t renderflags = SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC; | |||||
float gui_scale = 1; | |||||
uint32_t winFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; | |||||
uint32_t renderFlags = SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC; | |||||
float guiScale = 1; | |||||
for (int i = 1; i < argc; ++i) { | for (int i = 1; i < argc; ++i) { | ||||
if (strcmp(argv[i], "--lodpi") == 0) { | if (strcmp(argv[i], "--lodpi") == 0) { | ||||
winflags &= ~SDL_WINDOW_ALLOW_HIGHDPI; | |||||
winFlags &= ~SDL_WINDOW_ALLOW_HIGHDPI; | |||||
} else if (strcmp(argv[i], "--fullscreen") == 0) { | } else if (strcmp(argv[i], "--fullscreen") == 0) { | ||||
winflags |= SDL_WINDOW_FULLSCREEN_DESKTOP; | |||||
winFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; | |||||
} else if (strcmp(argv[i], "--no-vsync") == 0) { | } else if (strcmp(argv[i], "--no-vsync") == 0) { | ||||
renderflags &= ~SDL_RENDERER_PRESENTVSYNC; | |||||
renderFlags &= ~SDL_RENDERER_PRESENTVSYNC; | |||||
} else if (strcmp(argv[i], "--vulkan") == 0) { | } else if (strcmp(argv[i], "--vulkan") == 0) { | ||||
winflags |= SDL_WINDOW_VULKAN; | |||||
winFlags |= SDL_WINDOW_VULKAN; | |||||
} else if (strcmp(argv[i], "--sw-render") == 0) { | } else if (strcmp(argv[i], "--sw-render") == 0) { | ||||
renderflags &= ~SDL_RENDERER_ACCELERATED; | |||||
renderflags |= SDL_RENDERER_SOFTWARE; | |||||
renderFlags &= ~SDL_RENDERER_ACCELERATED; | |||||
renderFlags |= SDL_RENDERER_SOFTWARE; | |||||
} else if (strcmp(argv[i], "--2x") == 0) { | } else if (strcmp(argv[i], "--2x") == 0) { | ||||
gui_scale = 2; | |||||
guiScale = 2; | |||||
} else if (strcmp(argv[i], "--gles") == 0) { | } else if (strcmp(argv[i], "--gles") == 0) { | ||||
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengles2"); | SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengles2"); | ||||
} else { | } else { | ||||
sdlassert(SDL_Init(SDL_INIT_VIDEO) >= 0, "Could not initialize SDL"); | sdlassert(SDL_Init(SDL_INIT_VIDEO) >= 0, "Could not initialize SDL"); | ||||
Deferred<SDL_Quit> sdl; | Deferred<SDL_Quit> sdl; | ||||
int imgflags = IMG_INIT_PNG; | |||||
imgassert(IMG_Init(imgflags) == imgflags, "Could not initialize SDL_Image"); | |||||
Deferred<IMG_Quit> sdl_image; | |||||
int imgFlags = IMG_INIT_PNG; | |||||
imgassert(IMG_Init(imgFlags) == imgFlags, "Could not initialize SDL_Image"); | |||||
Deferred<IMG_Quit> sdlImage; | |||||
// Create the window | // Create the window | ||||
CPtr<SDL_Window, SDL_DestroyWindow> window( | CPtr<SDL_Window, SDL_DestroyWindow> window( | ||||
SDL_CreateWindow( | SDL_CreateWindow( | ||||
"Project: SWAN", | "Project: SWAN", | ||||
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, | SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, | ||||
(int)(640 * gui_scale), (int)(480 * gui_scale), winflags)); | |||||
(int)(640 * guiScale), (int)(480 * guiScale), winFlags)); | |||||
// Load and display application icon | // Load and display application icon | ||||
CPtr<SDL_Surface, SDL_FreeSurface> icon( | CPtr<SDL_Surface, SDL_FreeSurface> icon( | ||||
SDL_SetWindowIcon(window.get(), icon.get()); | SDL_SetWindowIcon(window.get(), icon.get()); | ||||
CPtr<SDL_Renderer, SDL_DestroyRenderer> renderer( | CPtr<SDL_Renderer, SDL_DestroyRenderer> renderer( | ||||
SDL_CreateRenderer(window.get(), -1, renderflags)); | |||||
SDL_CreateRenderer(window.get(), -1, renderFlags)); | |||||
sdlassert(renderer, "Could not create renderer"); | sdlassert(renderer, "Could not create renderer"); | ||||
SDL_SetRenderDrawBlendMode(renderer.get(), SDL_BLENDMODE_BLEND); | SDL_SetRenderDrawBlendMode(renderer.get(), SDL_BLENDMODE_BLEND); | ||||
SDL_SetRenderDrawColor(renderer.get(), 0, 0, 0, 255); | SDL_SetRenderDrawColor(renderer.get(), 0, 0, 0, 255); | ||||
Win win(window.get(), renderer.get(), gui_scale); | |||||
Win win(window.get(), renderer.get(), guiScale); | |||||
// Init ImGUI and ImGUI_SDL | // Init ImGUI and ImGUI_SDL | ||||
IMGUI_CHECKVERSION(); | IMGUI_CHECKVERSION(); | ||||
CPtr<ImGuiContext, ImGui::DestroyContext> context( | CPtr<ImGuiContext, ImGui::DestroyContext> context( | ||||
ImGui::CreateContext()); | ImGui::CreateContext()); | ||||
ImGuiSDL::Initialize(renderer.get(), (int)win.getPixSize().x, (int)win.getPixSize().y); | ImGuiSDL::Initialize(renderer.get(), (int)win.getPixSize().x, (int)win.getPixSize().y); | ||||
Deferred<ImGuiSDL::Deinitialize> imgui_sdl; | |||||
Deferred<ImGuiSDL::Deinitialize> imguiSDL; | |||||
info << "Initialized with window size " << win.getPixSize(); | info << "Initialized with window size " << win.getPixSize(); | ||||
// ImGuiIO is to glue SDL and ImGUI together | // ImGuiIO is to glue SDL and ImGUI together | ||||
ImGuiIO& imgui_io = ImGui::GetIO(); | |||||
imgui_io.BackendPlatformName = "imgui_sdl + Project: SWAN"; | |||||
ImGuiIO& imguiIO = ImGui::GetIO(); | |||||
imguiIO.BackendPlatformName = "imgui_sdl + Project: SWAN"; | |||||
// Create a world | // Create a world | ||||
Game game(win); | Game game(win); | ||||
std::vector<std::string> mods{ "core.mod" }; | std::vector<std::string> mods{ "core.mod" }; | ||||
game.createWorld("core::default", mods); | game.createWorld("core::default", mods); | ||||
auto prev_time = std::chrono::steady_clock::now(); | |||||
auto prevTime = std::chrono::steady_clock::now(); | |||||
float fps_acc = 0; | |||||
float tick_acc = 0; | |||||
float fpsAcc = 0; | |||||
float tickAcc = 0; | |||||
int fcount = 0; | |||||
int slow_frames = 0; | |||||
int fCount = 0; | |||||
int slowFrames = 0; | |||||
while (1) { | while (1) { | ||||
ZoneScopedN("game loop"); | ZoneScopedN("game loop"); | ||||
RTClock total_time_clock; | |||||
RTClock totalTimeClock; | |||||
SDL_Event evt; | SDL_Event evt; | ||||
while (SDL_PollEvent(&evt)) { | while (SDL_PollEvent(&evt)) { | ||||
case SDL_WINDOWEVENT: | case SDL_WINDOWEVENT: | ||||
if (evt.window.event == SDL_WINDOWEVENT_RESIZED) { | if (evt.window.event == SDL_WINDOWEVENT_RESIZED) { | ||||
imgui_io.DisplaySize.x = (float)evt.window.data1; | |||||
imgui_io.DisplaySize.y = (float)evt.window.data2; | |||||
imguiIO.DisplaySize.x = (float)evt.window.data1; | |||||
imguiIO.DisplaySize.y = (float)evt.window.data2; | |||||
win.onResize(evt.window.data1, evt.window.data2); | win.onResize(evt.window.data1, evt.window.data2); | ||||
} | } | ||||
break; | break; | ||||
break; | break; | ||||
case SDL_MOUSEMOTION: | case SDL_MOUSEMOTION: | ||||
imgui_io.MousePos.x = (float)evt.motion.x; | |||||
imgui_io.MousePos.y = (float)evt.motion.y; | |||||
if (!imgui_io.WantCaptureMouse) | |||||
imguiIO.MousePos.x = (float)evt.motion.x; | |||||
imguiIO.MousePos.y = (float)evt.motion.y; | |||||
if (!imguiIO.WantCaptureMouse) | |||||
game.onMouseMove(evt.motion.x, evt.motion.y); | game.onMouseMove(evt.motion.x, evt.motion.y); | ||||
break; | break; | ||||
case SDL_MOUSEBUTTONDOWN: | case SDL_MOUSEBUTTONDOWN: | ||||
imgui_io.MouseDown[sdlButtonToImGuiButton(evt.button.button)] = true; | |||||
if (!imgui_io.WantCaptureMouse) | |||||
imguiIO.MouseDown[sdlButtonToImGuiButton(evt.button.button)] = true; | |||||
if (!imguiIO.WantCaptureMouse) | |||||
game.onMouseDown(evt.button.x, evt.button.y, evt.button.button); | game.onMouseDown(evt.button.x, evt.button.y, evt.button.button); | ||||
break; | break; | ||||
case SDL_MOUSEBUTTONUP: | case SDL_MOUSEBUTTONUP: | ||||
imgui_io.MouseDown[sdlButtonToImGuiButton(evt.button.button)] = false; | |||||
if (!imgui_io.WantCaptureMouse) | |||||
imguiIO.MouseDown[sdlButtonToImGuiButton(evt.button.button)] = false; | |||||
if (!imguiIO.WantCaptureMouse) | |||||
game.onMouseUp(evt.button.x, evt.button.y, evt.button.button); | game.onMouseUp(evt.button.x, evt.button.y, evt.button.button); | ||||
break; | break; | ||||
case SDL_MOUSEWHEEL: | case SDL_MOUSEWHEEL: | ||||
imgui_io.MouseWheel += (float)evt.wheel.y; | |||||
if (!imgui_io.WantCaptureMouse) | |||||
imguiIO.MouseWheel += (float)evt.wheel.y; | |||||
if (!imguiIO.WantCaptureMouse) | |||||
game.onScrollWheel(evt.wheel.y); | game.onScrollWheel(evt.wheel.y); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
auto now = std::chrono::steady_clock::now(); | auto now = std::chrono::steady_clock::now(); | ||||
std::chrono::duration<float> dur(now - prev_time); | |||||
prev_time = now; | |||||
std::chrono::duration<float> dur(now - prevTime); | |||||
prevTime = now; | |||||
float dt = dur.count(); | float dt = dur.count(); | ||||
// Display FPS | // Display FPS | ||||
fps_acc += dt; | |||||
fcount += 1; | |||||
if (fps_acc >= 4) { | |||||
info << "FPS: " << fcount / 4.0; | |||||
fps_acc -= 4; | |||||
fcount = 0; | |||||
fpsAcc += dt; | |||||
fCount += 1; | |||||
if (fpsAcc >= 4) { | |||||
info << "FPS: " << fCount / 4.0; | |||||
fpsAcc -= 4; | |||||
fCount = 0; | |||||
} | } | ||||
// We want to warn if one frame takes over 0.1 seconds... | // We want to warn if one frame takes over 0.1 seconds... | ||||
if (dt > 0.1) { | if (dt > 0.1) { | ||||
if (slow_frames == 0) | |||||
if (slowFrames == 0) | |||||
warn << "Delta time too high! (" << dt << "s)"; | warn << "Delta time too high! (" << dt << "s)"; | ||||
slow_frames += 1; | |||||
slowFrames += 1; | |||||
// And we never want to do physics as if our one frame is greater than | // And we never want to do physics as if our one frame is greater than | ||||
// 0.5 seconds. | // 0.5 seconds. | ||||
if (dt > 0.5) | if (dt > 0.5) | ||||
dt = 0.5; | dt = 0.5; | ||||
} else if (slow_frames > 0) { | |||||
if (slow_frames > 1) | |||||
warn << slow_frames << " consecutive slow frames."; | |||||
slow_frames = 0; | |||||
} else if (slowFrames > 0) { | |||||
if (slowFrames > 1) | |||||
warn << slowFrames << " consecutive slow frames."; | |||||
slowFrames = 0; | |||||
} | } | ||||
// Simple case: we can keep up, only need one physics update | // Simple case: we can keep up, only need one physics update | ||||
RTClock update_clock; | |||||
RTClock updateClock; | |||||
if (dt <= 1 / 25.0) { | if (dt <= 1 / 25.0) { | ||||
ZoneScopedN("game update"); | ZoneScopedN("game update"); | ||||
game.update(dt); | game.update(dt); | ||||
} | } | ||||
// Tick at a consistent TICK_RATE | // Tick at a consistent TICK_RATE | ||||
tick_acc += dt; | |||||
while (tick_acc >= 1.0 / TICK_RATE) { | |||||
tickAcc += dt; | |||||
while (tickAcc >= 1.0 / TICK_RATE) { | |||||
ZoneScopedN("game tick"); | ZoneScopedN("game tick"); | ||||
tick_acc -= 1.0 / TICK_RATE; | |||||
tickAcc -= 1.0 / TICK_RATE; | |||||
RTClock tick_clock; | RTClock tick_clock; | ||||
game.tick(1.0 / TICK_RATE); | game.tick(1.0 / TICK_RATE); | ||||
} | } | ||||
} | } | ||||
// ImGUI | // ImGUI | ||||
imgui_io.DeltaTime = dt; | |||||
imguiIO.DeltaTime = dt; | |||||
ImGui::NewFrame(); | ImGui::NewFrame(); | ||||
{ | { | ||||
ZoneScopedN("game draw"); | ZoneScopedN("game draw"); | ||||
RTClock draw_clock; | |||||
RTClock drawClock; | |||||
game.draw(); | game.draw(); | ||||
} | } | ||||
ImGuiSDL::Render(ImGui::GetDrawData()); | ImGuiSDL::Render(ImGui::GetDrawData()); | ||||
} | } | ||||
RTClock present_clock; | |||||
RTClock presentClock; | |||||
{ | { | ||||
ZoneScopedN("render present"); | ZoneScopedN("render present"); | ||||
SDL_RenderPresent(renderer.get()); | SDL_RenderPresent(renderer.get()); |
#include <PerlinNoise/PerlinNoise.hpp> | #include <PerlinNoise/PerlinNoise.hpp> | ||||
#include <png++/png.hpp> | #include <png++/png.hpp> | ||||
static int grassLevel(const siv::PerlinNoise &perlin, int x) { | |||||
static int getGrassLevel(const siv::PerlinNoise &perlin, int x) { | |||||
return (int)(perlin.noise(x / 50.0, 0) * 13); | return (int)(perlin.noise(x / 50.0, 0) * 13); | ||||
} | } | ||||
static int stoneLevel(const siv::PerlinNoise &perlin, int x) { | |||||
static int getStoneLevel(const siv::PerlinNoise &perlin, int x) { | |||||
return (int)(perlin.noise(x / 50.0, 10) * 10) + 10; | return (int)(perlin.noise(x / 50.0, 10) * 10) + 10; | ||||
} | } | ||||
for (int x = x1; x <= x2; ++x) { | for (int x = x1; x <= x2; ++x) { | ||||
int px = x - x1; | int px = x - x1; | ||||
int grass_level = grassLevel(perlin, x); | |||||
int stone_level = stoneLevel(perlin, x); | |||||
int grassLevel = getGrassLevel(perlin, x); | |||||
int stoneLevel = getStoneLevel(perlin, x); | |||||
for (int y = y1; y <= y2; ++y) { | for (int y = y1; y <= y2; ++y) { | ||||
int py = y - y1; | int py = y - y1; | ||||
if (y > grass_level + 10) { | |||||
if (y > grassLevel + 10) { | |||||
double l = perlin.noise(x / 41.37, y / 16.37); | double l = perlin.noise(x / 41.37, y / 16.37); | ||||
if (l > 0.2) | if (l > 0.2) | ||||
image[py][px] = 255; | image[py][px] = 255; | ||||
else | else | ||||
image[py][px] = 0; | image[py][px] = 0; | ||||
} else if (y >= grass_level) { | |||||
} else if (y >= grassLevel) { | |||||
image[py][px] = 0; | image[py][px] = 0; | ||||
} else { | } else { | ||||
image[py][px] = 255; | image[py][px] = 255; | ||||
} | } | ||||
if (y == grass_level) { | |||||
if (y == grassLevel) { | |||||
image[py][px] = 128; | image[py][px] = 128; | ||||
} | } | ||||
} | } |