@@ -47,6 +47,6 @@ void ItemStackEntity::deserialize(const Swan::Context &ctx, const PackObject &ob | |||
Swan::Entity::PackObject ItemStackEntity::serialize(const Swan::Context &ctx, msgpack::zone &zone) { | |||
return { | |||
{ "pos", msgpack::object(body_.pos, zone) }, | |||
{ "tile", msgpack::object(item_->name_, zone) }, | |||
{ "tile", msgpack::object(item_->name, zone) }, | |||
}; | |||
} |
@@ -62,7 +62,7 @@ void PlayerEntity::update(const Swan::Context &ctx, float dt) { | |||
} | |||
// Fall down faster than we went up | |||
if (!physics_.on_ground && (!jumpPressed || physics_.vel.y > 0)) | |||
if (!physics_.onGround && (!jumpPressed || physics_.vel.y > 0)) | |||
physics_.force += Swan::Vec2(0, DOWN_FORCE); | |||
if (state_ != oldState) |
@@ -22,50 +22,50 @@ public: | |||
registerTile({ | |||
.name = "stone", | |||
.image = "core/tile/stone", | |||
.dropped_item = "core::stone", | |||
.image = "core::tile/stone", | |||
.droppedItem = "core::stone", | |||
}); | |||
registerTile({ | |||
.name = "dirt", | |||
.image = "core/tile/dirt", | |||
.dropped_item = "core::dirt", | |||
.image = "core::tile/dirt", | |||
.droppedItem = "core::dirt", | |||
}); | |||
registerTile({ | |||
.name = "grass", | |||
.image = "core/tile/grass", | |||
.dropped_item = "core::dirt", | |||
.image = "core::tile/grass", | |||
.droppedItem = "core::dirt", | |||
}); | |||
registerTile({ | |||
.name = "tree-trunk", | |||
.image = "core/tile/tree-trunk", | |||
.dropped_item = "core::tree-trunk", | |||
.image = "core::tile/tree-trunk", | |||
.droppedItem = "core::tree-trunk", | |||
}); | |||
registerTile({ | |||
.name = "leaves", | |||
.image = "core/tile/leaves", | |||
.image = "core::tile/leaves", | |||
}); | |||
registerTile({ | |||
.name = "torch", | |||
.image = "core/tile/torch", | |||
.is_solid = false, | |||
.light_level = 80/255.0, | |||
.image = "core::tile/torch", | |||
.isSolid = false, | |||
.lightLevel = 80/255.0, | |||
}); | |||
registerItem({ | |||
.name = "stone", | |||
.image = "core/tile/stone", | |||
.image = "core::tile/stone", | |||
}); | |||
registerItem({ | |||
.name = "dirt", | |||
.image = "core/tile/dirt", | |||
.image = "core::tile/dirt", | |||
}); | |||
registerItem({ | |||
.name = "grass", | |||
.image = "core/tile/grass", | |||
.image = "core::tile/grass", | |||
}); | |||
registerItem({ | |||
.name = "tree-trunk", | |||
.image = "core/tile/tree-trunk", | |||
.image = "core::tile/tree-trunk", | |||
}); | |||
registerWorldGen<DefaultWorldGen>("default"); | |||
@@ -75,9 +75,9 @@ public: | |||
} | |||
void onTileBreak(const Swan::Context &ctx, Swan::TilePos pos, Swan::Tile &tile) { | |||
if (tile.droppedItem_) { | |||
if (tile.droppedItem) { | |||
ctx.plane.spawnEntity<ItemStackEntity>( | |||
ctx, (Swan::Vec2)pos + Swan::Vec2{0.5, 0.5}, *tile.droppedItem_); | |||
ctx, (Swan::Vec2)pos + Swan::Vec2{0.5, 0.5}, *tile.droppedItem); | |||
} | |||
} | |||
@@ -25,7 +25,7 @@ class ResourceBuilder { | |||
public: | |||
ResourceBuilder(Renderer &rnd): rnd_(rnd) {} | |||
RenderSprite addSprite(std::string name, void *data, int width, int height, int fh); | |||
RenderSprite addSprite(std::string name, void *data, int width, int height, int frameHeight); | |||
RenderSprite addSprite(std::string name, void *data, int width, int height); | |||
void addTile(Renderer::TileID id, void *data, int frames = 1); | |||
void addTile(Renderer::TileID id, std::unique_ptr<unsigned char[]> data, int frames = 1); |
@@ -3,26 +3,25 @@ add_library(libswan SHARED | |||
src/traits/InventoryTrait.cc | |||
src/traits/PhysicsTrait.cc | |||
src/Animation.cc | |||
src/assets.cc | |||
src/Chunk.cc | |||
src/Clock.cc | |||
src/drawutil.cc | |||
src/Entity.cc | |||
src/Game.cc | |||
src/gfxutil.cc | |||
src/Item.cc | |||
src/ItemStack.cc | |||
src/LightServer.cc | |||
src/Mod.cc | |||
src/OS.cc | |||
src/Resource.cc | |||
src/Tile.cc | |||
src/World.cc | |||
src/WorldPlane.cc) | |||
target_include_directories(libswan | |||
PUBLIC "include" | |||
PRIVATE "include/swan") | |||
set_target_properties(libswan PROPERTIES OUTPUT_NAME swan) | |||
target_link_libraries(libswan swan-common ${libraries}) | |||
target_link_libraries(libswan swan-common libcygnet ${libraries}) | |||
install(TARGETS libswan DESTINATION swan/libswan) | |||
@@ -5,6 +5,7 @@ | |||
#include <string> | |||
#include <optional> | |||
#include <SDL.h> | |||
#include <cygnet/Renderer.h> | |||
#include "common.h" | |||
#include "Resource.h" | |||
@@ -19,8 +20,7 @@ public: | |||
win_(win), | |||
mousePos_(0, 0) {} | |||
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> &modPaths); | |||
void onKeyDown(SDL_Keysym sym) { | |||
pressedKeys_[sym.scancode] = true; | |||
@@ -73,6 +73,7 @@ public: | |||
std::unique_ptr<Tile> invalidTile_ = NULL; | |||
std::unique_ptr<Item> invalidItem_ = NULL; | |||
Win &win_; | |||
Cygnet::Renderer renderer_; | |||
private: | |||
std::bitset<SDL_NUM_SCANCODES> pressedKeys_; |
@@ -3,6 +3,7 @@ | |||
#include <string> | |||
#include "Resource.h" | |||
#include "Tile.h" | |||
namespace Swan { | |||
@@ -14,25 +15,12 @@ public: | |||
int maxStack = 64; | |||
}; | |||
Item(const ResourceManager &resources, const Builder &builder): | |||
name(builder.name), image(resources.getImage(builder.image)), | |||
maxStack(builder.maxStack) {} | |||
const Tile::ID id; | |||
const std::string name; | |||
const ImageResource ℑ | |||
const int maxStack; | |||
static std::unique_ptr<Item> createInvalid(Context &ctx); | |||
// For testing, we want to be able to create Items without an actual ImageResource. | |||
// Tests can create a MockItem class which inherits from Item and uses this ctor, | |||
// as long as the test never does anything which tries to follow the image_ member. | |||
// Eventually, this should become unnecessary, because we don't need to require | |||
// a complete ImageResource for a headless server, but for now, this will suffice. | |||
protected: | |||
Item(const Builder &builder): | |||
name(builder.name), image(*(ImageResource *)this), | |||
maxStack(builder.maxStack) {} | |||
Item(Tile::ID id, std::string name, const Builder &builder): | |||
id(id), name(name), maxStack(builder.maxStack) {} | |||
}; | |||
} |
@@ -23,14 +23,14 @@ public: | |||
Mod(std::string name): name_(std::move(name)) {} | |||
virtual ~Mod() = default; | |||
void registerImage(const std::string &id); | |||
void registerTile(Tile::Builder tile); | |||
void registerItem(Item::Builder item); | |||
void registerWorldGen(const std::string &name, std::unique_ptr<WorldGen::Factory> gen); | |||
void registerWorldGen(std::string name, std::unique_ptr<WorldGen::Factory> gen); | |||
void registerSprite(std::string sprite); | |||
template<typename WG> | |||
void registerWorldGen(const std::string &name) { | |||
worldgens_.push_back(WorldGen::Factory{ | |||
void registerWorldGen(std::string name) { | |||
worldGens_.push_back(WorldGen::Factory{ | |||
.name = name_ + "::" + name, | |||
.create = [](World &world) -> std::unique_ptr<WorldGen> { | |||
return std::make_unique<WG>(world); | |||
@@ -55,7 +55,8 @@ public: | |||
std::vector<std::string> images_; | |||
std::vector<Tile::Builder> tiles_; | |||
std::vector<Item::Builder> items_; | |||
std::vector<WorldGen::Factory> worldgens_; | |||
std::vector<std::string> sprites_; | |||
std::vector<WorldGen::Factory> worldGens_; | |||
std::vector<EntityCollection::Factory> entities_; | |||
}; | |||
@@ -72,12 +73,6 @@ public: | |||
mod_.reset(); | |||
} | |||
Iter<std::unique_ptr<ImageResource>> buildImages(SDL_Renderer *renderer); | |||
Iter<std::unique_ptr<Tile>> buildTiles(const ResourceManager &resources); | |||
Iter<std::unique_ptr<Item>> buildItems(const ResourceManager &resources); | |||
Iter<WorldGen::Factory> getWorldGens(); | |||
Iter<EntityCollection::Factory> getEntities(); | |||
std::unique_ptr<Mod> mod_; | |||
std::string path_; | |||
OS::Dynlib dynlib_; |
@@ -5,7 +5,6 @@ | |||
#include <optional> | |||
#include <memory> | |||
#include "Item.h" | |||
#include "Resource.h" | |||
namespace Swan { | |||
@@ -22,20 +21,16 @@ public: | |||
std::optional<std::string> droppedItem = std::nullopt; | |||
}; | |||
Tile(const ResourceManager &resources, const Builder &builder): | |||
name(builder.name), image(resources.getImage(builder.image)), | |||
isSolid(builder.isSolid), lightLevel(builder.lightLevel), | |||
droppedItem(builder.droppedItem) {} | |||
const ID id; | |||
const std::string name; | |||
const ImageResource ℑ | |||
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> createAir(const ResourceManager &ctx); | |||
static ID INVALID_ID; | |||
Tile(ID id, std::string name, const Builder &builder): | |||
id(id), name(name), | |||
isSolid(builder.isSolid), lightLevel(builder.lightLevel), | |||
droppedItem(builder.droppedItem) {} | |||
}; | |||
} |
@@ -5,6 +5,8 @@ | |||
#include <string> | |||
#include <random> | |||
#include <SDL.h> | |||
#include <cygnet/Renderer.h> | |||
#include <cygnet/ResourceManager.h> | |||
#include "common.h" | |||
#include "Item.h" | |||
@@ -23,9 +25,13 @@ class Game; | |||
class World { | |||
public: | |||
World(Game *game, unsigned long randSeed); | |||
static constexpr Tile::ID INVALID_TILE_ID = 0; | |||
static constexpr char INVALID_TILE_NAME[] = "@::invalid"; | |||
static constexpr Tile::ID AIR_TILE_ID = 1; | |||
static constexpr char AIR_TILE_NAME[] = "@::air"; | |||
World(Game *game, unsigned long randSeed, std::vector<std::string> modPaths); | |||
void addMod(ModWrapper &&mod); | |||
void setWorldGen(std::string gen); | |||
void spawnPlayer(); | |||
@@ -33,7 +39,7 @@ public: | |||
WorldPlane &addPlane(const std::string &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 &getTile(const std::string &name); | |||
Item &getItem(const std::string &name); | |||
@@ -48,22 +54,22 @@ public: | |||
evtTileBreak_; | |||
// World owns all mods | |||
Game *game_; // TODO: reference, not pointer | |||
std::mt19937 random_; | |||
std::vector<ModWrapper> mods_; | |||
//ResourceManager resources_; | |||
Cygnet::ResourceManager resources_; | |||
// World owns tiles and items, the mod just has Builder objects | |||
std::vector<std::unique_ptr<Tile>> tiles_; | |||
std::vector<Tile> tiles_; | |||
std::unordered_map<std::string, Tile::ID> tilesMap_; | |||
std::unordered_map<std::string, std::unique_ptr<Item>> items_; | |||
std::unordered_map<std::string, Item> items_; | |||
// Mods give us factories to create new world gens and new entity collections | |||
std::unordered_map<std::string, WorldGen::Factory> worldgenFactories_; | |||
std::vector<EntityCollection::Factory> entCollFactories_; | |||
std::unordered_map<std::string, WorldGen::Factory> worldGenFactories_; | |||
std::unordered_map<std::string, EntityCollection::Factory> entCollFactories_; | |||
BodyTrait::Body *player_; | |||
Game *game_; | |||
std::mt19937 random_; | |||
ResourceManager resources_; | |||
private: | |||
class ChunkRenderer { | |||
@@ -71,6 +77,9 @@ private: | |||
void tick(WorldPlane &plane, ChunkPos abspos); | |||
}; | |||
std::vector<ModWrapper> loadMods(std::vector<std::string> paths); | |||
Cygnet::ResourceManager buildResources(); | |||
ChunkRenderer chunkRenderer_; | |||
WorldPlane::ID currentPlane_; | |||
std::vector<std::unique_ptr<WorldPlane>> planes_; |
@@ -0,0 +1,20 @@ | |||
#include <memory> | |||
#include <unordered_map> | |||
#include <string> | |||
#include "util.h" | |||
namespace Swan { | |||
struct ImageAsset { | |||
int width; | |||
int frameHeight; | |||
int frameCount; | |||
std::unique_ptr<unsigned char[]> data; | |||
}; | |||
Result<ImageAsset> loadImageAsset( | |||
const std::unordered_map<std::string, std::string> modPaths, | |||
std::string path); | |||
} |
@@ -1,11 +1,11 @@ | |||
#pragma once | |||
#pragma once | |||
#include <optional> | |||
#include <functional> | |||
#include <memory> | |||
#include <chrono> | |||
#include <type_traits> | |||
#include <string> | |||
#include <stddef.h> | |||
namespace Swan { | |||
@@ -42,6 +42,96 @@ public: | |||
~Deferred() { Func(); } | |||
}; | |||
inline struct ResultOk {} Ok; | |||
inline struct ResultErr {} Err; | |||
// Result type for potential errors | |||
template<typename T, typename Err = std::string> | |||
class Result { | |||
public: | |||
Result(ResultOk, T &&val): isOk_(true), v_(ResultOk{}, std::move(val)) {} | |||
Result(ResultErr, Err &&err): isOk_(false), v_(ResultErr{}, std::move(err)) {} | |||
Result(const Result &other) { | |||
isOk_ = other.isOk_; | |||
if (other.isOk_) { | |||
new (&v_.val) T(other.v_.val); | |||
} else { | |||
new (&v_.err) T(other.v_.err); | |||
} | |||
} | |||
Result(Result &&other) { | |||
isOk_ = other.isOk_; | |||
if (other.isOk_) { | |||
new (&v_.val) T(std::move(other.v_.val)); | |||
} else { | |||
new (&v_.err) T(std::move(other.v_.err)); | |||
} | |||
} | |||
~Result() { | |||
destroy(); | |||
} | |||
Result<T, Err> &operator=(const Result<T, Err> &other) { | |||
destroy(); | |||
isOk_ = other.isOk_; | |||
if (other.isOk_) { | |||
new (&v_.val) T(other.v_.val); | |||
} else { | |||
new (&v_.err) Err(other.v_.err); | |||
} | |||
return *this; | |||
} | |||
Result<T, Err> &operator=(Result<T, Err> &&other) { | |||
destroy(); | |||
isOk_ = other.isOk_; | |||
if (other.isOk_) { | |||
new (&v_.val) T(std::move(other.v_.val)); | |||
} else { | |||
new (&v_.err) Err(std::move(other.v_.err)); | |||
} | |||
return *this; | |||
} | |||
operator bool() { return isOk_; } | |||
bool isOk() { return isOk_; } | |||
Err &err() { return v_.err; } | |||
T &value() { return v_.val; } | |||
T *operator->() { | |||
return &v_.val; | |||
} | |||
T &operator*() { | |||
return v_.val; | |||
} | |||
private: | |||
void destroy() { | |||
if (isOk_) { | |||
v_.val.~T(); | |||
} else { | |||
v_.err.~Err(); | |||
} | |||
} | |||
bool isOk_; | |||
union U { | |||
U() {} | |||
U(ResultOk, T &&val): val(std::move(val)) {} | |||
U(ResultOk, const T &val): val(val) {} | |||
U(ResultErr, Err &&err): err(std::move(err)) {} | |||
U(ResultErr, const Err &err): err(err) {} | |||
~U() {} | |||
T val; | |||
Err err; | |||
} v_; | |||
}; | |||
// Calling begin/end is stupid... | |||
template<typename T> | |||
auto callBegin(T &v) { |
@@ -11,7 +11,7 @@ void Animation::tick(float dt) { | |||
timer_ += interval_; | |||
frame_ += 1; | |||
if (frame_ >= resource_.num_frames_) | |||
if (frame_ >= resource_.numFrames_) | |||
frame_ = 0; | |||
} | |||
} |
@@ -124,7 +124,7 @@ void Chunk::render(const Context &ctx, SDL_Renderer *rnd) { | |||
} | |||
// We're caching tiles so we don't have to world.getTileByID() every time | |||
Tile::ID prevID = Tile::INVALID_ID; | |||
Tile::ID prevID = World::INVALID_TILE_ID; | |||
Tile *tile = ctx.game.invalidTile_.get(); | |||
// Fill tile texture |
@@ -11,27 +11,8 @@ | |||
namespace Swan { | |||
std::optional<ModWrapper> Game::loadMod(std::string path, World &world) { | |||
OS::Dynlib dl(path + "/mod"); | |||
auto create = dl.get<Mod *(*)(World &)>("mod_create"); | |||
if (create == NULL) { | |||
warn << path << ": No 'mod_create' function!"; | |||
return std::nullopt; | |||
} | |||
std::unique_ptr<Mod> mod(create(world)); | |||
return std::make_optional<ModWrapper>( | |||
std::move(mod), std::move(path), std::move(dl)); | |||
} | |||
void Game::createWorld(const std::string &worldgen, const std::vector<std::string> &modpaths) { | |||
world_.reset(new World(this, time(NULL))); | |||
for (auto &modpath: modpaths) { | |||
auto mod = loadMod(modpath, *world_); | |||
if (mod) | |||
world_->addMod(std::move(*mod)); | |||
} | |||
void Game::createWorld(const std::string &worldgen, const std::vector<std::string> &modPaths) { | |||
world_.reset(new World(this, time(NULL), modPaths)); | |||
world_->setWorldGen(worldgen); | |||
world_->setCurrentPlane(world_->addPlane()); |
@@ -1,16 +0,0 @@ | |||
#include "Item.h" | |||
#include "Resource.h" | |||
#include "Game.h" | |||
#include "common.h" | |||
namespace Swan { | |||
std::unique_ptr<Item> Item::createInvalid(Context &ctx) { | |||
return std::make_unique<Item>(ctx.resources, Builder{ | |||
.name = "@::invalid", | |||
.image = "@::invalid", | |||
}); | |||
} | |||
} |
@@ -8,56 +8,19 @@ | |||
namespace Swan { | |||
void Mod::registerImage(const std::string &id) { | |||
images_.push_back(name_ + "/" + id); | |||
info << " Adding image: " << images_.back(); | |||
} | |||
void Mod::registerTile(Tile::Builder tile) { | |||
tile.name = name_ + "::" + tile.name; | |||
tiles_.push_back(tile); | |||
info << " Adding tile: " << tile.name; | |||
info << " Adding tile: " << name_ << "::" << tile.name; | |||
} | |||
void Mod::registerItem(Item::Builder item) { | |||
item.name = name_ + "::" + item.name; | |||
items_.push_back(item); | |||
info << " Adding item: " << item.name; | |||
} | |||
Iter<std::unique_ptr<ImageResource>> ModWrapper::buildImages(SDL_Renderer *renderer) { | |||
return map(begin(mod_->images_), end(mod_->images_), | |||
[renderer, this](const std::string &id) { | |||
return std::make_unique<ImageResource>(renderer, path_, id); | |||
}); | |||
} | |||
Iter<std::unique_ptr<Tile>> ModWrapper::buildTiles(const ResourceManager &resources) { | |||
return map(begin(mod_->tiles_), end(mod_->tiles_), | |||
[&](const Tile::Builder &builder) { | |||
return std::make_unique<Tile>(resources, builder); | |||
}); | |||
} | |||
Iter<std::unique_ptr<Item>> ModWrapper::buildItems(const ResourceManager &resources) { | |||
return map(begin(mod_->items_), end(mod_->items_), | |||
[&](const Item::Builder &builder) { | |||
return std::make_unique<Item>(resources, builder); | |||
}); | |||
} | |||
Iter<WorldGen::Factory> ModWrapper::getWorldGens() { | |||
return map(begin(mod_->worldgens_), end(mod_->worldgens_), | |||
[](WorldGen::Factory &fact) { | |||
return fact; | |||
}); | |||
info << " Adding item: " << name_ << "::" << item.name; | |||
} | |||
Iter<EntityCollection::Factory> ModWrapper::getEntities() { | |||
return map(begin(mod_->entities_), end(mod_->entities_), | |||
[](EntityCollection::Factory &fact){ | |||
return fact; | |||
}); | |||
void Mod::registerSprite(std::string path) { | |||
sprites_.push_back(path); | |||
info << "Adding sprite: " << name_ << "::" << path; | |||
} | |||
} |
@@ -1,25 +0,0 @@ | |||
#include "Tile.h" | |||
#include "common.h" | |||
#include <Game.h> | |||
namespace Swan { | |||
Tile::ID Tile::INVALID_ID = 0; | |||
std::unique_ptr<Tile> Tile::createInvalid(const ResourceManager &resources) { | |||
return std::make_unique<Tile>(resources, Builder{ | |||
.name = "@::invalid", | |||
.image = "@::invalid", | |||
}); | |||
} | |||
std::unique_ptr<Tile> Tile::createAir(const ResourceManager &resources) { | |||
return std::make_unique<Tile>(resources, Builder{ | |||
.name = "@::air", | |||
.image = "@::air", | |||
.isSolid = false, | |||
}); | |||
} | |||
} |
@@ -1,11 +1,13 @@ | |||
#include "World.h" | |||
#include <algorithm> | |||
#include <tuple> | |||
#include "log.h" | |||
#include "Game.h" | |||
#include "Win.h" | |||
#include "Clock.h" | |||
#include "assets.h" | |||
namespace Swan { | |||
@@ -16,21 +18,199 @@ static void chunkLine(int l, WorldPlane &plane, ChunkPos &abspos, const Vec2i &d | |||
} | |||
} | |||
World::World(Game *game, unsigned long randSeed): | |||
game_(game), random_(randSeed), resources_(game->win_) { | |||
std::vector<ModWrapper> World::loadMods(std::vector<std::string> paths) { | |||
std::vector<ModWrapper> mods; | |||
mods.reserve(paths.size()); | |||
std::unique_ptr<Tile> invalidTile = Tile::createInvalid(resources_); | |||
tilesMap_[invalidTile->name] = 0; | |||
for (auto &path: paths) { | |||
OS::Dynlib dl(path + "/mod"); | |||
auto create = dl.get<Mod *(*)(World &)>("mod_create"); | |||
if (create == NULL) { | |||
warn << path << ": No 'mod_create' function!"; | |||
continue; | |||
} | |||
// tiles_ is empty, so pushing back now will ensure invalid_tile | |||
// ends up at location 0 | |||
tiles_.push_back(std::move(invalidTile)); | |||
std::unique_ptr<Mod> mod(create(*this)); | |||
mods.push_back(ModWrapper(std::move(mod), std::move(path), std::move(dl))); | |||
} | |||
return mods; | |||
} | |||
Cygnet::ResourceManager World::buildResources() { | |||
Cygnet::ResourceBuilder builder(game_->renderer_); | |||
auto fillTileImage = [&](unsigned char *data, int r, int g, int b, int a) { | |||
for (size_t i = 0; i < TILE_SIZE * TILE_SIZE; ++i) { | |||
data[i * 4 + 0] = r; | |||
data[i * 4 + 1] = g; | |||
data[i * 4 + 2] = b; | |||
data[i * 4 + 2] = a; | |||
} | |||
}; | |||
struct ImageAsset fallbackImage = { | |||
.width = 32, | |||
.frameHeight = 32, | |||
.frameCount = 1, | |||
.data = std::make_unique<unsigned char[]>(TILE_SIZE * TILE_SIZE * 4), | |||
}; | |||
fillTileImage(fallbackImage.data.get(), | |||
PLACEHOLDER_RED, PLACEHOLDER_GREEN, PLACEHOLDER_BLUE, 255); | |||
auto airImage = std::make_unique<unsigned char[]>(TILE_SIZE * TILE_SIZE * 4); | |||
fillTileImage(airImage.get(), | |||
PLACEHOLDER_RED, PLACEHOLDER_GREEN, PLACEHOLDER_BLUE, 255); | |||
// Let tile ID 0 be the invalid tile | |||
builder.addTile(INVALID_TILE_ID, fallbackImage.data.get()); | |||
tilesMap_[INVALID_TILE_NAME] = INVALID_TILE_ID; | |||
tiles_.push_back(Tile(INVALID_TILE_ID, INVALID_TILE_NAME, { | |||
.name = "", .image = "", // Not used in this case | |||
.isSolid = false, | |||
})); | |||
items_.emplace(INVALID_TILE_NAME, Item(INVALID_TILE_ID, INVALID_TILE_NAME, { | |||
.name = "", .image = "", // Not used in this case | |||
})); | |||
// ...And tile ID 1 be the air tile | |||
builder.addTile(AIR_TILE_ID, std::move(airImage)); | |||
tilesMap_[AIR_TILE_NAME] = AIR_TILE_ID; | |||
tiles_.push_back(Tile(AIR_TILE_ID, AIR_TILE_NAME, { | |||
.name = "", .image = "", // Not used in this case | |||
.isSolid = false, | |||
})); | |||
items_.emplace(AIR_TILE_NAME, Item(AIR_TILE_ID, AIR_TILE_NAME, { | |||
.name = "", .image = "", // Not used in this case | |||
})); | |||
// Assets are namespaced on the mod, so if something references, say, | |||
// "core::stone", we need to know which directory the "core" mod is in | |||
std::unordered_map<std::string, std::string> modPaths; | |||
for (auto &mod: mods_) { | |||
modPaths[mod.mod_->name_] = mod.path_; | |||
} | |||
auto loadTileImage = [&](std::string path) -> Result<ImageAsset> { | |||
// Don't force all tiles/items to have an associated image. | |||
// It could be that some tiles/items exist for a purpose which implies | |||
// it should never actually be visible. | |||
if (path == INVALID_TILE_NAME) { | |||
ImageAsset asset{ | |||
.width = 32, | |||
.frameHeight = 32, | |||
.frameCount = 1, | |||
.data = std::make_unique<unsigned char[]>(TILE_SIZE * TILE_SIZE * 4), | |||
}; | |||
memcpy(asset.data.get(), fallbackImage.data.get(), TILE_SIZE * TILE_SIZE * 4); | |||
return {Ok, std::move(asset)}; | |||
} | |||
auto image = loadImageAsset(modPaths, path); | |||
if (!image) { | |||
warn << '\'' << path << "': " << image.err(); | |||
return {Err, '\'' + path + "': " + image.err()}; | |||
} else if (image->width != TILE_SIZE) { | |||
warn << '\'' << path << "': Width must be " << TILE_SIZE << " pixels"; | |||
return {Err, '\'' + path + "': Width must be " + std::to_string(TILE_SIZE) + " pixels"}; | |||
} else { | |||
return image; | |||
} | |||
}; | |||
// Need to fill in every tile before we do items, | |||
// because all items will end up after all tiles in the tile atlas. | |||
// In the rendering system, there's no real difference between a tile | |||
// and an item. | |||
for (auto &mod: mods_) { | |||
for (auto &tileBuilder: mod.mod_->tiles_) { | |||
auto image = loadTileImage(tileBuilder.image); | |||
std::string tileName = mod.mod_->name_ + "::" + tileBuilder.name; | |||
Tile::ID tileId = tiles_.size(); | |||
if (image) { | |||
builder.addTile(tileId, std::move(image->data)); | |||
} else { | |||
warn << image.err(); | |||
builder.addTile(tileId, fallbackImage.data.get()); | |||
} | |||
tilesMap_[tileName] = tileId; | |||
tiles_.push_back(Tile(tileId, tileName, tileBuilder)); | |||
// All tiles should have an item. | |||
// Some items will be overwritten later my mod_->items, | |||
// but if not, this is their default item. | |||
items_.emplace(tileName, Item(tileId, tileName, { | |||
.name = "", .image = "", // Not used in this case | |||
})); | |||
} | |||
} | |||
// Put all items after all the tiles | |||
Tile::ID nextItemId = tiles_.size(); | |||
// Load all items which aren't just tiles in disguise. | |||
for (auto &mod: mods_) { | |||
for (auto &itemBuilder: mod.mod_->items_) { | |||
auto image = loadTileImage(itemBuilder.image); | |||
std::string itemName = mod.mod_->name_ + "::" + itemBuilder.name; | |||
Tile::ID itemId = nextItemId++; | |||
if (image) { | |||
builder.addTile(itemId, std::move(image->data)); | |||
} else { | |||
warn << image.err(); | |||
builder.addTile(itemId, fallbackImage.data.get()); | |||
} | |||
items_.emplace(itemName, Item(itemId, itemName, itemBuilder)); | |||
} | |||
} | |||
// Load sprites | |||
for (auto &mod: mods_) { | |||
for (auto spritePath: mod.mod_->sprites_) { | |||
std::string path = mod.mod_->name_ + "::" + spritePath; | |||
auto image = loadImageAsset(modPaths, path); | |||
if (image) { | |||
builder.addSprite( | |||
path, image->data.get(), image->width, | |||
image->frameHeight * image->frameCount, | |||
image->frameHeight); | |||
} else { | |||
warn << '\'' << path << "': " << image.err(); | |||
builder.addSprite( | |||
path, fallbackImage.data.get(), fallbackImage.width, | |||
fallbackImage.frameHeight * fallbackImage.frameCount, | |||
fallbackImage.frameHeight); | |||
} | |||
} | |||
} | |||
// Load world gens and entities | |||
for (auto &mod: mods_) { | |||
for (auto &worldGenFactory: mod.mod_->worldGens_) { | |||
std::string name = mod.mod_->name_ + "::" + worldGenFactory.name; | |||
worldGenFactories_.emplace(name, worldGenFactory); | |||
} | |||
for (auto &entCollFactory: mod.mod_->entities_) { | |||
std::string name = mod.mod_->name_ + "::" + entCollFactory.name; | |||
entCollFactories_.emplace(name, entCollFactory); | |||
} | |||
} | |||
// We're also going to need an air tile at location 1 | |||
tiles_.push_back(Tile::createAir(resources_)); | |||
tilesMap_["@::air"] = 1; | |||
return Cygnet::ResourceManager(std::move(builder)); | |||
} | |||
World::World(Game *game, unsigned long randSeed, std::vector<std::string> modPaths): | |||
game_(game), random_(randSeed), mods_(loadMods(std::move(modPaths))), | |||
resources_(buildResources()) {} | |||
void World::ChunkRenderer::tick(WorldPlane &plane, ChunkPos abspos) { | |||
ZoneScopedN("World::ChunkRenderer tick"); | |||
int l = 0; | |||
@@ -46,37 +226,6 @@ void World::ChunkRenderer::tick(WorldPlane &plane, ChunkPos abspos) { | |||
} | |||
} | |||
void World::addMod(ModWrapper &&mod) { | |||
info << "World: adding mod " << mod.mod_->name_; | |||
for (auto i: mod.buildImages(game_->win_.renderer_)) { | |||
resources_.addImage(std::move(i)); | |||
} | |||
for (auto t: mod.buildTiles(resources_)) { | |||
Tile::ID id = tiles_.size(); | |||
tilesMap_[t->name] = id; | |||
tiles_.push_back(std::move(t)); | |||
} | |||
for (auto i: mod.buildItems(resources_)) { | |||
items_[i->name] = std::move(i); | |||
} | |||
for (auto fact: mod.getWorldGens()) { | |||
worldgenFactories_.emplace( | |||
std::piecewise_construct, | |||
std::forward_as_tuple(fact.name), | |||
std::forward_as_tuple(fact)); | |||
} | |||
for (auto fact: mod.getEntities()) { | |||
entCollFactories_.push_back(fact); | |||
} | |||
mods_.push_back(std::move(mod)); | |||
} | |||
void World::setWorldGen(std::string gen) { | |||
defaultWorldGen_ = std::move(gen); | |||
} | |||
@@ -92,8 +241,8 @@ void World::setCurrentPlane(WorldPlane &plane) { | |||
WorldPlane &World::addPlane(const std::string &gen) { | |||
WorldPlane::ID id = planes_.size(); | |||
auto it = worldgenFactories_.find(gen); | |||
if (it == worldgenFactories_.end()) { | |||
auto it = worldGenFactories_.find(gen); | |||
if (it == worldGenFactories_.end()) { | |||
panic << "Tried to add plane with non-existant world gen " << gen << "!"; | |||
abort(); | |||
} | |||
@@ -101,7 +250,7 @@ WorldPlane &World::addPlane(const std::string &gen) { | |||
std::vector<std::unique_ptr<EntityCollection>> colls; | |||
colls.reserve(entCollFactories_.size()); | |||
for (auto &fact: entCollFactories_) { | |||
colls.emplace_back(fact.create(fact.name)); | |||
colls.emplace_back(fact.second.create(fact.second.name)); | |||
} | |||
WorldGen::Factory &factory = it->second; | |||
@@ -118,14 +267,14 @@ Item &World::getItem(const std::string &name) { | |||
return *game_->invalidItem_; | |||
} | |||
return *iter->second; | |||
return iter->second; | |||
} | |||
Tile::ID World::getTileID(const std::string &name) { | |||
auto iter = tilesMap_.find(name); | |||
if (iter == tilesMap_.end()) { | |||
warn << "Tried to get non-existant item " << name << "!"; | |||
return Tile::INVALID_ID; | |||
return INVALID_TILE_ID; | |||
} | |||
return iter->second; |
@@ -0,0 +1,70 @@ | |||
#include "assets.h" | |||
#include <SDL.h> | |||
#include <SDL_image.h> | |||
#include <cpptoml.h> | |||
#include <string.h> | |||
namespace Swan { | |||
Result<ImageAsset> loadImageAsset( | |||
const std::unordered_map<std::string, std::string> modPaths, | |||
std::string path) { | |||
auto sep = path.find("::"); | |||
if (sep == std::string::npos) { | |||
return {Err, "No '::' mod separator"}; | |||
} | |||
auto modPart = path.substr(0, sep); | |||
auto pathPart = path.substr(sep, path.size() - sep - 2); | |||
auto modPath = modPaths.find(modPart); | |||
if (modPath == modPaths.end()) { | |||
return {Err, "No mod named '" + modPart + '\''}; | |||
} | |||
std::string assetPath = modPath->second + "/assets/" + pathPart; | |||
std::string pngPath = assetPath + ".png"; | |||
std::string tomlPath = assetPath + ".toml"; | |||
CPtr<SDL_Surface, SDL_FreeSurface> surface(IMG_Load(pngPath.c_str())); | |||
if (!surface) { | |||
return {Err, "Loading image " + pngPath + " failed: " + SDL_GetError()}; | |||
} | |||
int frameHeight = surface->h; | |||
// Load TOML if it exists | |||
errno = ENOENT; // I don't know if ifstream is guaranteed to set errno | |||
std::ifstream tomlFile(tomlPath); | |||
if (tomlFile) { | |||
cpptoml::parser parser(tomlFile); | |||
try { | |||
auto toml = parser.parse(); | |||
frameHeight = toml->get_as<int>("height").value_or(frameHeight); | |||
} catch (cpptoml::parse_exception &exc) { | |||
return {Err, "Failed to parse toml file " + tomlPath + ": " + exc.what()}; | |||
} | |||
} else if (errno != ENOENT) { | |||
return {Err, "Couldn't open " + tomlPath + ": " + strerror(errno)}; | |||
} | |||
ImageAsset asset{ | |||
.width = surface->w, | |||
.frameHeight = frameHeight, | |||
.frameCount = surface->h / frameHeight, | |||
.data = std::make_unique<unsigned char[]>(surface->w * surface->h * 4), | |||
}; | |||
// TODO: Pixel formats? | |||
asset.data.reset(); | |||
for (size_t y = 0; y < (size_t)surface->h; ++y) { | |||
unsigned char *src = (unsigned char *)surface->pixels + y * surface->pitch; | |||
unsigned char *dest = asset.data.get() + y * surface->w * 4; | |||
memcpy(dest, src, surface->w * 4); | |||
} | |||
return {Ok, std::move(asset)}; | |||
} | |||
} |