#include <world.hpp> // standard library headers #include <algorithm> #include <fstream> #include <memory> #include <sstream> #include <chrono> using namespace std::literals::chrono_literals; // local library headers #include <tinyxml2.h> using namespace tinyxml2; // game headers #include <common.hpp> #include <components.hpp> #include <debug.hpp> #include <engine.hpp> #include <error.hpp> #include <fileio.hpp> #include <gametime.hpp> #include <player.hpp> #include <particle.hpp> #include <render.hpp> #include <ui.hpp> #include <vector3.hpp> #include <weather.hpp> WorldData2 WorldSystem::world; Mix_Music* WorldSystem::bgmObj; std::string WorldSystem::bgmCurrent; std::vector<std::string> WorldSystem::bgFiles; TextureIterator WorldSystem::bgTex; XMLDocument WorldSystem::xmlDoc; std::string WorldSystem::currentXMLFile; std::thread WorldSystem::thAmbient; std::vector<vec2> WorldSystem::stars; extern std::string xmlFolder; // wait static bool waitToSwap = false; // externally referenced in main.cpp int worldShade = 0; // ground-generating constants constexpr const float GROUND_HEIGHT_INITIAL = 80.0f; constexpr const float GROUND_HEIGHT_MINIMUM = 60.0f; constexpr const float GROUND_HEIGHT_MAXIMUM = 110.0f; constexpr const float GROUND_HILLINESS = 10.0f; // defines grass height in HLINEs const unsigned int GRASS_HEIGHT = HLINES(4); // the path of the currently loaded XML file, externally referenced in places static std::string currentXML; // pathnames of images for world themes //using StyleList = std::string[8]; constexpr const char* bgPaths[1][8] = { { // Forest "bg.png", // sky/background "bgFarMountain.png", // layer 1 (furthest) "forestTileFar.png", // layer 2 "forestTileBack.png", // layer 3 "forestTileMid.png", // layer 4 "forestTileFront.png", // layer 5 (closest) "dirt.png", // ground texture "grass.png" // grass (ground-top) texture } }; // pathnames of structure textures constexpr const char* buildPaths[] = { "townhall.png", "house1.png", "house2.png", "house1.png", "house1.png", "fountain1.png", "lampPost1.png", "brazzier.png" }; /* ---------------------------------------------------------------------------- ** Functions section ** --------------------------------------------------------------------------*/ void WorldSystem::generate(int width) { float geninc = 0; // allocate space for world world.data = std::vector<WorldData> (width + GROUND_HILLINESS); // prepare for generation world.data[0].groundHeight = GROUND_HEIGHT_INITIAL; auto wditer = std::begin(world.data) + GROUND_HILLINESS; if (world.indoor) { std::fill(world.data.begin(), world.data.end(), WorldData {true, {0, 0}, GROUND_HEIGHT_MINIMUM + 5, 4}); } else { // give every GROUND_HILLINESSth entry a groundHeight value for (; wditer < std::end(world.data); wditer += GROUND_HILLINESS) wditer[-static_cast<int>(GROUND_HILLINESS)].groundHeight = wditer[0].groundHeight + (randGet() % 8 - 4); // create slopes from the points that were just defined, populate the rest of the WorldData structure for (wditer = std::begin(world.data) + 1; wditer < std::end(world.data); wditer++){ auto w = &*(wditer); if (w->groundHeight != 0) geninc = (w[static_cast<int>(GROUND_HILLINESS)].groundHeight - w->groundHeight) / GROUND_HILLINESS; w->groundHeight = std::clamp(w[-1].groundHeight + geninc, GROUND_HEIGHT_MINIMUM, GROUND_HEIGHT_MAXIMUM); w->groundColor = randGet() % 32 / 8; w->grassUnpressed = true; w->grassHeight[0] = (randGet() % 16) / 3 + HLINES(2); w->grassHeight[1] = (randGet() % 16) / 3 + HLINES(2); } } // define x-coordinate of world's leftmost 'line' world.startX = HLINES(width * -0.5); // gen. star coordinates if (stars.empty()) { stars.resize(game::SCREEN_WIDTH / 30); for (auto& s : stars) { s.x = world.startX + (randGet() % (int)HLINES(width)); s.y = game::SCREEN_HEIGHT - (randGet() % (int)HLINES(game::SCREEN_HEIGHT / 1.3f)); } } } float WorldSystem::isAboveGround(const vec2& p) { int line = std::clamp(static_cast<int>((p.x - world.startX) / game::HLINE), 0, static_cast<int>(world.data.size())); const auto& gh = world.data[line].groundHeight; return p.y >= gh ? 0 : gh; } static Color ambient; bool WorldSystem::save(void) { if (world.indoor) return false; std::ofstream save (xmlFolder + currentXMLFile + ".dat"); // signature? save << "831998 "; game::entities.each<Position>([&](entityx::Entity entity, Position& pos) { // save position save << "p " << pos.x << ' ' << pos.y << ' '; // save dialog, if exists if (entity.has_component<Dialog>()) save << "d " << entity.component<Dialog>()->index << ' '; if (entity.has_component<Health>()) save << "h " << entity.component<Health>()->health << ' '; }, true); save.close(); return true; } static std::vector<entityx::Entity::Id> savedEntities; void WorldSystem::fight(entityx::Entity entity) { std::string exit = currentXMLFile; savedEntities.emplace_back(entity.id()); load(entity.component<Aggro>()->arena); savedEntities.clear(); entity.component<Health>()->health = entity.component<Health>()->maxHealth; entity.remove<Aggro>(); auto door = game::entities.create(); door.assign<Position>(0, 100); door.assign<Grounded>(); door.assign<Visible>(-5); door.assign<Portal>(exit); auto sprite = door.assign<Sprite>(); auto dtex = game::engine.getSystem<RenderSystem>()->loadTexture("assets/style/classic/door.png"); sprite->addSpriteSegment(SpriteData(dtex), 0); auto dim = HLINES(sprite->getSpriteSize()); door.assign<Solid>(dim.x, dim.y); } void WorldSystem::load(const std::string& file) { auto& render = *game::engine.getSystem<RenderSystem>(); entityx::Entity entity; // check for empty file name if (file.empty()) return; // save the current world's data if (!currentXMLFile.empty()) save(); // load file data to string auto xmlPath = xmlFolder + file; auto xmlRaw = readFile(xmlPath); // let tinyxml parse the file UserAssert(xmlDoc.Parse(xmlRaw.data()) == XML_NO_ERROR, "XML Error: Failed to parse file (not your fault though..?)"); // include headers std::vector<std::string> toAdd; auto ixml = xmlDoc.FirstChildElement("include"); while (ixml != nullptr) { auto file = ixml->Attribute("file"); UserAssert(file != nullptr, "XML Error: <include> tag file not given"); if (file != nullptr) { DEBUG_printf("Including file: %s\n", file); toAdd.emplace_back(xmlFolder + file); //xmlRaw.append(readFile(xmlFolder + file)); } else { UserError("XML Error: <include> tag file not given"); } ixml = ixml->NextSiblingElement("include"); } for (const auto& f : toAdd) xmlRaw.append(readFile(f)); UserAssert(xmlDoc.Parse(xmlRaw.data()) == XML_NO_ERROR, "XML Error: failed to append includes"); // look for an opening world tag auto wxml = xmlDoc.FirstChildElement("World"); if (wxml != nullptr) { wxml = wxml->FirstChildElement(); world.indoor = false; } else { wxml = xmlDoc.FirstChildElement("IndoorWorld"); UserAssert(wxml != nullptr, "XML Error: Cannot find a <World> or <IndoorWorld> tag in " + xmlPath); wxml = wxml->FirstChildElement(); world.indoor = true; if (world.outdoor.empty()) { world.outdoor = currentXMLFile; world.outdoorCoords = vec2(0, 100); } } world.toLeft = world.toRight = ""; currentXMLFile = file; //game::entities.reset(); if (!savedEntities.empty()) { savedEntities.emplace_back(game::engine.getSystem<PlayerSystem>()->getId()); game::entities.each<Position>( [&](entityx::Entity entity, Position& p) { (void)p; if (std::find(savedEntities.begin(), savedEntities.end(), entity.id()) == savedEntities.end()) entity.destroy(); }, true); } else { game::entities.reset(); game::engine.getSystem<PlayerSystem>()->create(); } // iterate through tags while (wxml != nullptr) { std::string tagName = wxml->Name(); // style tag if (tagName == "style") { world.styleFolder = wxml->StrAttribute("folder"); unsigned int styleNo; if (wxml->QueryUnsignedAttribute("background", &styleNo) != XML_NO_ERROR) UserError("XML Error: No background given in <style> in " + xmlPath); world.style = static_cast<WorldBGType>(styleNo); world.bgm = wxml->StrAttribute("bgm"); bgFiles.clear(); for (const auto& f : bgPaths[styleNo]) bgFiles.push_back(world.styleFolder + "bg/" + f); bgTex = TextureIterator(bgFiles); } // world generation else if (tagName == "generation") { generate(wxml->IntAttribute("width")); } // indoor stuff else if (tagName == "house") { if (!world.indoor) UserError("<house> can only be used inside <IndoorWorld>"); //world.indoorWidth = wxml->FloatAttribute("width"); world.indoorTex = render.loadTexture(wxml->StrAttribute("texture")); // TODO winbloze lol auto str = wxml->StrAttribute("texture"); auto tex = render.loadTexture(str); world.indoorTex = tex; } // weather tag else if (tagName == "weather") { game::engine.getSystem<WeatherSystem>()->setWeather(wxml->GetText()); //setWeather(wxml->GetText()); } // link tags else if (tagName == "link") { auto linkTo = wxml->Attribute("left"); if (linkTo != nullptr) { world.toLeft = linkTo; } else { linkTo = wxml->Attribute("right"); if (linkTo != nullptr) world.toRight = linkTo; else UserError("<link> doesn't handle left or right... huh"); } } // time setting else if (tagName == "time") { game::time::setTickCount(std::stoi(wxml->GetText())); } // custom entity tags else { auto cxml = xmlDoc.FirstChildElement(tagName.c_str()); if (cxml != nullptr) { entity = game::entities.create(); auto abcd = cxml->FirstChildElement(); while (abcd) { std::string tname = abcd->Name(); if (tname == "Position") entity.assign<Position>(wxml, abcd); else if (tname == "Visible") entity.assign<Visible>(wxml, abcd); else if (tname == "Sprite") entity.assign<Sprite>(wxml, abcd); else if (tname == "Portal") entity.assign<Portal>(wxml, abcd); else if (tname == "Solid") { auto solid = entity.assign<Solid>(wxml, abcd); if (solid->width == 0 && solid->height == 0) { auto dim = entity.component<Sprite>()->getSpriteSize(); entity.remove<Solid>(); entity.assign<Solid>(dim.x, dim.y); } } else if (tname == "Direction") entity.assign<Direction>(wxml, abcd); else if (tname == "Physics") entity.assign<Physics>(wxml, abcd); else if (tname == "Name") entity.assign<Name>(wxml, abcd); else if (tname == "Dialog") entity.assign<Dialog>(wxml, abcd); else if (tname == "Grounded") entity.assign<Grounded>(); // no need to pass xmls... else if (tname == "Wander") entity.assign<Wander>(); else if (tname == "Hop") entity.assign<Hop>(); else if (tname == "Health") entity.assign<Health>(wxml, abcd); else if (tname == "Aggro") entity.assign<Aggro>(wxml, abcd); else if (tname == "Animation") entity.assign<Animate>(wxml, abcd); else if (tname == "Trigger") entity.assign<Trigger>(wxml, abcd); abcd = abcd->NextSiblingElement(); } } else { UserError("Unknown tag <" + tagName + "> in file " + currentXMLFile); } } wxml = wxml->NextSiblingElement(); } // attempt to load data std::ifstream save (xmlFolder + currentXMLFile + ".dat"); if (save.good()) { // check signature int signature; save >> signature; if (signature != 831998) UserError("Save file signature is invalid... (delete it)"); char id; save >> id; entityx::ComponentHandle<Position> pos; for (entityx::Entity entity : game::entities.entities_with_components(pos)) { save >> pos->x >> pos->y; save >> id; while (!save.eof() && id != 'p') { switch (id) { case 'd': save >> entity.component<Dialog>()->index; break; case 'h': save >> entity.component<Health>()->health; break; } save >> id; } } save.close(); } game::events.emit<BGMToggleEvent>(); } /* World * loadWorldFromXMLNoSave(std::string path) { XMLDocument *_currentXMLDoc; static std::string _currentXML, _currentXMLRaw; XMLElement *wxml; XMLElement *vil; World *tmp; Entity *newEntity; bool Indoor; const char *ptr; std::string name, sptr; Village *vptr; Structures *s; if (vil) { vptr = tmp->addVillage(vil->StrAttribute("name"), tmp); vil = vil->FirstChildElement(); } while(vil) { name = vil->Name(); //READS DATA ABOUT STRUCTURE CONTAINED IN VILLAGE if (name == "structure") { s = new Structures(); s->createFromXML(vil, tmp); tmp->addStructure(s); } else if (name == "stall") { sptr = vil->StrAttribute("type"); // handle markets if (sptr == "market") { // create a structure and a merchant, and pair them s = new Structures(); s->createFromXML(vil, tmp); tmp->addStructure(s); tmp->addMerchant(0, 100, true); } // handle traders else if (sptr == "trader") { s = new Structures(); s->createFromXML(vil, tmp); tmp->addStructure(s); } // loop through buy/sell/trade tags XMLElement *sxml = vil->FirstChildElement(); std::string tag; Merchant *merch = dynamic_cast<Merchant *>(*std::find_if(tmp->entity.rbegin(), tmp->entity.rend(), [&](Entity *e) { return (e->type == MERCHT); })); while (sxml) { tag = sxml->Name(); if (tag == "buy") { //converts price to the currencies determined in items.xml // TODO } else if (tag == "sell") { //converts price so the player can sell // TODO } else if (tag == "trade") { //doesn't have to convert anything, we just trade multiple items merch->trade.push_back(Trade(sxml->IntAttribute("quantity"), sxml->StrAttribute("item"), sxml->IntAttribute("quantity1"), sxml->StrAttribute("item1"))); } else if (tag == "text") { //this is what the merchant says XMLElement *txml = sxml->FirstChildElement(); std::string textOption; while (txml) { textOption = txml->Name(); const char* buf = txml->GetText(); if (textOption == "greet") { //when you talk to him merch->text[0] = std::string(buf, strlen(buf)); merch->toSay = &merch->text[0]; } else if (textOption == "accept") { //when he accepts the trade merch->text[1] = std::string(buf, strlen(buf)); } else if (textOption == "deny") { //when you don't have enough money merch->text[2] = std::string(buf, strlen(buf)); } else if (textOption == "leave") { //when you leave the merchant merch->text[3] = std::string(buf, strlen(buf)); } txml = txml->NextSiblingElement(); } } sxml = sxml->NextSiblingElement(); } } float buildx = tmp->getStructurePos(-1).x; if (buildx < vptr->start.x) vptr->start.x = buildx; if (buildx > vptr->end.x) vptr->end.x = buildx; //go to the next element in the village block vil = vil->NextSiblingElement(); } return tmp; }*/ WorldSystem::WorldSystem(void) { bgmObj = nullptr; } WorldSystem::~WorldSystem(void) { // SDL2_mixer's object if (bgmObj != nullptr) Mix_FreeMusic(bgmObj); } void WorldSystem::render(void) { const auto SCREEN_WIDTH = game::SCREEN_WIDTH; const auto SCREEN_HEIGHT = game::SCREEN_HEIGHT; const vector2<int> backgroundOffset (static_cast<int>(SCREEN_WIDTH) / 2, static_cast<int>(SCREEN_HEIGHT) / 2); int iStart, iEnd, pOffset; // world width in pixels int width = HLINES(world.data.size()); static bool ambientUpdaterStarted = false; if (!ambientUpdaterStarted) { ambientUpdaterStarted = true; thAmbient = std::thread([&](void) { const bool &run = game::engine.shouldRun; while (run) { float v = 75 * sin((game::time::getTickCount() + (DAY_CYCLE / 2)) / (DAY_CYCLE / PI)); float rg = std::clamp(.5f + (-v / 100.0f), 0.01f, .9f); float b = std::clamp(.5f + (-v / 80.0f), 0.03f, .9f); ambient = Color(rg, rg, b, 1.0f); std::this_thread::sleep_for(1ms); } }); thAmbient.detach(); } // shade value for GLSL float shadeAmbient = std::max(0.0f, static_cast<float>(-worldShade) / 50 + 0.5f); // 0 to 1.5f if (shadeAmbient > 0.9f) shadeAmbient = 1; // TODO scroll backdrop //GLfloat bgOff = game::time::getTickCount() / static_cast<float>(DAY_CYCLE * 2); GLfloat bgOff = -0.5f * cos(PI / DAY_CYCLE * game::time::getTickCount()) + 0.5f; GLfloat topS = .125f + bgOff; GLfloat bottomS = bgOff; if (topS < 0.0f) topS += 1.0f; if (bottomS < 0.0f) bottomS += 1.0f; GLfloat scrolling_tex_coord[] = { 0.0f, bottomS, 1.0f, bottomS, 1.0f, bottomS, 1.0f, bottomS, 0.0f, bottomS, 0.0f, bottomS }; static const vec2 bg_tex_coord[] = { vec2(0.0f, 0.0f), vec2(1.0f, 0.0f), vec2(1.0f, 1.0f), vec2(1.0f, 1.0f), vec2(0.0f, 1.0f), vec2(0.0f, 0.0f) }; GLfloat back_tex_coord[] = { offset.x - backgroundOffset.x - 5, offset.y - backgroundOffset.y, 9.9f, offset.x + backgroundOffset.x + 5, offset.y - backgroundOffset.y, 9.9f, offset.x + backgroundOffset.x + 5, offset.y + backgroundOffset.y, 9.9f, offset.x + backgroundOffset.x + 5, offset.y + backgroundOffset.y, 9.9f, offset.x - backgroundOffset.x - 5, offset.y + backgroundOffset.y, 9.9f, offset.x - backgroundOffset.x - 5, offset.y - backgroundOffset.y, 9.9f }; // rendering!! glActiveTexture(GL_TEXTURE0); Render::worldShader.use(); glUniform1i(Render::worldShader.uniform[WU_texture], 0); glUniform1f(Render::worldShader.uniform[WU_light_impact], 0.0f); glUniform4f(Render::worldShader.uniform[WU_ambient], 1.0, 1.0, 1.0, 1.0); Render::worldShader.enable(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); bgTex(0); glUniform4f(Render::worldShader.uniform[WU_tex_color], 1.0, 1.0, 1.0, 1.0); glVertexAttribPointer(Render::worldShader.coord, 3, GL_FLOAT, GL_FALSE, 0, back_tex_coord); glVertexAttribPointer(Render::worldShader.tex , 2, GL_FLOAT, GL_FALSE, 0, scrolling_tex_coord); glDrawArrays(GL_TRIANGLES, 0, 6); // no more night bg //bgTex++; //glUniform4f(Render::worldShader.uniform[WU_tex_color], 1.0, 1.0, 1.0, 1.3 - static_cast<float>(alpha) / 255.0f); //makeWorldDrawingSimplerEvenThoughAndyDoesntThinkWeCanMakeItIntoFunctions(0, fron_tex_coord, tex_coord, 6); // TODO make stars dynamic (make them particles??) static const Texture starTex ("assets/style/classic/bg/star.png"); // TODO why in theme, not just std.? const static float stardim = 24; GLfloat* star_coord = new GLfloat[stars.size() * 5 * 6 + 1]; GLfloat* si = &star_coord[0]; if (worldShade > 0) { auto xcoord = offset.x * 0.9f; for (auto &s : stars) { float data[30] = { s.x + xcoord, s.y, 9.7, 0, 0, s.x + xcoord + stardim, s.y, 9.7, 1, 0, s.x + xcoord + stardim, s.y + stardim, 9.7, 1, 1, s.x + xcoord + stardim, s.y + stardim, 9.7, 1, 1, s.x + xcoord, s.y + stardim, 9.7, 0, 1, s.x + xcoord, s.y, 9.7, 0, 0 }; std::memcpy(si, data, sizeof(float) * 30); si += 30; } starTex.use(); glUniform4f(Render::worldShader.uniform[WU_tex_color], 1.0, 1.0, 1.0, 1.3); glVertexAttribPointer(Render::worldShader.coord, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), &star_coord[0]); glVertexAttribPointer(Render::worldShader.tex, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), &star_coord[3]); glDrawArrays(GL_TRIANGLES, 0, stars.size() * 6); } Render::worldShader.disable(); glUniform4f(Render::worldShader.uniform[WU_tex_color], 1.0, 1.0, 1.0, 1.0); glUniform4f(Render::worldShader.uniform[WU_ambient], ambient.red, ambient.green, ambient.blue, 1.0); Render::worldShader.unuse(); std::vector<vec3> bg_items; std::vector<vec2> bg_tex; bgTex++; auto mountainDim = bgTex.getTextureDim(); mountainDim.x = HLINES(mountainDim.x); mountainDim.y = HLINES(mountainDim.y); auto xcoord = width / 2 * -1 + offset.x * 0.85f; for (int i = 0; i <= width / mountainDim.x; i++) { bg_items.emplace_back(mountainDim.x * i + xcoord, GROUND_HEIGHT_MINIMUM, 8.0f); bg_items.emplace_back(mountainDim.x * (i + 1) + xcoord, GROUND_HEIGHT_MINIMUM, 8.0f); bg_items.emplace_back(mountainDim.x * (i + 1) + xcoord, GROUND_HEIGHT_MINIMUM + mountainDim.y, 8.0f); bg_items.emplace_back(mountainDim.x * (i + 1) + xcoord, GROUND_HEIGHT_MINIMUM + mountainDim.y, 8.0f); bg_items.emplace_back(mountainDim.x * i + xcoord, GROUND_HEIGHT_MINIMUM + mountainDim.y, 8.0f); bg_items.emplace_back(mountainDim.x * i + xcoord, GROUND_HEIGHT_MINIMUM, 8.0f); } for (uint i = 0; i < bg_items.size()/6; i++) { for (auto &v : bg_tex_coord) bg_tex.push_back(v); } Render::worldShader.use(); glUniform1f(Render::worldShader.uniform[WU_light_impact], 0.01); Render::worldShader.enable(); glVertexAttribPointer(Render::worldShader.coord, 3, GL_FLOAT, GL_FALSE, 0, &bg_items[0]); glVertexAttribPointer(Render::worldShader.tex , 2, GL_FLOAT, GL_FALSE, 0, &bg_tex[0]); glDrawArrays(GL_TRIANGLES, 0, bg_items.size()); Render::worldShader.disable(); Render::worldShader.unuse(); // draw the remaining layers static const float alphas[4] = { 0.6, 0.4, 0.25, 0.1 }; for (int i = 0; i < 4; i++) { bgTex++; auto xcoord = offset.x * alphas[i]; bg_items.clear(); bg_tex.clear(); vec2 dim = bgTex.getTextureDim() * game::HLINE; if (world.indoor && i == 3) { world.indoorTex.use(); const auto& startx = world.startX; dim = world.indoorTex.getDim() * game::HLINE; bg_items.emplace_back(startx, GROUND_HEIGHT_MINIMUM, 7 - (i * 0.1f)); bg_items.emplace_back(startx + dim.x, GROUND_HEIGHT_MINIMUM, 7 - (i * 0.1f)); bg_items.emplace_back(startx + dim.x, GROUND_HEIGHT_MINIMUM + dim.y, 7 - (i * 0.1f)); bg_items.emplace_back(startx + dim.x, GROUND_HEIGHT_MINIMUM + dim.y, 7 - (i * 0.1f)); bg_items.emplace_back(startx, GROUND_HEIGHT_MINIMUM + dim.y, 7 - (i * 0.1f)); bg_items.emplace_back(startx, GROUND_HEIGHT_MINIMUM, 7 - (i * 0.1f)); } else { for (int j = world.startX; j <= -world.startX; j += dim.x) { bg_items.emplace_back(j + xcoord, GROUND_HEIGHT_MINIMUM, 7 - (i * 0.1f)); bg_items.emplace_back(j + dim.x + xcoord, GROUND_HEIGHT_MINIMUM, 7 - (i * 0.1f)); bg_items.emplace_back(j + dim.x + xcoord, GROUND_HEIGHT_MINIMUM + dim.y, 7 - (i * 0.1f)); bg_items.emplace_back(j + dim.x + xcoord, GROUND_HEIGHT_MINIMUM + dim.y, 7 - (i * 0.1f)); bg_items.emplace_back(j + xcoord, GROUND_HEIGHT_MINIMUM + dim.y, 7 - (i * 0.1f)); bg_items.emplace_back(j + xcoord, GROUND_HEIGHT_MINIMUM, 7 - (i * 0.1f)); } } for (uint i = 0; i < bg_items.size() / 6; i++) { for (auto &v : bg_tex_coord) bg_tex.push_back(v); } Render::worldShader.use(); glUniform1f(Render::worldShader.uniform[WU_light_impact], 0.075f + (0.2f * i)); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); Render::worldShader.enable(); glVertexAttribPointer(Render::worldShader.coord, 3, GL_FLOAT, GL_FALSE, 0, bg_items.data()); glVertexAttribPointer(Render::worldShader.tex , 2, GL_FLOAT, GL_FALSE, 0, &bg_tex[0]); glDrawArrays(GL_TRIANGLES, 0, bg_items.size()); Render::worldShader.disable(); Render::worldShader.unuse(); } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // get the line that the player is currently standing on pOffset = (offset.x - world.startX) / game::HLINE; // only draw world within player vision iStart = std::clamp(static_cast<int>(pOffset - (SCREEN_WIDTH / 2 / game::HLINE) - GROUND_HILLINESS), 0, static_cast<int>(world.data.size())); iEnd = std::clamp(static_cast<int>(pOffset + (SCREEN_WIDTH / 2 / game::HLINE) + 2), 0, static_cast<int>(world.data.size() - GROUND_HILLINESS)); // draw the dirt waitToSwap = true; bgTex++; static std::vector<GLfloat> dirt; if (dirt.size() != world.data.size() * 30) { dirt.clear(); dirt.resize(world.data.size() * 30); } auto push5 = [](GLfloat *&vec, GLfloat *five) { for (int i = 0; i < 5; i++) *vec++ = *five++; }; GLfloat *dirtp = &dirt[0]; for (int i = iStart; i < iEnd; i++) { if (world.data[i].groundHeight <= 0) { // TODO holes (andy) TODO TODO TODO world.data[i].groundHeight = GROUND_HEIGHT_MINIMUM - 1; //glColor4ub(0, 0, 0, 255); } else { //safeSetColorA(150, 150, 150, 255); } int ty = world.data[i].groundHeight / 64 + world.data[i].groundColor; GLfloat five[5] = { 0, 0, world.startX + HLINES(i), world.data[i].groundHeight - GRASS_HEIGHT, -4 }; push5(dirtp, five); five[0]++, five[2] += game::HLINE; push5(dirtp, five); five[1] += ty, five[3] = 0; push5(dirtp, five); push5(dirtp, five); five[0]--, five[2] -= game::HLINE; push5(dirtp, five); five[1] = 0, five[3] = world.data[i].groundHeight - GRASS_HEIGHT; push5(dirtp, five); if (world.data[i].groundHeight == GROUND_HEIGHT_MINIMUM - 1) world.data[i].groundHeight = 0; } Render::worldShader.use(); glUniform1f(Render::worldShader.uniform[WU_light_impact], 0.45f); Render::worldShader.enable(); glVertexAttribPointer(Render::worldShader.coord, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), &dirt[2]); glVertexAttribPointer(Render::worldShader.tex, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), &dirt[0]); glDrawArrays(GL_TRIANGLES, 0 , dirt.size() / 5); Render::worldShader.disable(); Render::worldShader.unuse(); if (!world.indoor) { bgTex++; //safeSetColorA(255, 255, 255, 255); TODO TODO TODO static std::vector<GLfloat> grass; if (grass.size() != world.data.size() * 60) { grass.clear(); grass.resize(world.data.size() * 60); } GLfloat *grassp = &grass[0]; for (int i = iStart; i < iEnd; i++) { auto wd = world.data[i]; auto gh = wd.grassHeight; // flatten the grass if the player is standing on it. if (!wd.grassUnpressed) { gh[0] /= 4; gh[1] /= 4; } // actually draw the grass. if (wd.groundHeight) { float five[5] = { 0, 1, world.startX + HLINES(i), wd.groundHeight + gh[0], -3 }; push5(grassp, five); five[0]++, five[1]--, five[2] += HLINES(0.5f); push5(grassp, five); five[1]++, five[3] = wd.groundHeight - GRASS_HEIGHT; push5(grassp, five); push5(grassp, five); five[0]--, five[2] -= HLINES(0.5f); push5(grassp, five); five[1]--, five[3] = wd.groundHeight + gh[0]; push5(grassp, five); five[1]++; five[2] = world.startX + HLINES(i + 0.5), five[3] = wd.groundHeight + gh[1]; push5(grassp, five); five[0]++, five[1]--, five[2] += HLINES(0.5f) + 1; push5(grassp, five); five[1]++, five[3] = wd.groundHeight - GRASS_HEIGHT; push5(grassp, five); push5(grassp, five); five[0]--, five[2] -= HLINES(0.5f) + 1; push5(grassp, five); five[1]--, five[3] = wd.groundHeight + gh[1]; push5(grassp, five); } } Render::worldShader.use(); glUniform1f(Render::worldShader.uniform[WU_light_impact], 1.0f); Render::worldShader.enable(); glVertexAttribPointer(Render::worldShader.coord, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), &grass[2]); glVertexAttribPointer(Render::worldShader.tex, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), &grass[0]); glDrawArrays(GL_TRIANGLES, 0 , grass.size() / 5); // the starting pixel of the world static const float s = -(static_cast<float>(SCREEN_WIDTH) / 2.0f); // the ending pixel of the world static const float e = static_cast<float>(SCREEN_WIDTH) / 2.0f; static const float sheight = static_cast<float>(SCREEN_HEIGHT); if (offset.x + world.startX > s) { Colors::black.use(); glUniform1f(Render::worldShader.uniform[WU_light_impact], 0.0f); auto off = offset.y - static_cast<float>(SCREEN_HEIGHT) / 2.0f; GLfloat blackBarLeft[] = { s, 0.0f + off, -3.5f, 0.0f, 0.0f, world.startX, 0.0f + off, -3.5f, 1.0f, 0.0f, world.startX, sheight + off, -3.5f, 1.0f, 1.0f, world.startX, sheight + off, -3.5f, 1.0f, 1.0f, s, sheight + off, -3.5f, 0.0f, 1.0f, s, 0.0f + off, -3.5f, 0.0f, 0.0f }; glVertexAttribPointer(Render::worldShader.coord, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, &blackBarLeft[0]); glVertexAttribPointer(Render::worldShader.tex, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, &blackBarLeft[3]); glDrawArrays(GL_TRIANGLES, 0, 6); } if (offset.x - world.startX < e) { Colors::black.use(); glUniform1f(Render::worldShader.uniform[WU_light_impact], 0.0f); auto off = offset.y - static_cast<float>(SCREEN_HEIGHT) / 2.0f; GLfloat blackBarRight[] = { -(world.startX), 0.0f + off, -3.5f, 0.0f, 0.0f, e, 0.0f + off, -3.5f, 1.0f, 0.0f, e, sheight + off, -3.5f, 1.0f, 1.0f, e, sheight + off, -3.5f, 1.0f, 1.0f, -(world.startX), sheight + off, -3.5f, 0.0f, 1.0f, -(world.startX), 0.0f + off, -3.5f, 0.0f, 0.0f }; glVertexAttribPointer(Render::worldShader.coord, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, &blackBarRight[0]); glVertexAttribPointer(Render::worldShader.tex, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, &blackBarRight[3]); glDrawArrays(GL_TRIANGLES, 0, 6); } Render::worldShader.disable(); Render::worldShader.unuse(); } else { Render::useShader(&Render::worldShader); Render::worldShader.use(); Colors::red.use(); vec2 ll = vec2 {world.startX, GROUND_HEIGHT_MINIMUM}; Render::drawRect(ll, ll + vec2(world.indoorTex.getDim().x, 4), -3); Render::worldShader.unuse(); } waitToSwap = false; } void WorldSystem::receive(const BGMToggleEvent &bte) { if (bte.world == nullptr || world.bgm != bte.file) { if (bgmCurrent == world.bgm) return; Mix_FadeOutMusic(800); if (bgmObj != nullptr) Mix_FreeMusic(bgmObj); bgmCurrent = world.bgm; bgmObj = Mix_LoadMUS(world.bgm.c_str()); Mix_PlayMusic(bgmObj, -1); } } void WorldSystem::update(entityx::EntityManager &en, entityx::EventManager &ev, entityx::TimeDelta dt) { (void)en; (void)ev; // fade in music if not playing if (!Mix_PlayingMusic() && bgmObj != nullptr) Mix_FadeInMusic(bgmObj, -1, 2000); // run detect stuff detect(dt); } void WorldSystem::detect(entityx::TimeDelta dt) { game::entities.each<Health>( [](entityx::Entity e, Health& h) { if (h.health <= 0) { e.kill(); //e.destroy(); } }); game::entities.each<Grounded, Position, Solid>( [&](entityx::Entity e, Grounded &g, Position &loc, Solid &dim) { (void)e; if (!g.grounded) { // get the line the entity is on int line = std::clamp(static_cast<int>((loc.x + dim.width / 2 - world.startX) / game::HLINE), 0, static_cast<int>(world.data.size())); // make sure entity is above ground const auto& data = world.data; if (loc.y != data[line].groundHeight) { loc.y = data[line].groundHeight; e.remove<Grounded>(); } } }); game::entities.each<Direction, Physics>( [&](entityx::Entity e, Direction &vel, Physics &phys) { (void)e; // handle gravity if (vel.y > -2.0f) vel.y -= (GRAVITY_CONSTANT * phys.g) * dt; }); game::entities.each<Position, Direction, Solid>( [&](entityx::Entity e, Position &loc, Direction &vel, Solid &dim) { (void)e; //if (health.health <= 0) // UserError("die mofo"); // get the line the entity is on int line = std::clamp(static_cast<int>((loc.x + dim.width / 2 - world.startX) / game::HLINE), 0, static_cast<int>(world.data.size())); // make sure entity is above ground const auto& data = world.data; if (loc.y < data[line].groundHeight) { int dir = vel.x < 0 ? -1 : 1; auto thing = line + dir * 2; if (thing > 0 && thing < static_cast<int>(data.size()) && data[line + dir * 2].groundHeight - 30 > data[line + dir].groundHeight) { loc.x -= (PLAYER_SPEED_CONSTANT + 2.7f) * dir * 2; vel.x = 0; } else { loc.y = data[line].groundHeight - 0.001f * dt; vel.y = 0; if (!vel.grounded) { vel.grounded = true; game::engine.getSystem<ParticleSystem>()->addMultiple(20, ParticleType::SmallPoof, [&](){ return vec2(loc.x + randGet() % static_cast<int>(dim.width), loc.y);}, 500, 30); } } } // insure that the entity doesn't fall off either edge of the world. if (loc.x < world.startX) { std::cout << "Left!\n"; vel.x = 0; loc.x = world.startX + HLINES(0.5f); } else if (loc.x + dim.width + game::HLINE > -(static_cast<int>(world.startX))) { std::cout << "Right\n"; vel.x = 0; loc.x = -(static_cast<int>(world.startX)) - dim.width - game::HLINE; } }); } void WorldSystem::goWorldRight(Position& p, Solid &d) { if (!(world.toRight.empty()) && (p.x + d.width > world.startX * -1 - HLINES(5))) { UISystem::fadeToggle(); UISystem::waitForCover(); while (waitToSwap) std::this_thread::sleep_for(1ms); load(world.toRight); game::engine.getSystem<PlayerSystem>()->setX(world.startX + HLINES(10)); UISystem::fadeToggle(); } } void WorldSystem::goWorldLeft(Position& p) { if (!(world.toLeft.empty()) && (p.x < world.startX + HLINES(10))) { UISystem::fadeToggle(); UISystem::waitForCover(); while (waitToSwap) std::this_thread::sleep_for(1ms); load(world.toLeft); game::engine.getSystem<PlayerSystem>()->setX(world.startX * -1 - HLINES(15)); UISystem::fadeToggle(); } } void WorldSystem::goWorldPortal(Position& p) { std::string file; if (world.indoor) { file = world.outdoor; p.x = world.outdoorCoords.x; // ineffective, player is regen'd p.y = world.outdoorCoords.y; } else { game::entities.each<Position, Solid, Portal>( [&](entityx::Entity entity, Position& loc, Solid &dim, Portal &portal) { (void)entity; if (!(portal.toFile.empty()) && p.x > loc.x && p.x < loc.x + dim.width) { file = portal.toFile; world.outdoor = currentXMLFile; world.outdoorCoords = vec2(loc.x + dim.width / 2, 100); return; } }); } if (!file.empty()) { UISystem::fadeToggle(); UISystem::waitForCover(); while (waitToSwap) std::this_thread::sleep_for(1ms); load(file); UISystem::fadeToggle(); } }