assets/tile/dirt.png | assets/tile/dirt.png | ||||
assets/tile/leaves.png | assets/tile/leaves.png | ||||
assets/tile/tree-trunk.png | assets/tile/tree-trunk.png | ||||
assets/tile/torch.png | |||||
assets/misc/background-cave.png) | assets/misc/background-cave.png) | ||||
foreach(a ${assets}) | foreach(a ${assets}) | ||||
configure_file("${a}" "${a}" COPYONLY) | configure_file("${a}" "${a}" COPYONLY) |
if (ctx.game.isMousePressed(SDL_BUTTON_LEFT)) | if (ctx.game.isMousePressed(SDL_BUTTON_LEFT)) | ||||
ctx.plane.breakTile(mouse_tile_); | ctx.plane.breakTile(mouse_tile_); | ||||
// Place block | |||||
if (ctx.game.isMousePressed(SDL_BUTTON_RIGHT)) { | |||||
if (ctx.plane.getTileID(mouse_tile_) == ctx.world.getTileID("@::air")) { | |||||
ctx.plane.setTile(mouse_tile_, "core::torch"); | |||||
} | |||||
} | |||||
// Move left | // Move left | ||||
if (ctx.game.isKeyPressed(SDL_SCANCODE_A) || ctx.game.isKeyPressed(SDL_SCANCODE_LEFT)) { | if (ctx.game.isKeyPressed(SDL_SCANCODE_A) || ctx.game.isKeyPressed(SDL_SCANCODE_LEFT)) { | ||||
physics_.force += Swan::Vec2(-MOVE_FORCE, 0); | physics_.force += Swan::Vec2(-MOVE_FORCE, 0); |
registerImage("tile/grass"); | registerImage("tile/grass"); | ||||
registerImage("tile/tree-trunk"); | registerImage("tile/tree-trunk"); | ||||
registerImage("tile/leaves"); | registerImage("tile/leaves"); | ||||
registerImage("tile/torch"); | |||||
registerImage("entity/player-running"); | registerImage("entity/player-running"); | ||||
registerImage("entity/player-still"); | registerImage("entity/player-still"); | ||||
registerImage("misc/background-cave"); | registerImage("misc/background-cave"); | ||||
.name = "leaves", | .name = "leaves", | ||||
.image = "core/tile/leaves", | .image = "core/tile/leaves", | ||||
}); | }); | ||||
registerTile({ | |||||
.name = "torch", | |||||
.image = "core/tile/torch", | |||||
.is_solid = false, | |||||
.light_level = 20, | |||||
}); | |||||
registerItem({ | registerItem({ | ||||
.name = "stone", | .name = "stone", |
Chunk(ChunkPos pos): pos_(pos) { | Chunk(ChunkPos pos): pos_(pos) { | ||||
data_.reset(new uint8_t[DATA_SIZE]); | data_.reset(new uint8_t[DATA_SIZE]); | ||||
memset(getLightData(), 255, CHUNK_WIDTH * CHUNK_HEIGHT); | |||||
memset(getLightData(), 0, CHUNK_WIDTH * CHUNK_HEIGHT); | |||||
} | } | ||||
Tile::ID *getTileData() { | Tile::ID *getTileData() { | ||||
void setLightData(RelPos pos, uint8_t level) { | void setLightData(RelPos pos, uint8_t level) { | ||||
getLightData()[pos.y * CHUNK_WIDTH + pos.x] = level; | getLightData()[pos.y * CHUNK_WIDTH + pos.x] = level; | ||||
need_render_ = true; | |||||
need_light_render_ = true; | |||||
} | } | ||||
void render(const Context &ctx, SDL_Renderer *rnd); | void render(const Context &ctx, SDL_Renderer *rnd); | ||||
void renderLight(const Context &ctx, SDL_Renderer *rnd); | |||||
void compress(); | void compress(); | ||||
void decompress(); | void decompress(); | ||||
ssize_t compressed_size_ = -1; // -1 if not compressed, a positive number if compressed | ssize_t compressed_size_ = -1; // -1 if not compressed, a positive number if compressed | ||||
bool need_render_ = false; | bool need_render_ = false; | ||||
bool need_light_render_ = false; | |||||
float deactivate_timer_ = DEACTIVATE_INTERVAL; | float deactivate_timer_ = DEACTIVATE_INTERVAL; | ||||
bool is_modified_ = false; | bool is_modified_ = false; | ||||
std::string name; | std::string name; | ||||
std::string image; | std::string image; | ||||
bool is_solid = true; | bool is_solid = true; | ||||
uint8_t light_level = 0; | |||||
std::optional<std::string> dropped_item = std::nullopt; | std::optional<std::string> dropped_item = 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), dropped_item_(builder.dropped_item) {} | |||||
is_solid_(builder.is_solid), light_level_(builder.light_level), | |||||
dropped_item_(builder.dropped_item) {} | |||||
const std::string name_; | const std::string name_; | ||||
const ImageResource &image_; | const ImageResource &image_; | ||||
const bool is_solid_; | const bool is_solid_; | ||||
const uint8_t light_level_; | |||||
const std::optional<std::string> dropped_item_; | const std::optional<std::string> dropped_item_; | ||||
static std::unique_ptr<Tile> createInvalid(const ResourceManager &ctx); | static std::unique_ptr<Tile> createInvalid(const ResourceManager &ctx); |
std::unique_ptr<WorldGen> gen_; | std::unique_ptr<WorldGen> gen_; | ||||
private: | private: | ||||
void addLight(TilePos pos, uint8_t level); | |||||
void removeLight(TilePos pos, uint8_t level); | |||||
std::map<std::pair<int, int>, Chunk> chunks_; | std::map<std::pair<int, int>, Chunk> chunks_; | ||||
std::vector<Chunk *> active_chunks_; | std::vector<Chunk *> active_chunks_; | ||||
std::vector<std::pair<ChunkPos, Chunk *>> tick_chunks_; | std::vector<std::pair<ChunkPos, Chunk *>> tick_chunks_; |
compressed_size_ = -1; | compressed_size_ = -1; | ||||
} | } | ||||
void Chunk::renderLight(const Context &ctx, SDL_Renderer *rnd) { | |||||
std::optional<RenderTarget> target; | |||||
// The texture might not be created yet | |||||
if (!light_texture_) { | |||||
light_texture_.reset(SDL_CreateTexture( | |||||
ctx.game.win_.renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, | |||||
CHUNK_WIDTH, CHUNK_HEIGHT)); | |||||
SDL_SetTextureBlendMode(light_texture_.get(), SDL_BLENDMODE_BLEND); | |||||
target.emplace(rnd, texture_.get()); | |||||
} else { | |||||
target.emplace(rnd, texture_.get()); | |||||
} | |||||
// Fill light texture | |||||
target.emplace(rnd, light_texture_.get()); | |||||
RenderBlendMode mode(rnd, SDL_BLENDMODE_NONE); | |||||
RenderDrawColor color(rnd, 0, 0, 0, 0); | |||||
for (int y = 0; y < CHUNK_HEIGHT; ++y) { | |||||
for (int x = 0; x < CHUNK_WIDTH; ++x) { | |||||
int level = getLightLevel({ x, y }); | |||||
if (level >= 8) | |||||
color.change(0, 0, 0, 0); | |||||
else | |||||
color.change(0, 0, 0, 255 - level * 32); | |||||
SDL_Rect rect{ x, y, 1, 1 }; | |||||
SDL_RenderFillRect(rnd, &rect); | |||||
} | |||||
} | |||||
need_light_render_ = false; | |||||
} | |||||
void Chunk::render(const Context &ctx, SDL_Renderer *rnd) { | void Chunk::render(const Context &ctx, SDL_Renderer *rnd) { | ||||
std::optional<RenderTarget> target; | std::optional<RenderTarget> target; | ||||
target.emplace(rnd, texture_.get()); | target.emplace(rnd, texture_.get()); | ||||
} | } | ||||
// Same with light texture | |||||
if (!light_texture_) { | |||||
light_texture_.reset(SDL_CreateTexture( | |||||
ctx.game.win_.renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, | |||||
CHUNK_WIDTH, CHUNK_HEIGHT)); | |||||
SDL_SetTextureBlendMode(light_texture_.get(), SDL_BLENDMODE_BLEND); | |||||
} | |||||
// 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.invalid_tile_.get(); | ||||
} | } | ||||
} | } | ||||
// Fill light texture | |||||
target.emplace(rnd, light_texture_.get()); | |||||
RenderBlendMode mode(rnd, SDL_BLENDMODE_NONE); | |||||
RenderDrawColor color(rnd, 0, 0, 0, 0); | |||||
for (int y = 0; y < CHUNK_HEIGHT; ++y) { | |||||
for (int x = 0; x < CHUNK_WIDTH; ++x) { | |||||
color.change(0, 0, 0, 255 - getLightLevel({ x, y })); | |||||
SDL_Rect rect{ x, y, 1, 1 }; | |||||
SDL_RenderFillRect(rnd, &rect); | |||||
} | |||||
} | |||||
need_render_ = false; | need_render_ = false; | ||||
renderLight(ctx, rnd); | |||||
} | } | ||||
void Chunk::renderList(SDL_Renderer *rnd) { | void Chunk::renderList(SDL_Renderer *rnd) { | ||||
if (need_render_) | if (need_render_) | ||||
return; | return; | ||||
// We're responsible for the light level rendering though | |||||
if (need_light_render_) | |||||
renderLight(ctx, win.renderer_); | |||||
if (draw_list_.size() > 0) { | if (draw_list_.size() > 0) { | ||||
renderList(win.renderer_); | renderList(win.renderer_); | ||||
draw_list_.clear(); | draw_list_.clear(); | ||||
} | } | ||||
auto chunkpos = pos_ * Vec2i(CHUNK_WIDTH, CHUNK_HEIGHT); | auto chunkpos = pos_ * Vec2i(CHUNK_WIDTH, CHUNK_HEIGHT); | ||||
SDL_Rect rect{ 0, 0, CHUNK_WIDTH * TILE_SIZE, CHUNK_HEIGHT * TILE_SIZE }; | SDL_Rect rect{ 0, 0, CHUNK_WIDTH * TILE_SIZE, CHUNK_HEIGHT * TILE_SIZE }; | ||||
win.showTexture(chunkpos, texture_.get(), &rect); | win.showTexture(chunkpos, texture_.get(), &rect); |
Tile::ID old = chunk.getTileID(rp); | Tile::ID old = chunk.getTileID(rp); | ||||
if (id != old) { | if (id != old) { | ||||
chunk.setTileID(rp, id, world_->getTileByID(id).image_.texture_.get()); | |||||
Tile &newTile = world_->getTileByID(id); | |||||
Tile &oldTile = world_->getTileByID(old); | |||||
chunk.setTileID(rp, id, newTile.image_.texture_.get()); | |||||
chunk.markModified(); | chunk.markModified(); | ||||
if (oldTile.light_level_ > 0) { | |||||
removeLight(pos, oldTile.light_level_); | |||||
} | |||||
if (newTile.light_level_ > 0) { | |||||
addLight(pos, newTile.light_level_); | |||||
} | |||||
} | } | ||||
} | } | ||||
debug_boxes_.push_back(pos); | debug_boxes_.push_back(pos); | ||||
} | } | ||||
void WorldPlane::addLight(TilePos pos, uint8_t level) { | |||||
int sqrLevel = level * level; | |||||
for (int y = -level; y <= level; ++y) { | |||||
for (int x = -level; x <= level; ++x) { | |||||
int sqrDist = x * x + y * y; | |||||
if (sqrDist > sqrLevel) { | |||||
continue; | |||||
} | |||||
int lightDiff = level - (int)sqrt(sqrDist); | |||||
if (lightDiff <= 0) { | |||||
continue; | |||||
} | |||||
TilePos tp = pos + Vec2i(x, y); | |||||
ChunkPos cp = chunkPos(tp); | |||||
Chunk::RelPos rp = relPos(tp); | |||||
Chunk &ch = getChunk(cp); | |||||
int light = (int)ch.getLightLevel(rp) + lightDiff; | |||||
if (light > 255) { | |||||
light = 255; | |||||
} | |||||
ch.setLightData(rp, light); | |||||
} | |||||
} | |||||
} | |||||
void WorldPlane::removeLight(TilePos pos, uint8_t level) { | |||||
int sqrLevel = level * level; | |||||
for (int y = -level; y <= level; ++y) { | |||||
for (int x = -level; x <= level; ++x) { | |||||
int sqrDist = x * x + y * y; | |||||
if (sqrDist > sqrLevel) { | |||||
continue; | |||||
} | |||||
int lightDiff = level - (int)sqrt(sqrDist); | |||||
if (lightDiff <= 0) { | |||||
continue; | |||||
} | |||||
TilePos tp = pos + Vec2i(x, y); | |||||
ChunkPos cp = chunkPos(tp); | |||||
Chunk::RelPos rp = relPos(tp); | |||||
Chunk &ch = getChunk(cp); | |||||
int light = (int)ch.getLightLevel(rp) - lightDiff; | |||||
if (light < 0) { | |||||
light = 0; | |||||
} | |||||
ch.setLightData(rp, light); | |||||
} | |||||
} | |||||
} | |||||
} | } |