Swan::Entity::PackObject ItemStackEntity::serialize(const Swan::Context &ctx, msgpack::zone &zone) { | Swan::Entity::PackObject ItemStackEntity::serialize(const Swan::Context &ctx, msgpack::zone &zone) { | ||||
return { | return { | ||||
{ "pos", msgpack::object(body_.pos, zone) }, | { "pos", msgpack::object(body_.pos, zone) }, | ||||
{ "tile", msgpack::object(item_->name_, zone) }, | |||||
{ "tile", msgpack::object(item_->name, zone) }, | |||||
}; | }; | ||||
} | } |
} | } | ||||
// Fall down faster than we went up | // 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); | physics_.force += Swan::Vec2(0, DOWN_FORCE); | ||||
if (state_ != oldState) | if (state_ != oldState) |
registerTile({ | registerTile({ | ||||
.name = "stone", | .name = "stone", | ||||
.image = "core/tile/stone", | |||||
.dropped_item = "core::stone", | |||||
.image = "core::tile/stone", | |||||
.droppedItem = "core::stone", | |||||
}); | }); | ||||
registerTile({ | registerTile({ | ||||
.name = "dirt", | .name = "dirt", | ||||
.image = "core/tile/dirt", | |||||
.dropped_item = "core::dirt", | |||||
.image = "core::tile/dirt", | |||||
.droppedItem = "core::dirt", | |||||
}); | }); | ||||
registerTile({ | registerTile({ | ||||
.name = "grass", | .name = "grass", | ||||
.image = "core/tile/grass", | |||||
.dropped_item = "core::dirt", | |||||
.image = "core::tile/grass", | |||||
.droppedItem = "core::dirt", | |||||
}); | }); | ||||
registerTile({ | registerTile({ | ||||
.name = "tree-trunk", | .name = "tree-trunk", | ||||
.image = "core/tile/tree-trunk", | |||||
.dropped_item = "core::tree-trunk", | |||||
.image = "core::tile/tree-trunk", | |||||
.droppedItem = "core::tree-trunk", | |||||
}); | }); | ||||
registerTile({ | registerTile({ | ||||
.name = "leaves", | .name = "leaves", | ||||
.image = "core/tile/leaves", | |||||
.image = "core::tile/leaves", | |||||
}); | }); | ||||
registerTile({ | registerTile({ | ||||
.name = "torch", | .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({ | registerItem({ | ||||
.name = "stone", | .name = "stone", | ||||
.image = "core/tile/stone", | |||||
.image = "core::tile/stone", | |||||
}); | }); | ||||
registerItem({ | registerItem({ | ||||
.name = "dirt", | .name = "dirt", | ||||
.image = "core/tile/dirt", | |||||
.image = "core::tile/dirt", | |||||
}); | }); | ||||
registerItem({ | registerItem({ | ||||
.name = "grass", | .name = "grass", | ||||
.image = "core/tile/grass", | |||||
.image = "core::tile/grass", | |||||
}); | }); | ||||
registerItem({ | registerItem({ | ||||
.name = "tree-trunk", | .name = "tree-trunk", | ||||
.image = "core/tile/tree-trunk", | |||||
.image = "core::tile/tree-trunk", | |||||
}); | }); | ||||
registerWorldGen<DefaultWorldGen>("default"); | registerWorldGen<DefaultWorldGen>("default"); | ||||
} | } | ||||
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.droppedItem_) { | |||||
if (tile.droppedItem) { | |||||
ctx.plane.spawnEntity<ItemStackEntity>( | 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); | |||||
} | } | ||||
} | } | ||||
public: | public: | ||||
ResourceBuilder(Renderer &rnd): rnd_(rnd) {} | 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); | 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, void *data, int frames = 1); | ||||
void addTile(Renderer::TileID id, std::unique_ptr<unsigned char[]> data, int frames = 1); | void addTile(Renderer::TileID id, std::unique_ptr<unsigned char[]> data, int frames = 1); |
src/traits/InventoryTrait.cc | src/traits/InventoryTrait.cc | ||||
src/traits/PhysicsTrait.cc | src/traits/PhysicsTrait.cc | ||||
src/Animation.cc | src/Animation.cc | ||||
src/assets.cc | |||||
src/Chunk.cc | src/Chunk.cc | ||||
src/Clock.cc | src/Clock.cc | ||||
src/drawutil.cc | src/drawutil.cc | ||||
src/Entity.cc | src/Entity.cc | ||||
src/Game.cc | src/Game.cc | ||||
src/gfxutil.cc | src/gfxutil.cc | ||||
src/Item.cc | |||||
src/ItemStack.cc | src/ItemStack.cc | ||||
src/LightServer.cc | src/LightServer.cc | ||||
src/Mod.cc | src/Mod.cc | ||||
src/OS.cc | src/OS.cc | ||||
src/Resource.cc | src/Resource.cc | ||||
src/Tile.cc | |||||
src/World.cc | src/World.cc | ||||
src/WorldPlane.cc) | src/WorldPlane.cc) | ||||
target_include_directories(libswan | target_include_directories(libswan | ||||
PUBLIC "include" | PUBLIC "include" | ||||
PRIVATE "include/swan") | PRIVATE "include/swan") | ||||
set_target_properties(libswan PROPERTIES OUTPUT_NAME 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) | install(TARGETS libswan DESTINATION swan/libswan) | ||||
#include <string> | #include <string> | ||||
#include <optional> | #include <optional> | ||||
#include <SDL.h> | #include <SDL.h> | ||||
#include <cygnet/Renderer.h> | |||||
#include "common.h" | #include "common.h" | ||||
#include "Resource.h" | #include "Resource.h" | ||||
win_(win), | win_(win), | ||||
mousePos_(0, 0) {} | 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) { | void onKeyDown(SDL_Keysym sym) { | ||||
pressedKeys_[sym.scancode] = true; | pressedKeys_[sym.scancode] = true; | ||||
std::unique_ptr<Tile> invalidTile_ = NULL; | std::unique_ptr<Tile> invalidTile_ = NULL; | ||||
std::unique_ptr<Item> invalidItem_ = NULL; | std::unique_ptr<Item> invalidItem_ = NULL; | ||||
Win &win_; | Win &win_; | ||||
Cygnet::Renderer renderer_; | |||||
private: | private: | ||||
std::bitset<SDL_NUM_SCANCODES> pressedKeys_; | std::bitset<SDL_NUM_SCANCODES> pressedKeys_; |
#include <string> | #include <string> | ||||
#include "Resource.h" | #include "Resource.h" | ||||
#include "Tile.h" | |||||
namespace Swan { | namespace Swan { | ||||
int maxStack = 64; | 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 std::string name; | ||||
const ImageResource ℑ | |||||
const int maxStack; | 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) {} | |||||
}; | }; | ||||
} | } |
Mod(std::string name): name_(std::move(name)) {} | Mod(std::string name): name_(std::move(name)) {} | ||||
virtual ~Mod() = default; | virtual ~Mod() = default; | ||||
void registerImage(const std::string &id); | |||||
void registerTile(Tile::Builder tile); | void registerTile(Tile::Builder tile); | ||||
void registerItem(Item::Builder item); | 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> | 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, | .name = name_ + "::" + name, | ||||
.create = [](World &world) -> std::unique_ptr<WorldGen> { | .create = [](World &world) -> std::unique_ptr<WorldGen> { | ||||
return std::make_unique<WG>(world); | return std::make_unique<WG>(world); | ||||
std::vector<std::string> images_; | std::vector<std::string> images_; | ||||
std::vector<Tile::Builder> tiles_; | std::vector<Tile::Builder> tiles_; | ||||
std::vector<Item::Builder> items_; | 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_; | std::vector<EntityCollection::Factory> entities_; | ||||
}; | }; | ||||
mod_.reset(); | 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::unique_ptr<Mod> mod_; | ||||
std::string path_; | std::string path_; | ||||
OS::Dynlib dynlib_; | OS::Dynlib dynlib_; |
#include <optional> | #include <optional> | ||||
#include <memory> | #include <memory> | ||||
#include "Item.h" | |||||
#include "Resource.h" | #include "Resource.h" | ||||
namespace Swan { | namespace Swan { | ||||
std::optional<std::string> droppedItem = std::nullopt; | 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 std::string name; | ||||
const ImageResource ℑ | |||||
const bool isSolid; | const bool isSolid; | ||||
const float lightLevel; | const float lightLevel; | ||||
const std::optional<std::string> droppedItem; | 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) {} | |||||
}; | }; | ||||
} | } |
#include <string> | #include <string> | ||||
#include <random> | #include <random> | ||||
#include <SDL.h> | #include <SDL.h> | ||||
#include <cygnet/Renderer.h> | |||||
#include <cygnet/ResourceManager.h> | |||||
#include "common.h" | #include "common.h" | ||||
#include "Item.h" | #include "Item.h" | ||||
class World { | class World { | ||||
public: | 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 setWorldGen(std::string gen); | ||||
void spawnPlayer(); | void spawnPlayer(); | ||||
WorldPlane &addPlane(const std::string &gen); | WorldPlane &addPlane(const std::string &gen); | ||||
WorldPlane &addPlane() { return addPlane(defaultWorldGen_); } | 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); | ||||
Tile &getTile(const std::string &name); | Tile &getTile(const std::string &name); | ||||
Item &getItem(const std::string &name); | Item &getItem(const std::string &name); | ||||
evtTileBreak_; | evtTileBreak_; | ||||
// World owns all mods | // World owns all mods | ||||
Game *game_; // TODO: reference, not pointer | |||||
std::mt19937 random_; | |||||
std::vector<ModWrapper> mods_; | std::vector<ModWrapper> mods_; | ||||
//ResourceManager resources_; | |||||
Cygnet::ResourceManager resources_; | |||||
// 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<Tile> tiles_; | |||||
std::unordered_map<std::string, Tile::ID> tilesMap_; | 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 | // 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_; | BodyTrait::Body *player_; | ||||
Game *game_; | |||||
std::mt19937 random_; | |||||
ResourceManager resources_; | |||||
private: | private: | ||||
class ChunkRenderer { | class ChunkRenderer { | ||||
void tick(WorldPlane &plane, ChunkPos abspos); | void tick(WorldPlane &plane, ChunkPos abspos); | ||||
}; | }; | ||||
std::vector<ModWrapper> loadMods(std::vector<std::string> paths); | |||||
Cygnet::ResourceManager buildResources(); | |||||
ChunkRenderer chunkRenderer_; | ChunkRenderer chunkRenderer_; | ||||
WorldPlane::ID currentPlane_; | WorldPlane::ID currentPlane_; | ||||
std::vector<std::unique_ptr<WorldPlane>> planes_; | std::vector<std::unique_ptr<WorldPlane>> planes_; |
#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); | |||||
} |
#pragma once | |||||
#pragma once | |||||
#include <optional> | #include <optional> | ||||
#include <functional> | #include <functional> | ||||
#include <memory> | #include <memory> | ||||
#include <chrono> | #include <chrono> | ||||
#include <type_traits> | #include <type_traits> | ||||
#include <string> | |||||
#include <stddef.h> | #include <stddef.h> | ||||
namespace Swan { | namespace Swan { | ||||
~Deferred() { Func(); } | ~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... | // Calling begin/end is stupid... | ||||
template<typename T> | template<typename T> | ||||
auto callBegin(T &v) { | auto callBegin(T &v) { |
timer_ += interval_; | timer_ += interval_; | ||||
frame_ += 1; | frame_ += 1; | ||||
if (frame_ >= resource_.num_frames_) | |||||
if (frame_ >= resource_.numFrames_) | |||||
frame_ = 0; | frame_ = 0; | ||||
} | } | ||||
} | } |
} | } | ||||
// 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 = World::INVALID_TILE_ID; | |||||
Tile *tile = ctx.game.invalidTile_.get(); | Tile *tile = ctx.game.invalidTile_.get(); | ||||
// Fill tile texture | // Fill tile texture |
namespace Swan { | 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_->setWorldGen(worldgen); | ||||
world_->setCurrentPlane(world_->addPlane()); | world_->setCurrentPlane(world_->addPlane()); |
#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", | |||||
}); | |||||
} | |||||
} |
namespace Swan { | 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) { | void Mod::registerTile(Tile::Builder tile) { | ||||
tile.name = name_ + "::" + tile.name; | |||||
tiles_.push_back(tile); | tiles_.push_back(tile); | ||||
info << " Adding tile: " << tile.name; | |||||
info << " Adding tile: " << name_ << "::" << tile.name; | |||||
} | } | ||||
void Mod::registerItem(Item::Builder item) { | void Mod::registerItem(Item::Builder item) { | ||||
item.name = name_ + "::" + item.name; | |||||
items_.push_back(item); | 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; | |||||
} | } | ||||
} | } |
#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, | |||||
}); | |||||
} | |||||
} |
#include "World.h" | #include "World.h" | ||||
#include <algorithm> | #include <algorithm> | ||||
#include <tuple> | |||||
#include "log.h" | #include "log.h" | ||||
#include "Game.h" | #include "Game.h" | ||||
#include "Win.h" | #include "Win.h" | ||||
#include "Clock.h" | #include "Clock.h" | ||||
#include "assets.h" | |||||
namespace Swan { | namespace Swan { | ||||
} | } | ||||
} | } | ||||
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) { | void World::ChunkRenderer::tick(WorldPlane &plane, ChunkPos abspos) { | ||||
ZoneScopedN("World::ChunkRenderer tick"); | ZoneScopedN("World::ChunkRenderer tick"); | ||||
int l = 0; | int l = 0; | ||||
} | } | ||||
} | } | ||||
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) { | void World::setWorldGen(std::string gen) { | ||||
defaultWorldGen_ = std::move(gen); | defaultWorldGen_ = std::move(gen); | ||||
} | } | ||||
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 = 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 << "!"; | 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(entCollFactories_.size()); | colls.reserve(entCollFactories_.size()); | ||||
for (auto &fact: entCollFactories_) { | 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; | WorldGen::Factory &factory = it->second; | ||||
return *game_->invalidItem_; | 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 = tilesMap_.find(name); | auto iter = tilesMap_.find(name); | ||||
if (iter == tilesMap_.end()) { | 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 INVALID_TILE_ID; | |||||
} | } | ||||
return iter->second; | return iter->second; |
#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)}; | |||||
} | |||||
} |