#include <world.hpp> /* ---------------------------------------------------------------------------- ** Includes section ** --------------------------------------------------------------------------*/ // standard library headers #include <algorithm> #include <sstream> #include <fstream> #include <memory> #include <mutex> #include <chrono> using namespace std::literals::chrono_literals; // local game headers #include <ui.hpp> #include <gametime.hpp> #include <render.hpp> #include <engine.hpp> #include <components.hpp> #include <player.hpp> // local library headers #include <tinyxml2.h> using namespace tinyxml2; void makeWorldDrawingSimplerEvenThoughAndyDoesntThinkWeCanMakeItIntoFunctions( unsigned size, void *coordAddr, void *texAddr, unsigned triCount ) { glVertexAttribPointer(Render::worldShader.coord, 3, GL_FLOAT, GL_FALSE, size, coordAddr); glVertexAttribPointer(Render::worldShader.tex , 2, GL_FLOAT, GL_FALSE, size, texAddr ); glDrawArrays(GL_TRIANGLES, 0, triCount); } void makeWorldDrawingSimplerEvenThoughAndyDoesntThinkWeCanMakeItIntoFunctions_JustDrawThis( unsigned size, void *coordAddr, void *texAddr, unsigned triCount ) { Render::worldShader.enable(); makeWorldDrawingSimplerEvenThoughAndyDoesntThinkWeCanMakeItIntoFunctions(size, coordAddr, texAddr, triCount); Render::worldShader.disable(); } /* ---------------------------------------------------------------------------- ** Variables section ** --------------------------------------------------------------------------*/ extern std::string xmlFolder; // wait static bool waitToSwap = false; // particle mutex std::mutex partMutex; // 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 std::string currentXML; // pathnames of images for world themes using StyleList = std::array<std::string, 9>; static const std::vector<StyleList> bgPaths = { { // Forest "bg.png", // daytime background "bgn.png", // nighttime 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 static const std::string buildPaths[] = { "townhall.png", "house1.png", "house2.png", "house1.png", "house1.png", "fountain1.png", "lampPost1.png", "brazzier.png" }; // alpha-related values used for world drawing? nobody knows... static const float bgDraw[4][3]={ { 100, 240, 0.6 }, { 150, 250, 0.4 }, { 200, 255, 0.25 }, { 255, 255, 0.1 } }; /* ---------------------------------------------------------------------------- ** Functions section ** --------------------------------------------------------------------------*/ void WorldSystem::generate(int width) { float geninc = 0; // allocate space for world world.data = std::vector<WorldData> (width + GROUND_HILLINESS, WorldData { false, {0, 0}, 0, 0 }); // prepare for generation world.data[0].groundHeight = GROUND_HEIGHT_INITIAL; auto wditer = std::begin(world.data) + GROUND_HILLINESS; if (world.indoor) { for (auto &l : world.data) { l.groundHeight = GROUND_HEIGHT_MINIMUM + 5; l.groundColor = 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); } 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 << ' '; }); save.close(); return true; } void WorldSystem::load(const std::string& file) { auto& render = *game::engine.getSystem<RenderSystem>(); auto str2coord = [](std::string s) -> vec2 { auto cpos = s.find(','); s[cpos] = '\0'; return vec2 (std::stof(s), std::stof(s.substr(cpos + 1))); }; entityx::Entity entity; // check for empty file name if (file.empty()) return; // load file data to string auto xmlPath = xmlFolder + file; auto xmlRawData = readFile(xmlPath.c_str()); std::string xmlRaw = xmlRawData; delete[] xmlRawData; // let tinyxml parse the file if (xmlDoc.Parse(xmlRaw.data()) != XML_NO_ERROR) UserError("XML Error: Failed to parse file (not your fault though..?)"); // include headers auto ixml = xmlDoc.FirstChildElement("include"); while (ixml != nullptr) { auto file = ixml->Attribute("file"); if (file != nullptr) { DEBUG_printf("Including file: %s\n", file); auto include = readFile((xmlFolder + file).c_str()); xmlRaw.append(include); delete[] include; } else { UserError("XML Error: <include> tag file not given"); } ixml = ixml->NextSiblingElement(); } if (xmlDoc.Parse(xmlRaw.data()) != XML_NO_ERROR) UserError("XML Error:"); // 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"); if (wxml != nullptr) { wxml = wxml->FirstChildElement(); world.indoor = true; } else { UserError("XML Error: Cannot find a <World> or <IndoorWorld> tag in " + xmlPath); } } world.toLeft = world.toRight = ""; currentXMLFile = file; game::entities.reset(); game::engine.getSystem<PlayerSystem>()->create(); // iterate through tags while (wxml) { 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(); const auto& files = bgPaths[static_cast<int>(world.style)]; for (const auto& f : files) 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"); auto str = wxml->StrAttribute("texture"); auto tex = render.loadTexture(str); world.indoorTex = tex; } // weather tag else if (tagName == "weather") { 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") { vec2 coords; if (wxml->Attribute("position") != nullptr) { coords = str2coord(wxml->StrAttribute("position")); } else { coords = str2coord(abcd->StrAttribute("value")); } float cdat[2] = {coords.x, coords.y}; entity.assign<Position>(cdat[0], cdat[1]); } else if (tname == "Visible") { entity.assign<Visible>(abcd->FloatAttribute("value")); } else if (tname == "Sprite") { auto sprite = entity.assign<Sprite>(); auto tex = abcd->Attribute("image"); sprite->addSpriteSegment(SpriteData(tex, vec2(0, 0)), vec2(0, 0)); } else if (tname == "Portal") { entity.assign<Portal>(wxml->StrAttribute("inside")); } else if (tname == "Solid") { vec2 dim; if (abcd->Attribute("value") != nullptr) dim = str2coord(abcd->StrAttribute("value")); else dim = entity.component<Sprite>().get()->getSpriteSize() * game::HLINE; float cdat[2] = {dim.x, dim.y}; entity.assign<Solid>(cdat[0], cdat[1]); } else if (tname == "Direction") { vec2 dir; if (wxml->Attribute("direction") != nullptr) { dir = str2coord(wxml->StrAttribute("direction")); } else if (wxml->Attribute("value") != nullptr) { dir = str2coord(wxml->StrAttribute("value")); } else { dir = vec2(0,0); } float cdat[2] = {dir.x, dir.y}; entity.assign<Direction>(cdat[0], cdat[1]); } else if (tname == "Physics") { float g; if (wxml->Attribute("gravity") != nullptr) { g = wxml->FloatAttribute("gravity"); } else if (wxml->Attribute("value") != nullptr) { g = wxml->FloatAttribute("value"); } else { g = 1.0f; } entity.assign<Physics>(g); } else if (tname == "Name") { entity.assign<Name>(coalesce(wxml->Attribute("name"), abcd->Attribute("value"))); } else if (tname == "Dialog") { entity.assign<Dialog>((wxml->BoolAttribute("hasDialog") ? 0 : 9999)); } else if (tname == "Grounded") { entity.assign<Grounded>(); } else if (tname == "Wander") { entity.assign<Wander>(); } else if (tname == "Animation") { entity.assign<Animate>(); auto animx = abcd->FirstChildElement(); while (animx) { std::string animType = animx->Name(); if (animType == "movement") { entity.component<Animate>().get()->frame = developFrame(animx); } animx = animx->NextSiblingElement(); } } abcd = abcd->NextSiblingElement(); } } else { UserError("Unknown tag <" + tagName + "> in file " + currentXMLFile); } } // hill creation /*else if (tagName == "hill") { addHill(ivec2 { wxml->IntAttribute("peakx"), wxml->IntAttribute("peaky") }, wxml->UnsignedAttribute("width")); }*/ 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 (id != 'p') { switch (id) { case 'd': save >> entity.component<Dialog>()->index; 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; // iterate through world tags while (wxml) { newEntity = nullptr; name = wxml->Name(); // set spawn x for player else if (name == "spawnx" && !(loadedLeft | loadedRight)) { player->loc.x = std::stoi(wxml->GetText()); } // mob creation else if (name == "rabbit") { newEntity = new Rabbit(); } else if (name == "bird") { newEntity = new Bird(); } else if (name == "trigger") { newEntity = new Trigger(); } else if (name == "door") { newEntity = new Door(); } else if (name == "page") { newEntity = new Page(); } else if (name == "cat") { newEntity = new Cat(); } else if (name == "chest") { newEntity = new Chest(); } // npc creation else if (name == "npc") { newEntity = new NPC(); } // structure creation else if (name == "structure") { newEntity = new Structures(); } // hill creation else if (name == "hill") { tmp->addHill(ivec2 { wxml->IntAttribute("peakx"), wxml->IntAttribute("peaky") }, wxml->UnsignedAttribute("width")); } if (newEntity != nullptr) { //bool alive = true; //if (wxml->QueryBoolAttribute("alive", &alive) != XML_NO_ERROR || alive) { switch (newEntity->type) { case NPCT: tmp->addNPC(dynamic_cast<NPC *>(newEntity)); break; case MOBT: tmp->addMob(dynamic_cast<Mob *>(newEntity), vec2 {0, 0}); break; case STRUCTURET: tmp->addStructure(dynamic_cast<Structures *>(newEntity)); break; default: break; } std::swap(currentXML, _currentXML); std::swap(currentXMLRaw, _currentXMLRaw); newEntity->createFromXML(wxml, tmp); std::swap(currentXML, _currentXML); std::swap(currentXMLRaw, _currentXMLRaw); //} } wxml = wxml->NextSiblingElement(); } 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(); } if (!loadedLeft && !loadedRight) { currentXML = _currentXML; currentXMLRaw = _currentXMLRaw; } else { delete _currentXMLDoc; } return tmp; }*/ WorldSystem::WorldSystem(void) : weather(WorldWeather::None), 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 ivec2 backgroundOffset = ivec2 { 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()); // used for alpha values of background textures int alpha; 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(); } switch (weather) { case WorldWeather::Snowy: alpha = 150; break; case WorldWeather::Rain: alpha = 0; break; default: alpha = 255 - worldShade * 4; break; } // 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; // draw background images. GLfloat tex_coord[] = { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,}; // TODO scroll backdrop GLfloat bgOff = game::time::getTickCount() / static_cast<float>(DAY_CYCLE * 2); GLfloat topS = .125f + bgOff; GLfloat bottomS = 0.0f + 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 }; GLfloat fron_tex_coord[] = { offset.x - backgroundOffset.x - 5, offset.y + backgroundOffset.y, 9.8f, offset.x + backgroundOffset.x + 5, offset.y + backgroundOffset.y, 9.8f, offset.x + backgroundOffset.x + 5, offset.y - backgroundOffset.y, 9.8f, offset.x + backgroundOffset.x + 5, offset.y - backgroundOffset.y, 9.8f, offset.x - backgroundOffset.x - 5, offset.y - backgroundOffset.y, 9.8f, offset.x - backgroundOffset.x - 5, offset.y + backgroundOffset.y, 9.8f }; // rendering!! glActiveTexture(GL_TEXTURE0); glUniform1i(Render::worldShader.uniform[WU_texture], 0); Render::worldShader.use(); 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); makeWorldDrawingSimplerEvenThoughAndyDoesntThinkWeCanMakeItIntoFunctions(0, back_tex_coord, scrolling_tex_coord, 6); 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 /*static GLuint starTex = Texture::loadTexture("assets/style/classic/bg/star.png"); const static float stardim = 24; GLfloat star_coord[star.size() * 5 * 6 + 1]; GLfloat *si = &star_coord[0]; if (worldShade > 0) { auto xcoord = offset.x * 0.9f; for (auto &s : star) { 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; } glBindTexture(GL_TEXTURE_2D, starTex); glUniform4f(Render::worldShader.uniform[WU_tex_color], 1.0, 1.0, 1.0, 1.3 - static_cast<float>(alpha)/255.0f); makeWorldDrawingSimplerEvenThoughAndyDoesntThinkWeCanMakeItIntoFunctions(5 * sizeof(GLfloat), &star_coord[0], &star_coord[3], star.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); makeWorldDrawingSimplerEvenThoughAndyDoesntThinkWeCanMakeItIntoFunctions_JustDrawThis(0, bg_items.data(), bg_tex.data(), bg_items.size()); Render::worldShader.unuse(); // draw the remaining layers for (int i = 0; i < 4; i++) { bgTex++; auto dim = bgTex.getTextureDim(); dim.x = HLINES(dim.x); dim.y = HLINES(dim.y); auto xcoord = offset.x * bgDraw[i][2]; bg_items.clear(); bg_tex.clear(); if (world.indoor && i == 3) { glBindTexture(GL_TEXTURE_2D, world.indoorTex); const auto& startx = world.startX; bg_items.emplace_back(startx, GROUND_HEIGHT_MINIMUM, 7 - (i * 0.1f)); bg_items.emplace_back(startx + world.indoorWidth, GROUND_HEIGHT_MINIMUM, 7 - (i * 0.1f)); bg_items.emplace_back(startx + world.indoorWidth, GROUND_HEIGHT_MINIMUM + dim.y, 7 - (i * 0.1f)); bg_items.emplace_back(startx + world.indoorWidth, 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); makeWorldDrawingSimplerEvenThoughAndyDoesntThinkWeCanMakeItIntoFunctions_JustDrawThis(0, bg_items.data(), &bg_tex[0], bg_items.size()); 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())); // 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) 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); 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 auto blackTex = Texture::genColor(Color(0, 0, 0)); static const float sheight = static_cast<float>(SCREEN_HEIGHT); if (offset.x + world.startX > s) { glBindTexture(GL_TEXTURE_2D, blackTex); 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) { glBindTexture(GL_TEXTURE_2D, blackTex); 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(); static const GLuint rug = Texture::genColor(Color {255, 0, 0}); glBindTexture(GL_TEXTURE_2D, rug); vec2 ll = vec2 {world.startX, GROUND_HEIGHT_MINIMUM}; Render::drawRect(ll, ll + vec2 {world.indoorWidth, 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::setWeather(const std::string &s) { for (unsigned int i = 3; i--;) { if (WorldWeatherString[i] == s) { weather = static_cast<WorldWeather>(i); return; } } weather = WorldWeather::None; } 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<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; if (line + dir * 2 < 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; // TODO ground flag } } // insure that the entity doesn't fall off either edge of the world. if (loc.x < world.startX) { vel.x = 0; loc.x = world.startX + HLINES(0.5f); } else if (loc.x + dim.width + game::HLINE > -(static_cast<int>(world.startX))) { 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))) { ui::toggleBlack(); ui::waitForCover(); while (waitToSwap) std::this_thread::sleep_for(1ms); load(world.toRight); game::engine.getSystem<PlayerSystem>()->setX(world.startX + HLINES(10)); ui::toggleBlack(); } } void WorldSystem::goWorldLeft(Position& p) { if (!(world.toLeft.empty()) && (p.x < world.startX + HLINES(10))) { ui::toggleBlack(); ui::waitForCover(); while (waitToSwap) std::this_thread::sleep_for(1ms); load(world.toLeft); game::engine.getSystem<PlayerSystem>()->setX(world.startX * -1 - HLINES(15)); ui::toggleBlack(); } } 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()) { ui::toggleBlack(); ui::waitForCover(); while (waitToSwap) std::this_thread::sleep_for(1ms); load(file); ui::toggleBlack(); } }