#include <world.hpp> /* ---------------------------------------------------------------------------- ** Includes section ** --------------------------------------------------------------------------*/ // standard library headers #include <algorithm> #include <sstream> #include <fstream> #include <memory> #include <mutex> // local game headers #include <ui.hpp> #include <gametime.hpp> #include <render.hpp> #include <engine.hpp> #include <components.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; // 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 constexpr const unsigned int GRASS_HEIGHT = 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 } }; std::string currentXMLRaw; XMLDocument currentXMLDoc; /* ---------------------------------------------------------------------------- ** Functions section ** --------------------------------------------------------------------------*/ void WorldSystem::generate(unsigned 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 + 2; w->grassHeight[1] = (randGet() % 16) / 3 + 2; } } // define x-coordinate of world's leftmost 'line' world.startX = (width - GROUND_HILLINESS) * game::HLINE / 2 * -1; } static Color ambient; bool WorldSystem::save(const std::string& s) { (void)s; /*for (const auto &e : entity) e->saveToXML(); currentXMLDoc.SaveFile((s.empty() ? currentXML : xmlFolder + s).c_str(), false);*/ return false; } /*static bool loadedLeft = false; static bool loadedRight = false; World *loadWorldFromXML(std::string path) { if (!currentXML.empty()) currentWorld->save(); return loadWorldFromXMLNoSave(path); } World *loadWorldFromPtr(World *ptr) { currentWorld->save(); // save the current world to the current xml path if (ptr->getToLeft() == currentXML) { currentWorldToLeft = currentWorld; loadedRight = true; currentWorldToRight = loadWorldFromXMLNoSave(ptr->getToRight()); loadedRight = false; } else if (ptr->getToRight() == currentXML) { currentWorldToRight = currentWorld; loadedLeft = true; currentWorldToLeft = loadWorldFromXMLNoSave(ptr->getToLeft()); loadedLeft = false; } return ptr; } World * loadWorldFromXMLNoTakeover(std::string path) { loadedLeft = true, loadedRight = true; auto ret = loadWorldFromXMLNoSave(path); loadedLeft = false, loadedRight = false; return ret; } */ void WorldSystem::load(const std::string& file) { std::string xmlRaw; std::string xmlPath; // check for empty file name if (file.empty()) return; // load file data to string xmlPath = xmlFolder + file; auto xmlRawData = readFile(xmlPath.c_str()); 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..?)"); // 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); } } // 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[(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->UnsignedAttribute("width") / game::HLINE); } // indoor stuff else if (tagName == "house") { if (!world.indoor) UserError("<house> can only be used inside <IndoorWorld>"); world.indoorWidth = wxml->FloatAttribute("width"); world.indoorTex = Texture::loadTexture(wxml->Attribute("texture")); } // 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())); } // hill creation /*else if (tagName == "hill") { addHill(ivec2 { wxml->IntAttribute("peakx"), wxml->IntAttribute("peaky") }, wxml->UnsignedAttribute("width")); }*/ wxml = wxml->NextSiblingElement(); } } /* 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 auto HLINE = game::HLINE; 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 = world.data.size() * HLINE; // used for alpha values of background textures int alpha; static bool ambientUpdaterStarted = false; if (!ambientUpdaterStarted) { ambientUpdaterStarted = true; std::thread([&](void) { while (true) { 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(std::chrono::milliseconds(1)); } }).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()/24000.0f; GLfloat topS = .125f + bgOff; GLfloat bottomS = 0.0f + bgOff; if (topS < 0.0f) topS += 1.0f; if (bottomS < 0.0f) bottomS += 1.0f; if(bgOff < 0)std::cout << bottomS << "," << topS << std::endl; GLfloat scrolling_tex_coord[] = {0.0f, bottomS, 1.0f, bottomS, 1.0f, bottomS, 1.0f, bottomS, 0.0f, bottomS, 0.0f, bottomS}; 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++; dim2 mountainDim = bgTex.getTextureDim(); 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 (unsigned int i = 0; i < 4; i++) { bgTex++; auto dim = bgTex.getTextureDim(); 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*.1)); bg_items.emplace_back(startx + world.indoorWidth, GROUND_HEIGHT_MINIMUM, 7-(i*.1)); bg_items.emplace_back(startx + world.indoorWidth, GROUND_HEIGHT_MINIMUM + dim.y, 7-(i*.1)); bg_items.emplace_back(startx + world.indoorWidth, GROUND_HEIGHT_MINIMUM + dim.y, 7-(i*.1)); bg_items.emplace_back(startx, GROUND_HEIGHT_MINIMUM + dim.y, 7-(i*.1)); bg_items.emplace_back(startx, GROUND_HEIGHT_MINIMUM, 7-(i*.1)); } else { for (int j = world.startX; j <= -world.startX; j += dim.x) { bg_items.emplace_back(j + xcoord, GROUND_HEIGHT_MINIMUM, 7-(i*.1)); bg_items.emplace_back(j + dim.x + xcoord, GROUND_HEIGHT_MINIMUM, 7-(i*.1)); bg_items.emplace_back(j + dim.x + xcoord, GROUND_HEIGHT_MINIMUM + dim.y, 7-(i*.1)); bg_items.emplace_back(j + dim.x + xcoord, GROUND_HEIGHT_MINIMUM + dim.y, 7-(i*.1)); bg_items.emplace_back(j + xcoord, GROUND_HEIGHT_MINIMUM + dim.y, 7-(i*.1)); bg_items.emplace_back(j + xcoord, GROUND_HEIGHT_MINIMUM, 7-(i*.1)); } } 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 /*+ player->width / 2*/ - world.startX) / HLINE; // only draw world within player vision iStart = std::clamp(static_cast<int>(pOffset - (SCREEN_WIDTH / 2 / HLINE) - GROUND_HILLINESS), 0, static_cast<int>(world.data.size())); iEnd = std::clamp(static_cast<int>(pOffset + (SCREEN_WIDTH / 2 / HLINE)), 0, static_cast<int>(world.data.size())) + 1; // draw the dirt bgTex++; std::vector<std::pair<vec2,vec3>> c; 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; c.push_back(std::make_pair(vec2(0, 0), vec3(world.startX + HLINES(i), world.data[i].groundHeight - GRASS_HEIGHT, -4.0f))); c.push_back(std::make_pair(vec2(1, 0), vec3(world.startX + HLINES(i) + HLINE, world.data[i].groundHeight - GRASS_HEIGHT, -4.0f))); c.push_back(std::make_pair(vec2(1, ty),vec3(world.startX + HLINES(i) + HLINE, 0, -4.0f))); c.push_back(std::make_pair(vec2(1, ty),vec3(world.startX + HLINES(i) + HLINE, 0, -4.0f))); c.push_back(std::make_pair(vec2(0, ty),vec3(world.startX + HLINES(i), 0, -4.0f))); c.push_back(std::make_pair(vec2(0, 0), vec3(world.startX + HLINES(i), world.data[i].groundHeight - GRASS_HEIGHT, -4.0f))); if (world.data[i].groundHeight == GROUND_HEIGHT_MINIMUM - 1) world.data[i].groundHeight = 0; } std::vector<GLfloat> dirtc; std::vector<GLfloat> dirtt; for (auto &v : c) { dirtc.push_back(v.second.x); dirtc.push_back(v.second.y); dirtc.push_back(v.second.z); dirtt.push_back(v.first.x); dirtt.push_back(v.first.y); } Render::worldShader.use(); glUniform1f(Render::worldShader.uniform[WU_light_impact], 0.45f); Render::worldShader.enable(); glVertexAttribPointer(Render::worldShader.coord, 3, GL_FLOAT, GL_FALSE, 0, &dirtc[0]); glVertexAttribPointer(Render::worldShader.tex, 2, GL_FLOAT, GL_FALSE, 0, &dirtt[0]); glDrawArrays(GL_TRIANGLES, 0 , c.size()); Render::worldShader.disable(); Render::worldShader.unuse(); if (!world.indoor) { bgTex++; safeSetColorA(255, 255, 255, 255); c.clear(); std::vector<GLfloat> grassc; std::vector<GLfloat> grasst; 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) { const auto& worldStart = world.startX; c.push_back(std::make_pair(vec2(0, 0),vec3(worldStart + HLINES(i) , wd.groundHeight + gh[0], -3))); c.push_back(std::make_pair(vec2(1, 0),vec3(worldStart + HLINES(i) + HLINE / 2, wd.groundHeight + gh[0], -3))); c.push_back(std::make_pair(vec2(1, 1),vec3(worldStart + HLINES(i) + HLINE / 2, wd.groundHeight - GRASS_HEIGHT, -3))); c.push_back(std::make_pair(vec2(1, 1),vec3(worldStart + HLINES(i) + HLINE / 2, wd.groundHeight - GRASS_HEIGHT, -3))); c.push_back(std::make_pair(vec2(0, 1),vec3(worldStart + HLINES(i) , wd.groundHeight - GRASS_HEIGHT, -3))); c.push_back(std::make_pair(vec2(0, 0),vec3(worldStart + HLINES(i) , wd.groundHeight + gh[0], -3))); c.push_back(std::make_pair(vec2(0, 0),vec3(worldStart + HLINES(i) + HLINE / 2, wd.groundHeight + gh[1], -3))); c.push_back(std::make_pair(vec2(1, 0),vec3(worldStart + HLINES(i) + HLINE , wd.groundHeight + gh[1], -3))); c.push_back(std::make_pair(vec2(1, 1),vec3(worldStart + HLINES(i) + HLINE , wd.groundHeight - GRASS_HEIGHT, -3))); c.push_back(std::make_pair(vec2(1, 1),vec3(worldStart + HLINES(i) + HLINE , wd.groundHeight - GRASS_HEIGHT, -3))); c.push_back(std::make_pair(vec2(0, 1),vec3(worldStart + HLINES(i) + HLINE / 2, wd.groundHeight - GRASS_HEIGHT, -3))); c.push_back(std::make_pair(vec2(0, 0),vec3(worldStart + HLINES(i) + HLINE / 2, wd.groundHeight + gh[1], -3))); } } for (auto &v : c) { grassc.push_back(v.second.x); grassc.push_back(v.second.y); grassc.push_back(v.second.z); grasst.push_back(v.first.x); grasst.push_back(v.first.y); } Render::worldShader.use(); glUniform1f(Render::worldShader.uniform[WU_light_impact], 1.0f); Render::worldShader.enable(); glVertexAttribPointer(Render::worldShader.coord, 3, GL_FLOAT, GL_FALSE, 0, &grassc[0]); glVertexAttribPointer(Render::worldShader.tex, 2, GL_FLOAT, GL_FALSE, 0, &grasst[0]); glDrawArrays(GL_TRIANGLES, 0 , c.size()); 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, vec2 {ll.x + world.indoorWidth, ll.y + 4}, -3); Render::worldShader.unuse(); } //player->draw(); } void WorldSystem::receive(const BGMToggleEvent &bte) { if (bte.world == nullptr || world.bgm != bte.file) { Mix_FadeOutMusic(800); if (bgmObj != nullptr) Mix_FreeMusic(bgmObj); //worldBgmFile = bte.file; 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; (void)dt; // fade in music if not playing if (bgmObj != nullptr && !Mix_PlayingMusic()) Mix_FadeInMusic(bgmObj, -1, 2000); // run detect stuff detect(dt); } void WorldSystem::detect(entityx::TimeDelta dt) { game::entities.each<Position, Direction, Health, Solid>( [&](entityx::Entity e, Position &loc, Direction &vel, Health &health, 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 } } // handle gravity else if (vel.y > -2.0f) { vel.y -= GRAVITY_CONSTANT * dt; } // 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 > -((int)world.startX)) { vel.x = 0; loc.x = -((int)world.startX) - dim.width - game::HLINE; } }); }