Browse Source

rewrite mod and asset loading logic

feature/replace-renderer
Martin Dørum 3 years ago
parent
commit
de5e49e700

+ 1
- 1
core.mod/src/entities/ItemStackEntity.cc View File

@@ -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) },
};
}

+ 1
- 1
core.mod/src/entities/PlayerEntity.cc View File

@@ -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)

+ 18
- 18
core.mod/src/main.cc View File

@@ -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);
}
}


+ 1
- 1
libcygnet/include/cygnet/ResourceManager.h View File

@@ -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);

+ 2
- 3
libswan/CMakeLists.txt View File

@@ -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)


+ 3
- 2
libswan/include/swan/Game.h View File

@@ -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_;

+ 4
- 16
libswan/include/swan/Item.h View File

@@ -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 &image;
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) {}
};

}

+ 6
- 11
libswan/include/swan/Mod.h View File

@@ -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
- 10
libswan/include/swan/Tile.h View File

@@ -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 &image;
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) {}
};

}

+ 20
- 11
libswan/include/swan/World.h View File

@@ -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_;

+ 20
- 0
libswan/include/swan/assets.h View File

@@ -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);

}

+ 92
- 2
libswan/include/swan/util.h View File

@@ -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) {

+ 1
- 1
libswan/src/Animation.cc View File

@@ -11,7 +11,7 @@ void Animation::tick(float dt) {
timer_ += interval_;

frame_ += 1;
if (frame_ >= resource_.num_frames_)
if (frame_ >= resource_.numFrames_)
frame_ = 0;
}
}

+ 1
- 1
libswan/src/Chunk.cc View File

@@ -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

+ 2
- 21
libswan/src/Game.cc View File

@@ -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());

+ 0
- 16
libswan/src/Item.cc View File

@@ -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",
});
}

}

+ 5
- 42
libswan/src/Mod.cc View File

@@ -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;
}

}

+ 0
- 25
libswan/src/Tile.cc View File

@@ -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,
});
}

}

+ 195
- 46
libswan/src/World.cc View File

@@ -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;

+ 70
- 0
libswan/src/assets.cc View File

@@ -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)};
}

}

Loading…
Cancel
Save