This should've been multiple commits, but hey, it's late.opengl-renderer-broken
@@ -1,7 +1,8 @@ | |||
add_library(core.mod SHARED | |||
src/main.cc | |||
src/WGDefault.cc | |||
src/entities/EntPlayer.cc) | |||
src/entities/EntPlayer.cc | |||
src/entities/EntItemStack.cc) | |||
set_target_properties(core.mod PROPERTIES OUTPUT_NAME mod PREFIX "") | |||
target_link_libraries(core.mod libswan) | |||
@@ -0,0 +1,23 @@ | |||
#include "EntItemStack.h" | |||
EntItemStack::EntItemStack(const Swan::Context &ctx, const Swan::SRF ¶ms): | |||
body_(SIZE, MASS) { | |||
readSRF(ctx, params); | |||
} | |||
void EntItemStack::readSRF(const Swan::Context &ctx, const Swan::SRF &srf) { | |||
auto &arr = dynamic_cast<const Swan::SRFArray &>(srf); | |||
auto *pos = dynamic_cast<Swan::SRFFloatArray *>(arr.val[0].get()); | |||
auto *name = dynamic_cast<Swan::SRFString *>(arr.val[1].get()); | |||
body_.pos_.set(pos->val[0], pos->val[1]); | |||
item_ = &ctx.world.getItem(name->val); | |||
} | |||
Swan::SRF *EntItemStack::writeSRF(const Swan::Context &ctx) { | |||
return new Swan::SRFArray{ | |||
new Swan::SRFFloatArray{ body_.pos_.x_, body_.pos_.y_ }, | |||
new Swan::SRFString{ item_->name }, | |||
}; | |||
} |
@@ -0,0 +1,24 @@ | |||
#pragma once | |||
#include <swan/swan.h> | |||
class EntItemStack: public Swan::Entity { | |||
public: | |||
class Factory: public Swan::Entity::Factory { | |||
Swan::Entity *create(const Swan::Context &ctx, const Swan::SRF ¶ms) override { | |||
return new EntItemStack(ctx, params); | |||
} | |||
}; | |||
EntItemStack(const Swan::Context &ctx, const Swan::SRF ¶ms); | |||
void readSRF(const Swan::Context &ctx, const Swan::SRF &srf) override; | |||
Swan::SRF *writeSRF(const Swan::Context &ctx) override; | |||
private: | |||
static constexpr float MASS = 80; | |||
static constexpr Swan::Vec2 SIZE = Swan::Vec2(0.5, 0.5); | |||
Swan::Item *item_ = &Swan::Item::INVALID_ITEM; | |||
Swan::Body body_; | |||
}; |
@@ -3,7 +3,7 @@ | |||
EntPlayer::EntPlayer(const Swan::Context &ctx, const Swan::SRF ¶ms): | |||
body_(SIZE, MASS) { | |||
readSRF(params); | |||
readSRF(ctx, params); | |||
anims_[(int)State::IDLE].init(32, 64, 0.8, | |||
ctx.world.getAsset("core::player-still")); | |||
@@ -30,7 +30,7 @@ void EntPlayer::update(const Swan::Context &ctx, float dt) { | |||
// Break block | |||
if (ctx.game.isMousePressed(sf::Mouse::Button::Left)) | |||
ctx.plane.setTile(mouse_tile_, "core::air"); | |||
ctx.plane.breakBlock(mouse_tile_); | |||
// Move left | |||
if (ctx.game.isKeyPressed(sf::Keyboard::A) || ctx.game.isKeyPressed(sf::Keyboard::Left)) { | |||
@@ -62,11 +62,11 @@ void EntPlayer::update(const Swan::Context &ctx, float dt) { | |||
body_.collide(ctx.plane); | |||
} | |||
void EntPlayer::readSRF(const Swan::SRF &srf) { | |||
void EntPlayer::readSRF(const Swan::Context &ctx, const Swan::SRF &srf) { | |||
auto pos = dynamic_cast<const Swan::SRFFloatArray &>(srf); | |||
body_.pos_.set(pos.val[0], pos.val[1]); | |||
} | |||
Swan::SRF *EntPlayer::writeSRF() { | |||
Swan::SRF *EntPlayer::writeSRF(const Swan::Context &ctx) { | |||
return new Swan::SRFFloatArray{ body_.pos_.x_, body_.pos_.y_ }; | |||
} |
@@ -17,8 +17,8 @@ public: | |||
void draw(const Swan::Context &ctx, Swan::Win &win) override; | |||
void update(const Swan::Context &ctx, float dt) override; | |||
void readSRF(const Swan::SRF &srf) override; | |||
Swan::SRF *writeSRF() override; | |||
void readSRF(const Swan::Context &ctx, const Swan::SRF &srf) override; | |||
Swan::SRF *writeSRF(const Swan::Context &ctx) override; | |||
private: | |||
static constexpr float FORCE = 3000; |
@@ -2,6 +2,7 @@ | |||
#include "WGDefault.h" | |||
#include "entities/EntPlayer.h" | |||
#include "entities/EntItemStack.h" | |||
extern "C" void mod_init(Swan::Mod &mod) { | |||
mod.init("core"); | |||
@@ -9,19 +10,18 @@ extern "C" void mod_init(Swan::Mod &mod) { | |||
mod.registerTile("air", new Swan::Tile{ | |||
.image = mod.loadImage("assets/tiles/air.png"), | |||
.is_solid = false, | |||
.dropped_item = "dirt", | |||
}); | |||
mod.registerTile("stone", new Swan::Tile{ | |||
.image = mod.loadImage("assets/tiles/stone.png"), | |||
.dropped_item = "stone", | |||
.dropped_item = "core::stone", | |||
}); | |||
mod.registerTile("dirt", new Swan::Tile{ | |||
.image = mod.loadImage("assets/tiles/dirt.png"), | |||
.dropped_item = "dirt", | |||
.dropped_item = "core::dirt", | |||
}); | |||
mod.registerTile("grass", new Swan::Tile{ | |||
.image = mod.loadImage("assets/tiles/grass.png"), | |||
.dropped_item = "grass", | |||
.dropped_item = "core::grass", | |||
}); | |||
mod.registerItem("stone", new Swan::Item{ | |||
@@ -37,6 +37,7 @@ extern "C" void mod_init(Swan::Mod &mod) { | |||
mod.registerWorldGen("default", new WGDefault::Factory()); | |||
mod.registerEntity("player", new EntPlayer::Factory()); | |||
mod.registerEntity("item-stack", new EntItemStack::Factory()); | |||
mod.registerAsset("player-running", new Swan::Asset("assets/entities/player-running.png")); | |||
mod.registerAsset("player-still", new Swan::Asset("assets/entities/player-still.png")); |
@@ -17,6 +17,8 @@ class Chunk { | |||
public: | |||
using RelPos = TilePos; | |||
ChunkPos pos_; | |||
Chunk(ChunkPos pos): pos_(pos) { | |||
data_.reset(new uint8_t[CHUNK_WIDTH * CHUNK_HEIGHT * sizeof(Tile::ID)]); | |||
visuals_.reset(new Visuals()); | |||
@@ -34,16 +36,22 @@ public: | |||
void decompress(); | |||
void render(const Context &ctx); | |||
void draw(const Context &ctx, Win &win); | |||
void tick(); | |||
ChunkPos pos_; | |||
bool keepActive(); // Returns true if chunk was inactive | |||
bool isActive() { return active_timer_ != 0; } | |||
private: | |||
static constexpr int ACTIVE_TIMEOUT = 200; | |||
static sf::Uint8 *renderbuf; | |||
bool isCompressed() { return compressed_size_ != -1; } | |||
std::unique_ptr<uint8_t[]> data_; | |||
int 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; | |||
int active_timer_ = ACTIVE_TIMEOUT; | |||
struct Visuals { | |||
sf::Texture tex_; |
@@ -27,8 +27,8 @@ public: | |||
virtual void draw(const Context &ctx, Win &win) {} | |||
virtual void update(const Context &ctx, float dt) {} | |||
virtual void tick() {} | |||
virtual void readSRF(const SRF &srf) {} | |||
virtual SRF *writeSRF() { return new SRFNone(); } | |||
virtual void readSRF(const Swan::Context &ctx, const SRF &srf) {} | |||
virtual SRF *writeSRF(const Swan::Context &ctx) { return new SRFNone(); } | |||
}; | |||
} |
@@ -6,8 +6,7 @@ | |||
namespace Swan { | |||
class Item { | |||
public: | |||
struct Item { | |||
std::unique_ptr<sf::Image> image; | |||
std::string name = ""; |
@@ -24,7 +24,7 @@ struct SRF { | |||
struct SRFObject: SRF { | |||
SRFObject() {} | |||
SRFObject(std::initializer_list<std::pair<std::string, SRF *>> &lst); | |||
SRFObject(std::initializer_list<std::pair<std::string, SRF *>> lst); | |||
void serialize(std::ostream &os) const override; | |||
void parse(std::istream &os) override; | |||
@@ -35,7 +35,7 @@ struct SRFObject: SRF { | |||
struct SRFArray: SRF { | |||
SRFArray() {} | |||
SRFArray(std::initializer_list<SRF *> &lst); | |||
SRFArray(std::initializer_list<SRF *> lst); | |||
void serialize(std::ostream &os) const override; | |||
void parse(std::istream &os) override; | |||
@@ -55,6 +55,28 @@ struct SRFString: SRF { | |||
std::string val; | |||
}; | |||
struct SRFByte: SRF { | |||
SRFByte(): val(0) {} | |||
SRFByte(uint8_t v): val(v) {} | |||
void serialize(std::ostream &os) const override; | |||
void parse(std::istream &os) override; | |||
std::ostream &pretty(std::ostream &os) const override; | |||
uint8_t val; | |||
}; | |||
struct SRFWord: SRF { | |||
SRFWord(): val(0) {} | |||
SRFWord(uint16_t v): val(v) {} | |||
void serialize(std::ostream &os) const override; | |||
void parse(std::istream &os) override; | |||
std::ostream &pretty(std::ostream &os) const override; | |||
uint16_t val; | |||
}; | |||
struct SRFInt: SRF { | |||
SRFInt(): val(0) {} | |||
SRFInt(int32_t v): val(v) {} |
@@ -3,6 +3,8 @@ | |||
#include <vector> | |||
#include <utility> | |||
#include <memory> | |||
#include <map> | |||
#include <set> | |||
#include "common.h" | |||
#include "Chunk.h" | |||
@@ -30,9 +32,11 @@ public: | |||
Chunk &getChunk(ChunkPos pos); | |||
void setTileID(TilePos pos, Tile::ID id); | |||
void setTile(TilePos pos, const std::string &name); | |||
Tile::ID getTileID(TilePos pos); | |||
Tile &getTile(TilePos pos); | |||
Entity &spawnPlayer(); | |||
void breakBlock(TilePos pos); | |||
void draw(Win &win); | |||
void update(float dt); | |||
@@ -46,6 +50,7 @@ public: | |||
private: | |||
std::map<std::pair<int, int>, Chunk> chunks_; | |||
std::set<Chunk *> active_chunks_; | |||
std::vector<std::unique_ptr<Entity>> entities_; | |||
std::vector<TilePos> debug_boxes_; | |||
}; |
@@ -11,9 +11,7 @@ namespace Swan { | |||
sf::Uint8 *Chunk::renderbuf = new sf::Uint8[CHUNK_WIDTH * TILE_SIZE * CHUNK_HEIGHT * TILE_SIZE * 4]; | |||
Tile::ID *Chunk::getTileData() { | |||
if (compressed_size_ != -1) | |||
decompress(); | |||
keepActive(); | |||
return (Tile::ID *)data_.get(); | |||
} | |||
@@ -26,15 +24,14 @@ void Chunk::setTileID(RelPos pos, Tile::ID id) { | |||
} | |||
void Chunk::drawBlock(RelPos pos, const Tile &t) { | |||
if (compressed_size_ != -1) | |||
decompress(); | |||
keepActive(); | |||
visuals_->tex_.update(*t.image, pos.x_ * TILE_SIZE, pos.y_ * TILE_SIZE); | |||
visuals_->dirty_ = true; | |||
} | |||
void Chunk::compress() { | |||
if (compressed_size_ != -1) | |||
if (isCompressed()) | |||
return; | |||
sf::Clock clock; | |||
@@ -68,6 +65,11 @@ void Chunk::compress() { | |||
} | |||
void Chunk::decompress() { | |||
if (!isCompressed()) | |||
return; | |||
sf::Clock clock; | |||
uint8_t *dest = new uint8_t[CHUNK_WIDTH * CHUNK_HEIGHT * sizeof(Tile::ID)]; | |||
uLongf destlen = CHUNK_WIDTH * CHUNK_HEIGHT * sizeof(Tile::ID); | |||
int ret = uncompress( | |||
@@ -86,6 +88,9 @@ void Chunk::decompress() { | |||
visuals_->sprite_ = sf::Sprite(); | |||
visuals_->dirty_ = true; | |||
need_render_ = true; | |||
fprintf(stderr, "Decompressed chunk %i,%i from %li bytes to %lu bytes in %.3fs.\n", | |||
pos_.x_, pos_.y_, compressed_size_, CHUNK_WIDTH * CHUNK_HEIGHT * sizeof(Tile::ID), | |||
clock.getElapsedTime().asSeconds()); | |||
compressed_size_ = -1; | |||
} | |||
@@ -118,8 +123,8 @@ void Chunk::render(const Context &ctx) { | |||
} | |||
void Chunk::draw(const Context &ctx, Win &win) { | |||
if (compressed_size_ != -1) | |||
decompress(); | |||
if (isCompressed()) | |||
return; | |||
if (need_render_) { | |||
render(ctx); | |||
@@ -135,4 +140,25 @@ void Chunk::draw(const Context &ctx, Win &win) { | |||
win.draw(visuals_->sprite_); | |||
} | |||
void Chunk::tick() { | |||
if (active_timer_ == 0) | |||
return; | |||
active_timer_ -= 1; | |||
if (active_timer_ == 0) { | |||
compress(); | |||
} | |||
} | |||
bool Chunk::keepActive() { | |||
bool wasActive = isActive(); | |||
active_timer_ = ACTIVE_TIMEOUT; | |||
if (wasActive) | |||
return false; | |||
decompress(); | |||
return true; | |||
} | |||
} |
@@ -6,15 +6,17 @@ enum class Type { | |||
OBJECT = 0, | |||
ARRAY = 1, | |||
STRING = 2, | |||
INT = 3, | |||
FLOAT = 4, | |||
DOUBLE = 5, | |||
NONE = 6, | |||
BYTE_ARRAY = 7, | |||
WORD_ARRAY = 8, | |||
INT_ARRAY = 9, | |||
FLOAT_ARRAY = 10, | |||
DOUBLE_ARRAY = 11, | |||
BYTE = 3, | |||
WORD = 4, | |||
INT = 5, | |||
FLOAT = 6, | |||
DOUBLE = 7, | |||
NONE = 8, | |||
BYTE_ARRAY = 9, | |||
WORD_ARRAY = 10, | |||
INT_ARRAY = 11, | |||
FLOAT_ARRAY = 12, | |||
DOUBLE_ARRAY = 13, | |||
}; | |||
static void writeByte(std::ostream &os, uint8_t val) { | |||
@@ -123,6 +125,10 @@ SRF *SRF::read(std::istream &is) { | |||
srf = new SRFArray(); break; | |||
case Type::STRING: | |||
srf = new SRFString(); break; | |||
case Type::BYTE: | |||
srf = new SRFByte(); break; | |||
case Type::WORD: | |||
srf = new SRFWord(); break; | |||
case Type::INT: | |||
srf = new SRFInt(); break; | |||
case Type::FLOAT: | |||
@@ -147,7 +153,7 @@ SRF *SRF::read(std::istream &is) { | |||
return srf; | |||
} | |||
SRFObject::SRFObject(std::initializer_list<std::pair<std::string, SRF *>> &lst) { | |||
SRFObject::SRFObject(std::initializer_list<std::pair<std::string, SRF *>> lst) { | |||
for (auto &[k, v]: lst) | |||
val[k] = std::unique_ptr<SRF>(v); | |||
} | |||
@@ -186,7 +192,7 @@ std::ostream &SRFObject::pretty(std::ostream &os) const { | |||
return os << " }"; | |||
} | |||
SRFArray::SRFArray(std::initializer_list<SRF *> &lst) { | |||
SRFArray::SRFArray(std::initializer_list<SRF *> lst) { | |||
for (auto &v: lst) | |||
val.push_back(std::unique_ptr<SRF>(v)); | |||
} | |||
@@ -236,6 +242,38 @@ std::ostream &SRFString::pretty(std::ostream &os) const { | |||
return os << '"' << val << '"'; | |||
} | |||
void SRFByte::serialize(std::ostream &os) const { | |||
writeByte(os, (uint8_t)Type::BYTE); | |||
writeByte(os, val); | |||
} | |||
void SRFByte::parse(std::istream &is) { | |||
val = readByte(is); | |||
} | |||
std::ostream &SRFByte::pretty(std::ostream &os) const { | |||
return os << "0x" | |||
<< hexchr((val & 0xf0) >> 4) | |||
<< hexchr((val & 0x0f) >> 0); | |||
} | |||
void SRFWord::serialize(std::ostream &os) const { | |||
writeByte(os, (uint8_t)Type::WORD); | |||
writeWord(os, val); | |||
} | |||
void SRFWord::parse(std::istream &is) { | |||
val = readWord(is); | |||
} | |||
std::ostream &SRFWord::pretty(std::ostream &os) const { | |||
return os << "0x" | |||
<< hexchr((val & 0xf000) >> 12) | |||
<< hexchr((val & 0x0f00) >> 8) | |||
<< hexchr((val & 0x00f0) >> 4) | |||
<< hexchr((val & 0x000f) >> 0); | |||
} | |||
void SRFInt::serialize(std::ostream &os) const { | |||
writeByte(os, (uint8_t)Type::INT); | |||
writeInt(os, val); | |||
@@ -300,7 +338,9 @@ void SRFByteArray::parse(std::istream &is) { | |||
std::ostream &SRFByteArray::pretty(std::ostream &os) const { | |||
os << "byte[ " << std::hex; | |||
for (auto v: val) { | |||
os << hexchr((v & 0xf0) >> 4) << hexchr((v & 0x0f) >> 0) << ' '; | |||
os | |||
<< hexchr((v & 0xf0) >> 4) | |||
<< hexchr((v & 0x0f) >> 0) << ' '; | |||
} | |||
return os << ']'; |
@@ -8,14 +8,12 @@ namespace Swan { | |||
static bool chunkLine(int l, sf::Clock &clock, WorldPlane &plane, ChunkPos &abspos, const Vec2i &dir) { | |||
for (int i = 0; i < l; ++i) { | |||
if (!plane.hasChunk(abspos)) { | |||
plane.getChunk(abspos); | |||
// Don't blow our frame budget on generating chunks, | |||
// but generate as many as possible within the budget | |||
if (clock.getElapsedTime().asSeconds() > 1.0 / 100) | |||
return true; | |||
} | |||
plane.getChunk(abspos); | |||
// Don't blow our frame budget on generating chunks, | |||
// but generate as many as possible within the budget | |||
if (clock.getElapsedTime().asSeconds() > 1.0 / 100) | |||
return true; | |||
abspos += dir; | |||
} | |||
@@ -53,7 +53,10 @@ Chunk &WorldPlane::getChunk(ChunkPos pos) { | |||
if (iter == chunks_.end()) { | |||
iter = chunks_.emplace(pos, Chunk(pos)).first; | |||
gen_->genChunk(*this, iter->second); | |||
active_chunks_.insert(&iter->second); | |||
iter->second.render(getContext()); | |||
} else if (iter->second.keepActive()) { | |||
active_chunks_.insert(&iter->second); | |||
} | |||
return iter->second; | |||
@@ -64,22 +67,45 @@ void WorldPlane::setTileID(TilePos pos, Tile::ID id) { | |||
Chunk::RelPos rp = relPos(pos); | |||
chunk.setTileID(rp, id); | |||
chunk.drawBlock(rp, world_->getTileByID(id)); | |||
if (active_chunks_.find(&chunk) == active_chunks_.end()) | |||
active_chunks_.insert(&chunk); | |||
} | |||
void WorldPlane::setTile(TilePos pos, const std::string &name) { | |||
setTileID(pos, world_->getTileID(name)); | |||
} | |||
Tile &WorldPlane::getTile(TilePos pos) { | |||
Tile::ID WorldPlane::getTileID(TilePos pos) { | |||
Chunk &chunk = getChunk(chunkPos(pos)); | |||
Tile::ID id = chunk.getTileID(relPos(pos)); | |||
return world_->getTileByID(id); | |||
if (active_chunks_.find(&chunk) == active_chunks_.end()) | |||
active_chunks_.insert(&chunk); | |||
return chunk.getTileID(relPos(pos)); | |||
} | |||
Tile &WorldPlane::getTile(TilePos pos) { | |||
return world_->getTileByID(getTileID(pos)); | |||
} | |||
Entity &WorldPlane::spawnPlayer() { | |||
return gen_->spawnPlayer(*this); | |||
} | |||
void WorldPlane::breakBlock(TilePos pos) { | |||
Tile &t = getTile(pos); | |||
setTile(pos, "core::air"); | |||
if (t.dropped_item != "") { | |||
spawnEntity("core::item-stack", SRFArray{ | |||
new SRFFloatArray{ pos.x_ + 0.5f, pos.y_ + 0.5f }, | |||
new SRFString{ t.dropped_item }, | |||
}); | |||
} | |||
} | |||
void WorldPlane::draw(Win &win) { | |||
const Vec2 &ppos = world_->player_->getPos(); | |||
ChunkPos pcpos = ChunkPos( | |||
@@ -88,9 +114,9 @@ void WorldPlane::draw(Win &win) { | |||
for (int x = -1; x <= 1; ++x) { | |||
for (int y = -1; y <= 1; ++y) { | |||
auto chunk = chunks_.find(pcpos + ChunkPos(x, y)); | |||
if (chunk != chunks_.end()) | |||
chunk->second.draw(getContext(), win); | |||
auto iter = chunks_.find(pcpos + ChunkPos(x, y)); | |||
if (iter != chunks_.end()) | |||
iter->second.draw(getContext(), win); | |||
} | |||
} | |||
@@ -111,13 +137,21 @@ void WorldPlane::draw(Win &win) { | |||
void WorldPlane::update(float dt) { | |||
debug_boxes_.clear(); | |||
for (auto &ent: entities_) | |||
ent->update(getContext(), dt); | |||
// Don't use iterators, because an entity's update method might push_back to entities | |||
for (size_t len = entities_.size(), i = 0; i < len; ++i) | |||
entities_[i]->update(getContext(), dt); | |||
} | |||
void WorldPlane::tick() { | |||
for (auto &ent: entities_) | |||
ent->tick(); | |||
for (auto &chunk: active_chunks_) { | |||
chunk->tick(); | |||
if (!chunk->isActive()) | |||
active_chunks_.erase(chunk); | |||
} | |||
} | |||
void WorldPlane::debugBox(TilePos pos) { |