diff --git a/Aquaria/AquariaMenuItem.cpp b/Aquaria/AquariaMenuItem.cpp index 521a6db..71d8689 100644 --- a/Aquaria/AquariaMenuItem.cpp +++ b/Aquaria/AquariaMenuItem.cpp @@ -28,6 +28,8 @@ bool AquariaGuiElement::canDirMoveGlobal = true; int AquariaGuiElement::currentGuiInputLevel = 0; +AquariaGuiElement *AquariaGuiElement::currentFocus = 0; + AquariaGuiElement::AquariaGuiElement() { for (int i = 0; i < DIR_MAX; i++) @@ -78,6 +80,7 @@ void AquariaGuiElement::setFocus(bool v) if (v) { + currentFocus = this; if (dsq->inputMode == INPUT_JOYSTICK) core->setMousePosition(getGuiPosition()); @@ -91,6 +94,8 @@ void AquariaGuiElement::setFocus(bool v) } } } + else if(this == currentFocus) + currentFocus = 0; } void AquariaGuiElement::updateMovement(float dt) @@ -246,6 +251,27 @@ void AquariaGuiElement::updateMovement(float dt) } } +AquariaGuiElement *AquariaGuiElement::getClosestGuiElement(const Vector& pos) +{ + AquariaGuiElement *gui = 0, *closest = 0; + float minlen = 0; + for (GuiElements::iterator i = guiElements.begin(); i != guiElements.end(); i++) + { + gui = (*i); + if (gui->isGuiVisible() && gui->hasInput()) + { + Vector dist = gui->getGuiPosition() - pos; + float len = dist.getSquaredLength2D(); + if(!closest || len < minlen) + { + closest = gui; + minlen = len; + } + } + } + return closest; +} + AquariaGuiQuad::AquariaGuiQuad() : Quad(), AquariaGuiElement() { @@ -810,6 +836,7 @@ AquariaMenuItem::AquariaMenuItem() : Quad(), ActionMapper(), AquariaGuiElement() void AquariaMenuItem::destroy() { + setFocus(false); Quad::destroy(); AquariaGuiElement::clean(); } @@ -978,9 +1005,10 @@ bool AquariaMenuItem::isCursorInMenuItem() { std::swap(hw, hh); } - if (v.y > position.y - hh && v.y < position.y + hh) + Vector pos = getWorldPosition(); + if (v.y > pos.y - hh && v.y < pos.y + hh) { - if (v.x > position.x - hw && v.x < position.x + hw) + if (v.x > pos.x - hw && v.x < pos.x + hw) { return true; } diff --git a/Aquaria/AquariaMenuItem.h b/Aquaria/AquariaMenuItem.h index 8f1b59d..c9c4ad5 100644 --- a/Aquaria/AquariaMenuItem.h +++ b/Aquaria/AquariaMenuItem.h @@ -43,6 +43,8 @@ public: int guiInputLevel; static int currentGuiInputLevel; bool hasInput(); + static AquariaGuiElement *currentFocus; + static AquariaGuiElement *getClosestGuiElement(const Vector& pos); protected: typedef std::list GuiElements; static GuiElements guiElements; @@ -80,10 +82,11 @@ public: void useGlow(const std::string &tex, int w, int h); void useSound(const std::string &tex); - bool isCursorInMenuItem(); + virtual bool isCursorInMenuItem(); Vector getGuiPosition(); bool isGuiVisible(); int shareAlpha; + protected: std::string useSfx; diff --git a/Aquaria/Continuity.cpp b/Aquaria/Continuity.cpp index a3dd8fb..8a5fb6c 100644 --- a/Aquaria/Continuity.cpp +++ b/Aquaria/Continuity.cpp @@ -24,6 +24,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "ScriptedEntity.h" #include "AutoMap.h" #include "GridRender.h" +#include "DeflateCompressor.h" #include "../ExternalLibs/tinyxml.h" @@ -870,7 +871,7 @@ void Continuity::loadTreasureData() std::string line, gfx; int num, use; float sz; - std::ifstream in2("data/treasures.txt"); + InStream in2("data/treasures.txt"); while (std::getline(in2, line)) { std::istringstream is(line); @@ -903,7 +904,7 @@ void Continuity::loadIngredientData(const std::string &file) /* int num; - std::ifstream in2("data/ingredientdescriptions.txt"); + InStream in2("data/ingredientdescriptions.txt"); while (std::getline(in2, line)) { IngredientDescription desc; @@ -916,7 +917,7 @@ void Continuity::loadIngredientData(const std::string &file) clearIngredientData(); recipes.clear(); - std::ifstream in(file.c_str()); + InStream in(file.c_str()); bool recipes = false; while (std::getline(in, line)) @@ -1241,7 +1242,7 @@ void Continuity::loadEatBank() { eats.clear(); - std::ifstream inf("data/eats.txt"); + InStream inf("data/eats.txt"); EatData curData; std::string read; @@ -2183,7 +2184,7 @@ void Continuity::setActivePet(int flag) void Continuity::loadPetData() { petData.clear(); - std::ifstream in("data/pets.txt"); + InStream in("data/pets.txt"); std::string read; while (std::getline(in, read)) { @@ -2469,12 +2470,30 @@ void Continuity::saveFile(int slot, Vector position, unsigned char *scrShotData, doc.InsertEndChild(startData); - // FIXME: Patch TinyXML to write out a string and compress in-memory + std::string fn = core->adjustFilenameCase(getSaveFileName(slot, "aqs")); + FILE *fh = fopen(fn.c_str(), "wb"); + if(!fh) + { + debugLog("FAILED TO SAVE GAME"); + return; + } - doc.SaveFile(dsq->getSaveDirectory() + "/poot.tmp"); - - packFile(dsq->getSaveDirectory() + "/poot.tmp", getSaveFileName(slot, "aqs"), 9); - remove((dsq->getSaveDirectory() + "/poot.tmp").c_str()); + TiXmlPrinter printer; + doc.Accept( &printer ); + const char* xmlstr = printer.CStr(); + ZlibCompressor z; + z.init((void*)xmlstr, printer.Size(), ZlibCompressor::REUSE); + z.SetForceCompression(true); + z.Compress(3); + std::ostringstream os; + os << "Writing " << z.size() << " bytes to save file " << fn; + debugLog(os.str()); + size_t written = fwrite(z.contents(), 1, z.size(), fh); + if (written != z.size()) + { + debugLog("FAILED TO WRITE SAVE FILE COMPLETELY"); + } + fclose(fh); } std::string Continuity::getSaveFileName(int slot, const std::string &pfix) @@ -2491,7 +2510,7 @@ void Continuity::loadFileData(int slot, TiXmlDocument &doc) { unsigned long size = 0; char *buf = readCompressedFile(teh_file, &size); - if (!doc.LoadMem(buf, size)) + if (!buf || !doc.LoadMem(buf, size)) errorLog("Failed to load save data: " + teh_file); return; } @@ -3267,7 +3286,7 @@ void Continuity::reset() health = maxHealth; speedTypes.clear(); - std::ifstream inFile("data/speedtypes.txt"); + InStream inFile("data/speedtypes.txt"); int n, spd; while (inFile >> n) { diff --git a/Aquaria/DSQ.cpp b/Aquaria/DSQ.cpp index b1e444b..bc3f7a3 100644 --- a/Aquaria/DSQ.cpp +++ b/Aquaria/DSQ.cpp @@ -37,6 +37,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "RoundedRect.h" #include "TTFFont.h" +#include "ModSelector.h" +#include "Network.h" + #ifdef BBGE_BUILD_OPENGL #include @@ -170,27 +173,6 @@ DSQ::DSQ(std::string fileSystem) : Core(fileSystem, LR_MAX, APPNAME, PARTICLE_AM almb = armb = 0; bar_left = bar_right = bar_up = bar_down = barFade_left = barFade_right = 0; - // do copy stuff -#ifdef BBGE_BUILD_UNIX - std::string fn; - fn = getPreferencesFolder() + "/" + userSettingsFilename; - if (!exists(fn)) - Linux_CopyTree(core->adjustFilenameCase(userSettingsFilename).c_str(), core->adjustFilenameCase(fn).c_str()); - - fn = getUserDataFolder() + "/_mods"; - if (!exists(fn)) - Linux_CopyTree(core->adjustFilenameCase("_mods").c_str(), core->adjustFilenameCase(fn).c_str()); -#endif - - std::string p1 = getUserDataFolder(); - std::string p2 = getUserDataFolder() + "/save"; -#if defined(BBGE_BUILD_UNIX) - mkdir(p1.c_str(), S_IRWXU); - mkdir(p2.c_str(), S_IRWXU); -#elif defined(BBGE_BUILD_WINDOWS) - CreateDirectoryA(p2.c_str(), NULL); -#endif - difficulty = DIFF_NORMAL; /* @@ -214,7 +196,7 @@ DSQ::DSQ(std::string fileSystem) : Core(fileSystem, LR_MAX, APPNAME, PARTICLE_AM subtext = 0; subbox = 0; menuSelectDelay = 0; - modSelector = 0; + modSelectorScr = 0; blackout = 0; useMic = false; autoSingMenuOpen = false; @@ -228,9 +210,6 @@ DSQ::DSQ(std::string fileSystem) : Core(fileSystem, LR_MAX, APPNAME, PARTICLE_AM achievement_box = 0; #endif - vars = &v; - v.load(); - #ifdef AQUARIA_BUILD_CONSOLE console = 0; #endif @@ -252,25 +231,6 @@ DSQ::DSQ(std::string fileSystem) : Core(fileSystem, LR_MAX, APPNAME, PARTICLE_AM for (int i = 0; i < 16; i++) firstElementOnLayer[i] = 0; - addStateInstance(game = new Game); - addStateInstance(new GameOver); -#ifdef AQUARIA_BUILD_SCENEEDITOR - addStateInstance(new AnimationEditor); -#endif - addStateInstance(new Intro2); - addStateInstance(new BitBlotLogo); -#ifdef AQUARIA_BUILD_SCENEEDITOR - addStateInstance(new ParticleEditor); -#endif - addStateInstance(new Credits); - addStateInstance(new Intro); - addStateInstance(new Nag); - - //addStateInstance(new Logo); - //addStateInstance(new SCLogo); - //addStateInstance(new IntroText); - //addStateInstance(new Intro); - //stream = 0; } @@ -950,6 +910,49 @@ This build is not yet final, and as such there are a couple things lacking. They // steam callbacks are inited here dsq->continuity.init(); + vars = &v; + v.load(); + + // do copy stuff +#ifdef BBGE_BUILD_UNIX + std::string fn; + fn = getPreferencesFolder() + "/" + userSettingsFilename; + if (!exists(fn)) + Linux_CopyTree(core->adjustFilenameCase(userSettingsFilename).c_str(), core->adjustFilenameCase(fn).c_str()); + + fn = getUserDataFolder() + "/_mods"; + if (!exists(fn)) + Linux_CopyTree(core->adjustFilenameCase("_mods").c_str(), core->adjustFilenameCase(fn).c_str()); +#endif + + std::string p1 = getUserDataFolder(); + std::string p2 = getUserDataFolder() + "/save"; +#if defined(BBGE_BUILD_UNIX) + mkdir(p1.c_str(), S_IRWXU); + mkdir(p2.c_str(), S_IRWXU); +#elif defined(BBGE_BUILD_WINDOWS) + CreateDirectoryA(p2.c_str(), NULL); +#endif + + addStateInstance(game = new Game); + addStateInstance(new GameOver); +#ifdef AQUARIA_BUILD_SCENEEDITOR + addStateInstance(new AnimationEditor); +#endif + addStateInstance(new Intro2); + addStateInstance(new BitBlotLogo); +#ifdef AQUARIA_BUILD_SCENEEDITOR + addStateInstance(new ParticleEditor); +#endif + addStateInstance(new Credits); + addStateInstance(new Intro); + addStateInstance(new Nag); + + //addStateInstance(new Logo); + //addStateInstance(new SCLogo); + //addStateInstance(new IntroText); + //addStateInstance(new Intro); + //packReadInfo("mus.dat"); this->setBaseTextureDirectory("gfx/"); @@ -1090,37 +1093,7 @@ This build is not yet final, and as such there are a couple things lacking. They user.apply(); - /* - - sound->loadLocalSound("bbfloppy"); - - Quad *disk = new Quad("bitblot/disk", Vector(400, 300)); - disk->alpha = 0; - disk->alpha.interpolateTo(1, 0.5); - disk->scale = Vector(0.6, 0.6); - addRenderObject(disk, LR_HUD); - - debugLog("core->main"); - core->main(0.5); - debugLog("end of core->main"); - - disk->position.interpolateTo(Vector(400,560), 0.5); - - core->main(0.4); - - sound->playSfx("bbfloppy"); - - core->main(0.1); - - */ - - - /* - loading = new Quad("loading", Vector(400,300)); - loading->followCamera = 1; - loading->alpha = 0.01; - addRenderObject(loading, LR_HUD); - */ + applyPatches(); loading = new Quad("loading/juice", Vector(400,300)); loading->alpha = 1.0; @@ -2096,7 +2069,7 @@ void DSQ::toggleMuffleSound(bool toggle) */ } -void loadModsCallback(const std::string &filename, intptr_t param) +void DSQ::loadModsCallback(const std::string &filename, intptr_t param) { //errorLog(filename); int pos = filename.find_last_of('/')+1; @@ -2104,9 +2077,36 @@ void loadModsCallback(const std::string &filename, intptr_t param) std::string name = filename.substr(pos, pos2-pos); ModEntry m; m.path = name; + m.id = dsq->modEntries.size(); + + TiXmlDocument d; + if(!Mod::loadModXML(&d, name)) + { + std::ostringstream os; + os << "Failed to load mod xml: " << filename << " -- Error: " << d.ErrorDesc(); + dsq->debugLog(os.str()); + return; + } + + m.type = Mod::getTypeFromXML(d.FirstChildElement("AquariaMod")); + dsq->modEntries.push_back(m); - debugLog("Loaded ModEntry [" + m.path + "]"); + std::ostringstream ss; + ss << "Loaded ModEntry [" << m.path << "] -> " << m.id << " | type " << m.type; + + dsq->debugLog(ss.str()); +} + +void DSQ::loadModPackagesCallback(const std::string &filename, intptr_t param) +{ + bool ok = dsq->mountModPackage(filename); + + std::ostringstream ss; + ss << "Mount Mod Package '" << filename << "' : " << (ok ? "ok" : "FAIL"); + dsq->debugLog(ss.str()); + + // they will be enumerated by the following loadModsCallback round } void DSQ::startSelectedMod() @@ -2127,28 +2127,6 @@ void DSQ::startSelectedMod() } } -void DSQ::selectNextMod() -{ - selectedMod ++; - - if (selectedMod >= modEntries.size()) - selectedMod = 0; - - if (modSelector) - modSelector->refreshTexture(); -} - -void DSQ::selectPrevMod() -{ - selectedMod --; - - if (selectedMod < 0) - selectedMod = modEntries.size()-1; - - if (modSelector) - modSelector->refreshTexture(); -} - ModEntry* DSQ::getSelectedModEntry() { if (!modEntries.empty() && selectedMod >= 0 && selectedMod < modEntries.size()) @@ -2159,11 +2137,132 @@ ModEntry* DSQ::getSelectedModEntry() void DSQ::loadMods() { modEntries.clear(); - + +#ifdef BBGE_BUILD_VFS + + // first load the packages, then enumerate XMLs + forEachFile(mod.getBaseModPath(), ".aqmod", loadModPackagesCallback, 0); + forEachFile(mod.getBaseModPath(), ".zip", loadModPackagesCallback, 0); +#endif + forEachFile(mod.getBaseModPath(), ".xml", loadModsCallback, 0); selectedMod = 0; } +void DSQ::applyPatches() +{ +#ifndef AQUARIA_DEMO +#ifdef BBGE_BUILD_VFS + + // This is to allow files in patches to override files in mods on non-win32 systems (theoretically) + if(!vfs.GetDir("_mods")) + { + vfs.MountExternalPath(mod.getBaseModPath().c_str(), "_mods"); + } + + // user wants mods, but not yet loaded + if(activePatches.size() && modEntries.empty()) + loadMods(); + + for (std::set::iterator it = activePatches.begin(); it != activePatches.end(); ++it) + for(int i = 0; i < modEntries.size(); ++i) + if(modEntries[i].type == MODTYPE_PATCH) + if(!nocasecmp(modEntries[i].path.c_str(), it->c_str())) + applyPatch(modEntries[i].path); +#endif +#endif +} + + +#ifdef BBGE_BUILD_VFS + +static void refr_pushback(ttvfs::VFSDir *vd, void *user) +{ + std::list *li = (std::list*)user; + li->push_back(vd); +} + +static void refr_insert(VFILE *vf, void *user) +{ + // texture names are like: "naija/naija2-frontleg3" - no .png extension, and no gfx/ path + std::set*files = (std::set*)user; + std::string t = vf->fullname(); + size_t dotpos = t.rfind('.'); + size_t pathstart = t.find("gfx/"); + if(dotpos == std::string::npos || pathstart == std::string::npos || dotpos < pathstart) + return; // whoops + + files->insert(t.substr(pathstart + 4, dotpos - (pathstart + 4))); +} + + +// this thing is rather heuristic... but works for normal mod paths +// there is apparently nothing else except Textures that is a subclass of Resource, +// thus directly using "gfx" subdir should be fine... +void DSQ::refreshResourcesForPatch(const std::string& name) +{ + ttvfs::VFSDir *vd = vfs.GetDir((mod.getBaseModPath() + name + "/gfx").c_str()); // only textures are resources, anyways + if(!vd) + return; + + std::list left; + std::set files; + left.push_back(vd); + + do + { + vd = left.front(); + left.pop_front(); + vd->forEachDir(refr_pushback, &left); + vd->forEachFile(refr_insert, &files); + } + while(left.size()); + + std::ostringstream os; + os << "refreshResourcesForPatch - " << files.size() << " to refresh"; + debugLog(os.str()); + + for(int i = 0; i < dsq->resources.size(); ++i) + { + Resource *r = dsq->resources[i]; + if(files.find(r->name) != files.end()) + r->reload(); + } +} +#else +void DSQ::refreshResourcesForPatch(const std::string& name) {} +#endif + +void DSQ::applyPatch(const std::string& name) +{ +#ifdef BBGE_BUILD_VFS +#ifdef AQUARIA_DEMO + return; +#endif + + std::string src = mod.getBaseModPath(); + src += name; + debugLog("Apply patch: " + src); + vfs.Mount(src.c_str(), "", true); + + activePatches.insert(name); + refreshResourcesForPatch(name); +#endif +} + +void DSQ::unapplyPatch(const std::string& name) +{ +#ifdef BBGE_BUILD_VFS + std::string src = mod.getBaseModPath(); + src += name; + debugLog("Unapply patch: " + src); + vfs.Unmount(src.c_str(), ""); + + activePatches.erase(name); + refreshResourcesForPatch(name); +#endif +} + void DSQ::playMenuSelectSfx() { core->sound->playSfx("MenuSelect"); @@ -2225,6 +2324,8 @@ void DSQ::playPositionalSfx(const std::string &name, const Vector &position, flo void DSQ::shutdown() { + Network::shutdown(); + scriptInterface.shutdown(); precacher.clean(); /* @@ -2681,11 +2782,6 @@ void DSQ::doModSelect() modIsSelected = false; dsq->loadMods(); - - selectedMod = user.data.lastSelectedMod; - - if (selectedMod >= modEntries.size() || selectedMod < 0) - selectedMod = 0; createModSelector(); @@ -2699,7 +2795,6 @@ void DSQ::doModSelect() if (modIsSelected) { - user.data.lastSelectedMod = selectedMod; dsq->startSelectedMod(); } @@ -2723,66 +2818,66 @@ void DSQ::createModSelector() blackout->alpha.interpolateTo(1, 0.2); addRenderObject(blackout, LR_MENU); - menu.resize(4); + modSelectorScr = new ModSelectorScreen(); + modSelectorScr->position = Vector(400,300); + modSelectorScr->setWidth(getVirtualWidth()); // just to be sure + modSelectorScr->setHeight(getVirtualHeight()); + modSelectorScr->autoWidth = AUTO_VIRTUALWIDTH; + modSelectorScr->autoHeight = AUTO_VIRTUALHEIGHT; + modSelectorScr->init(); + addRenderObject(modSelectorScr, LR_MENU); +} - menu[0] = new Quad("Cancel", Vector(750,580)); - menu[0]->followCamera = 1; - addRenderObject(menu[0], LR_MENU); +bool DSQ::modIsKnown(const std::string& name) +{ + std::string nlower = name; + stringToLower(nlower); + for(int i = 0; i < modEntries.size(); ++i) + { + std::string elower = modEntries[i].path; + stringToLower(elower); + if(nlower == elower) + return true; + } + return false; +} - AquariaMenuItem *a = new AquariaMenuItem(); - //menu[0]->setLabel("Cancel"); - a->useGlow("glow", 200, 50); - a->event.set(MakeFunctionEvent(DSQ,onExitSaveSlotMenu)); - a->position = Vector(750, 580); - addRenderObject(a, LR_MENU); - menu[1] = a; - AquariaMenuItem *m1 = a; +bool DSQ::mountModPackage(const std::string& pkg) +{ +#ifdef BBGE_BUILD_VFS + ttvfs::VFSDir *vd = vfs.AddArchive(pkg.c_str(), false, mod.getBaseModPath().c_str()); + if (!vd) + { + debugLog("Package: Unable to load " + pkg); + return false; + } + debugLog("Package: Mounted " + pkg + " as archive in _mods"); + return true; +#else + debugLog("Package: Can't mount " + pkg + ", VFS support disabled"); + return false; +#endif +} +#ifdef BBGE_BUILD_VFS +static void _CloseSubdirCallback(ttvfs::VFSDir *vd, void*) +{ + vd->close(); + ttvfs::VFSBase *origin = vd->getOrigin(); + if(origin) + origin->close(); +} +#endif - a = new AquariaMenuItem(); - a->useQuad("gui/arrow-left"); - a->useGlow("glow", 100, 50); - a->useSound("Click"); - a->event.set(MakeFunctionEvent(DSQ, selectPrevMod)); - a->position = Vector(150, 300); - addRenderObject(a, LR_MENU); - - menu[2] = a; - - AquariaMenuItem *m2 = a; - - a = new AquariaMenuItem(); - a->useQuad("gui/arrow-right"); - a->useGlow("glow", 100, 50); - a->useSound("Click"); - a->event.set(MakeFunctionEvent(DSQ, selectNextMod)); - a->position = Vector(650, 300); - addRenderObject(a, LR_MENU); - - menu[3] = a; - - AquariaMenuItem *m3 = a; - - modSelector = new ModSelector(); - modSelector->position = Vector(400,300); - modSelector->alpha = 0; - modSelector->alpha.interpolateTo(1, 0.4); - modSelector->followCamera = 1; - addRenderObject(modSelector, LR_MENU); - - modSelector->setFocus(true); - - m2->setDirMove(DIR_RIGHT, modSelector); - modSelector->setDirMove(DIR_RIGHT, m3); - modSelector->setDirMove(DIR_LEFT, m2); - m2->setDirMove(DIR_LEFT, modSelector); - - modSelector->setDirMove(DIR_DOWN, m1); - m2->setDirMove(DIR_DOWN, m1); - m3->setDirMove(DIR_DOWN, m1); - - m1->setDirMove(DIR_UP, modSelector); +// This just closes some file handles, nothing fancy +void DSQ::unloadMods() +{ +#ifdef BBGE_BUILD_VFS + ttvfs::VFSDir *mods = vfs.GetDir(mod.getBaseModPath().c_str()); + if(mods) + mods->forEachDir(_CloseSubdirCallback); +#endif } void DSQ::applyParallaxUserSettings() @@ -2802,14 +2897,18 @@ void DSQ::clearModSelector() blackout = 0; } - if (modSelector) + if(modSelectorScr) { - modSelector->setLife(1); - modSelector->setDecayRate(2); - modSelector->fadeAlphaWithLife = 1; - modSelector = 0; + modSelectorScr->close(); + modSelectorScr->setLife(1); + modSelectorScr->setDecayRate(2); + modSelectorScr->fadeAlphaWithLife = 1; + modSelectorScr = 0; } + // This just closes some file handles, nothing fancy + unloadMods(); + clearMenu(); } @@ -2955,6 +3054,9 @@ void DSQ::title(bool fade) { mod.shutdown(); } + + // Will be re-loaded on demand + unloadMods(); // VERY important dsq->continuity.reset(); @@ -3193,10 +3295,6 @@ void DSQ::doSaveSlotMenu(SaveSlotMode ssm, const Vector &position) { std::ostringstream os; os << dsq->getSaveDirectory() << "/screen-" << numToZeroString(selectedSaveSlot->getSlotIndex(), 4) << ".zga"; - std::string tempfile = dsq->getSaveDirectory() + "/poot-s.tmp"; - - //saveCenteredScreenshotTGA(tempfile, scrShotWidth); - //saveSizedScreenshotTGA(tempfile,512,1); // Cut off top and bottom to get a 4:3 aspect ratio. int adjHeight = (scrShotWidth * 3.0f) / 4.0f; @@ -3205,12 +3303,8 @@ void DSQ::doSaveSlotMenu(SaveSlotMode ssm, const Vector &position) int adjOffset = scrShotWidth * ((scrShotHeight-adjHeight)/2) * 4; memmove(scrShotData, scrShotData + adjOffset, adjImageSize); memset(scrShotData + adjImageSize, 0, imageDataSize - adjImageSize); - tgaSave(tempfile.c_str(), scrShotWidth, scrShotHeight, 32, scrShotData); + zgaSave(os.str().c_str(), scrShotWidth, scrShotHeight, 32, scrShotData); scrShotData = 0; // deleted by tgaSave() - - // FIXME: Get rid of tempfile and compress in-memory - packFile(dsq->getSaveDirectory() + "/poot-s.tmp", os.str(),9); - remove((dsq->getSaveDirectory() + "/poot-s.tmp").c_str()); } PlaySfx sfx; @@ -3302,6 +3396,10 @@ bool DSQ::confirm(const std::string &text, const std::string &image, bool ok, fl bgLabel->scale.interpolateTo(Vector(1,1), t); addRenderObject(bgLabel, LR_CONFIRM); + const int GUILEVEL_CONFIRM = 200; + + AquariaGuiElement::currentGuiInputLevel = GUILEVEL_CONFIRM; + dsq->main(t); float t2 = 0.05; @@ -3320,10 +3418,6 @@ bool DSQ::confirm(const std::string &text, const std::string &image, bool ok, fl addRenderObject(no, LR_CONFIRM); */ - const int GUILEVEL_CONFIRM = 200; - - AquariaGuiElement::currentGuiInputLevel = GUILEVEL_CONFIRM; - AquariaMenuItem *yes=0; AquariaMenuItem *no=0; @@ -3414,8 +3508,16 @@ bool DSQ::confirm(const std::string &text, const std::string &image, bool ok, fl bgLabel->safeKill(); txt->safeKill(); - if (yes) yes->safeKill(); - if (no) no->safeKill(); + if (yes) + { + yes->setFocus(false); + yes->safeKill(); + } + if (no) + { + no->setFocus(false); + no->safeKill(); + } bool ret = (confirmDone == 1); @@ -3770,7 +3872,7 @@ std::string DSQ::getDialogueFilename(const std::string &f) return "dialogue/" + languagePack + "/" + f + ".txt"; } -void DSQ::jumpToSection(std::ifstream &inFile, const std::string §ion) +void DSQ::jumpToSection(InStream &inFile, const std::string §ion) { if (section.empty()) return; std::string file = dsq->getDialogueFilename(dialogueFile); @@ -4553,6 +4655,8 @@ void DSQ::onUpdate(float dt) lockMouse(); + + Network::update(); } void DSQ::lockMouse() diff --git a/Aquaria/DSQ.h b/Aquaria/DSQ.h index 6f287da..a5305b5 100644 --- a/Aquaria/DSQ.h +++ b/Aquaria/DSQ.h @@ -229,17 +229,26 @@ protected: bool vis, hidden; }; +enum ModType +{ + MODTYPE_MOD, + MODTYPE_PATCH, +}; + struct ModEntry { + unsigned int id; // index in vector + ModType type; std::string path; }; +class ModSelectorScreen; + class Mod { public: Mod(); void clear(); - void loadModXML(TiXmlDocument *d, std::string modName); void setActive(bool v); void start(); void stop(); @@ -261,6 +270,10 @@ public: void shutdown(); bool isShuttingDown(); + + static bool loadModXML(TiXmlDocument *d, std::string modName); + static ModType getTypeFromXML(TiXmlElement *xml); + protected: bool shuttingDown; bool active; @@ -275,18 +288,6 @@ protected: std::string path; }; -class ModSelector : public AquariaGuiQuad -{ -public: - ModSelector(); - void refreshTexture(); -protected: - bool refreshing; - BitmapText *label; - void onUpdate(float dt); - bool mouseDown; -}; - class AquariaScreenTransition : public ScreenTransition { public: @@ -1394,7 +1395,7 @@ public: void takeScreenshot(); void takeScreenshotKey(); - void jumpToSection(std::ifstream &inFile, const std::string §ion); + void jumpToSection(InStream &inFile, const std::string §ion); PathFinding pathFinding; void runGesture(const std::string &line); @@ -1458,6 +1459,11 @@ public: void createModSelector(); void clearModSelector(); + bool mountModPackage(const std::string&); + bool modIsKnown(const std::string& name); + void unloadMods(); + static void loadModsCallback(const std::string &filename, intptr_t param); + static void loadModPackagesCallback(const std::string &filename, intptr_t param); bool doScreenTrans; @@ -1485,14 +1491,18 @@ public: Mod mod; void loadMods(); + void applyPatches(); + void refreshResourcesForPatch(const std::string& name); + void applyPatch(const std::string& name); + void unapplyPatch(const std::string& name); + bool isPatchActive(const std::string& name) { return activePatches.find(name) != activePatches.end(); } std::vector modEntries; + std::set activePatches; int selectedMod; - ModSelector *modSelector; + ModSelectorScreen *modSelectorScr; void startSelectedMod(); - void selectNextMod(); - void selectPrevMod(); ModEntry* getSelectedModEntry(); #ifdef BBGE_BUILD_ACHIEVEMENTS_INTERNAL diff --git a/Aquaria/Emote.cpp b/Aquaria/Emote.cpp index 5ef43b4..ccff447 100644 --- a/Aquaria/Emote.cpp +++ b/Aquaria/Emote.cpp @@ -31,7 +31,7 @@ Emote::Emote() void Emote::load(const std::string &file) { emotes.clear(); - std::ifstream in(file.c_str()); + InStream in(file.c_str()); std::string line; while (std::getline(in, line)) diff --git a/Aquaria/Game.cpp b/Aquaria/Game.cpp index b758879..396fc06 100644 --- a/Aquaria/Game.cpp +++ b/Aquaria/Game.cpp @@ -2510,7 +2510,7 @@ void Game::loadEntityTypeList() // and group list! { entityTypeList.clear(); - std::ifstream in("scripts/entities/entities.txt"); + InStream in("scripts/entities/entities.txt"); std::string line; if(!in) { @@ -2543,7 +2543,7 @@ void Game::loadEntityTypeList() fn = dsq->mod.getPath() + "entitygroups.txt"; } - std::ifstream in2(fn.c_str()); + InStream in2(fn.c_str()); int curGroup=0; while (std::getline(in2, line)) @@ -5395,7 +5395,7 @@ void Game::findMaxCameraValues() void Game::setWarpAreaSceneName(WarpArea &warpArea) { - std::ifstream in("data/warpAreas.txt"); + InStream in("data/warpAreas.txt"); std::string color, area1, dir1, area2, dir2; std::string line; while (std::getline(in, line)) @@ -7932,7 +7932,7 @@ void Game::onFlipTest() void appendFileToString(std::string &string, const std::string &file) { - std::ifstream inf(file.c_str()); + InStream inf(file.c_str()); if (inf.is_open()) { @@ -10843,7 +10843,7 @@ void Game::loadElementTemplates(std::string pack) tileCache.clean(); } - std::ifstream in(fn.c_str()); + InStream in(fn.c_str()); std::string line; while (std::getline(in, line)) { diff --git a/Aquaria/GameplayVariables.cpp b/Aquaria/GameplayVariables.cpp index a938b7f..f6888a3 100644 --- a/Aquaria/GameplayVariables.cpp +++ b/Aquaria/GameplayVariables.cpp @@ -24,7 +24,7 @@ GameplayVariables *vars = 0; void GameplayVariables::load() { - std::ifstream inFile("data/variables.txt"); + InStream inFile("data/variables.txt"); if(!inFile) { core->messageBox("error", "Variables data not found! Aborting..."); diff --git a/Aquaria/Main.cpp b/Aquaria/Main.cpp index d64d9dc..215c659 100644 --- a/Aquaria/Main.cpp +++ b/Aquaria/Main.cpp @@ -58,7 +58,7 @@ static void StartAQConfig() static void CheckConfig(void) { #ifdef BBGE_BUILD_WINDOWS - bool hasCfg = exists("usersettings.xml", false); + bool hasCfg = exists("usersettings.xml", false, true); if(!hasCfg) StartAQConfig(); #endif diff --git a/Aquaria/Mod.cpp b/Aquaria/Mod.cpp index 02271c7..c715bb3 100644 --- a/Aquaria/Mod.cpp +++ b/Aquaria/Mod.cpp @@ -77,9 +77,9 @@ bool Mod::isEditorBlocked() return blockEditor; } -void Mod::loadModXML(TiXmlDocument *d, std::string modName) +bool Mod::loadModXML(TiXmlDocument *d, std::string modName) { - d->LoadFile(baseModPath + modName + ".xml"); + return d->LoadFile(baseModPath + modName + ".xml"); } std::string Mod::getBaseModPath() @@ -289,3 +289,29 @@ void Mod::update(float dt) applyStart(); } } + +ModType Mod::getTypeFromXML(TiXmlElement *xml) // should be ... - element +{ + if(xml) + { + TiXmlElement *prop = xml->FirstChildElement("Properties"); + if(prop) + { + const char *type = prop->Attribute("type"); + if(type) + { + if(!strcmp(type, "mod")) + return MODTYPE_MOD; + else if(!strcmp(type, "patch")) + return MODTYPE_PATCH; + else + { + std::ostringstream os; + os << "Unknown mod type '" << type << "' in XML, default to MODTYPE_MOD"; + debugLog(os.str()); + } + } + } + } + return MODTYPE_MOD; // the default +} diff --git a/Aquaria/ModDownloader.cpp b/Aquaria/ModDownloader.cpp new file mode 100644 index 0000000..ec683e2 --- /dev/null +++ b/Aquaria/ModDownloader.cpp @@ -0,0 +1,552 @@ +#include "DSQ.h" +#include "minihttp.h" + +#ifdef BBGE_BUILD_VFS + +#include "ModDownloader.h" +#include "ModSelector.h" +#include "Network.h" +#include "tinyxml.h" + +#ifdef BBGE_BUILD_UNIX +#include +#endif + +using Network::NetEvent; +using Network::NE_ABORT; +using Network::NE_FINISH; +using Network::NE_UPDATE; + + +// external, global +ModDL moddl; + + +// TODO: move this to Base.cpp and replace other similar occurrances +static void createDir(const char *d) +{ +#if defined(BBGE_BUILD_UNIX) + mkdir(d, S_IRWXU); +#elif defined(BBGE_BUILD_WINDOWS) + CreateDirectoryA(d, NULL); +#endif +} + +// .../_mods/ +// .../_mods/.zip +static std::string _PathToModName(const std::string& path) +{ + size_t pos = path.find_last_of('/')+1; + size_t pos2 = path.find_last_of('.'); + return path.substr(pos, pos2-pos); +} + +// fuuugly +static bool _CompareByPackageURL(ModIconOnline *ico, const std::string& n) +{ + return ico->packageUrl == n; +} +static bool _CompareByIcon(ModIconOnline *ico, const std::string& n) +{ + return ico->iconfile == n; +} +// this function is required because it is never guaranteed that the original +// ModIconOnline which triggered the download still exists. +// This means the pointer to the icon can't be stored anywhere without risking crashing. +// Instead, use this way to find the correct icon, even if it was deleted and recreated in the meantime. +static ModIconOnline *_FindModIconOnline(const std::string& n, bool (*func)(ModIconOnline*,const std::string&)) +{ + ModSelectorScreen* scr = dsq->modSelectorScr; + IconGridPanel *grid = scr? scr->panels[2] : NULL; + if(!grid) + return NULL; + + for(RenderObject::Children::iterator it = grid->children.begin(); it != grid->children.end(); ++it) + { + ModIconOnline *ico = dynamic_cast(*it); + if(ico && func(ico, n)) + return ico; + } + return NULL; +} + +class ModlistRequest : public Network::RequestData +{ +public: + ModlistRequest(bool chain) : allowChaining(chain), first(false) {} + virtual ~ModlistRequest() {} + virtual void notify(NetEvent ev, size_t recvd, size_t total) + { + moddl.NotifyModlist(this, ev, recvd, total); + if(ev == NE_ABORT || ev == NE_FINISH) + delete this; + } + bool allowChaining; + bool first; +}; + +class IconRequest : public Network::RequestData +{ +public: + virtual ~IconRequest() {} + virtual void notify(NetEvent ev, size_t recvd, size_t total) + { + moddl.NotifyIcon(this, ev, recvd, total); + if(ev == NE_ABORT || ev == NE_FINISH) + delete this; + } +}; + +class ModRequest : public Network::RequestData +{ +public: + virtual ~ModRequest() {} + virtual void notify(NetEvent ev, size_t recvd, size_t total) + { + moddl.NotifyMod(this, ev, recvd, total); + if(ev == NE_ABORT || ev == NE_FINISH) + delete this; + } + std::string modname; +}; + +ModDL::ModDL() +{ +} + +ModDL::~ModDL() +{ +} + +void ModDL::init() +{ + tempDir = dsq->getUserDataFolder() + "/webcache"; + createDir(tempDir.c_str()); + + ttvfs::VFSDir *vd = vfs.GetDir(tempDir.c_str()); + if(vd) + vd->load(false); +} + +bool ModDL::hasUrlFileCached(const std::string& url) +{ + return exists(remoteToLocalName(url)); +} + +std::string ModDL::remoteToLocalName(const std::string& url) +{ + if(!url.length()) + return ""; + + std::string here; + here.reserve(url.length() + tempDir.length() + 2); + here += tempDir; + here += '/'; + for(size_t i = 0; i < url.length(); ++i) + { + if(!(isalnum(url[i]) || url[i] == '_' || url[i] == '-' || url[i] == '.')) + here += '_'; + else + here += url[i]; + } + return here; +} + +void ModDL::GetModlist(const std::string& url, bool allowChaining, bool first) +{ + if(first) + knownServers.clear(); + + // Prevent recursion, self-linling, or cycle linking. + // In theory, this allows setting up a server network + // where each server links to any servers it knows, + // without screwing up, but this isn't going to happen anyways. + // It's still useful for safety. -- FG + if(knownServers.size() > 30) + { + debugLog("GetModlist: Too many servers. Whaat?!"); + return; + } + else + { + std::string host, dummy_file; + int dummy_port; + minihttp::SplitURI(url, host, dummy_file, dummy_port); + stringToLower(host); + if(knownServers.find(host) != knownServers.end()) + { + debugLog("GetModlist: Already seen host: " + host + " - ignoring"); + return; + } + knownServers.insert(host); + } + + std::ostringstream os; + os << "Fetching mods list [" << url << "], chain: " << allowChaining; + debugLog(os.str()); + + std::string localName = remoteToLocalName(url); + + debugLog("... to: " + localName); + + ModlistRequest *rq = new ModlistRequest(allowChaining); + rq->tempFilename = localName; + rq->finalFilename = localName; + rq->allowChaining = allowChaining; + rq->url = url; + rq->first = first; + + Network::download(rq); + + ModSelectorScreen* scr = dsq->modSelectorScr; + if(scr) + { + scr->globeIcon->quad->color.interpolateTo(Vector(1,1,1), 0.3f); + scr->globeIcon->alpha.interpolateTo(0.5f, 0.2f, -1, true, true); + scr->dlText.setText("Retrieving online mod list..."); + scr->dlText.alpha.stopPath(); + scr->dlText.alpha.interpolateTo(1, 0.1f); + } +} + +void ModDL::NotifyModlist(ModlistRequest *rq, NetEvent ev, size_t recvd, size_t total) +{ + if(ev == NE_UPDATE) + return; + + ModSelectorScreen* scr = dsq->modSelectorScr; + + if(ev == NE_ABORT) + { + dsq->sound->playSfx("denied"); + if(scr) + { + scr->globeIcon->alpha.stop(); + scr->globeIcon->alpha.interpolateTo(1, 0.5f, 0, false, true); + scr->globeIcon->quad->color.interpolateTo(Vector(0.5f, 0.5f, 0.5f), 0.3f); + scr->dlText.setText("Unable to retrieve online mod list.\nCheck your connection and try again."); // TODO: put into stringbank + scr->dlText.alpha = 0; + scr->dlText.alpha.ensureData(); + scr->dlText.alpha.data->path.addPathNode(0, 0); + scr->dlText.alpha.data->path.addPathNode(1, 0.1); + scr->dlText.alpha.data->path.addPathNode(1, 0.7); + scr->dlText.alpha.data->path.addPathNode(0, 1); + scr->dlText.alpha.startPath(5); + + // Allow requesting another server list if the initial fetch failed. + // Do not care for child servers. + if(rq->first) + scr->gotServerList = false; + } + return; + } + + if(scr) + { + scr->globeIcon->alpha.stop(); + scr->globeIcon->alpha.interpolateTo(1, 0.2f); + scr->dlText.alpha.stopPath(); + scr->dlText.alpha.interpolateTo(0, 0.3f); + if(rq->first) + dsq->clickRingEffect(scr->globeIcon->getWorldPosition(), 1); + } + + if(!ParseModXML(rq->finalFilename, rq->allowChaining)) + { + if(scr) + { + scr->dlText.alpha.stopPath(); + scr->dlText.alpha.interpolateTo(1, 0.5f); + scr->dlText.setText("Server error!\nBad XML, please contact server admin.\nURL: " + rq->url); // TODO: -> stringbank + } + } +} + +bool ModDL::ParseModXML(const std::string& fn, bool allowChaining) +{ + TiXmlDocument xml; + if(!xml.LoadFile(fn)) + { + debugLog("Failed to parse downloaded XML: " + fn); + return false; + } + + ModSelectorScreen* scr = dsq->modSelectorScr; + IconGridPanel *grid = scr? scr->panels[2] : NULL; + + // XML Format: + /* + + //-- Server network - link to other servers + ... + + + + // -- size is optional, used to detect file change on server + // -- saveAs is optional, and ".aqmod" appended to it + //-- optional tag + //-- optional tag, pops up confirm dialog + //-- optional tag, if not given, "mod" is assumed. + + + + ... + + + */ + + TiXmlElement *modlist = xml.FirstChildElement("ModList"); + if(!modlist) + { + debugLog("ModList root tag not found"); + return false; + } + + if(allowChaining) + { + TiXmlElement *servx = modlist->FirstChildElement("Server"); + while(servx) + { + int chain = 0; + servx->Attribute("chain", &chain); + if(const char *url = servx->Attribute("url")) + GetModlist(url, chain, false); + + servx = servx->NextSiblingElement("Server"); + } + } + + TiXmlElement *modx = modlist->FirstChildElement("AquariaMod"); + while(modx) + { + std::string namestr, descstr, iconurl, pkgurl, confirmStr, localname; + std::string sizestr; + bool isPatch = false; + int serverSize = 0; + int serverIconSize = 0; + TiXmlElement *fullname, *desc, *icon, *pkg, *confirm, *props; + fullname = modx->FirstChildElement("Fullname"); + desc = modx->FirstChildElement("Description"); + icon = modx->FirstChildElement("Icon"); + pkg = modx->FirstChildElement("Package"); + confirm = modx->FirstChildElement("Confirm"); + props = modx->FirstChildElement("Properties"); + + if(fullname && fullname->Attribute("text")) + namestr = fullname->Attribute("text"); + + if(desc && desc->Attribute("text")) + descstr = desc->Attribute("text"); + + if(icon) + { + if(icon->Attribute("url")) + iconurl = icon->Attribute("url"); + if(icon->Attribute("size")) + icon->Attribute("size", &serverIconSize); + } + + if(props && props->Attribute("type")) + isPatch = !strcmp(props->Attribute("type"), "patch"); + + if(pkg) + { + if(pkg->Attribute("url")) + { + pkgurl = pkg->Attribute("url"); + localname = _PathToModName(pkgurl); + } + if(pkg->Attribute("saveAs")) + localname = _PathToModName(pkg->Attribute("saveAs")); + + if(pkg->Attribute("size")) + pkg->Attribute("size", &serverSize); + } + + if(confirm && confirm->Attribute("text")) + confirmStr = confirm->Attribute("text"); + + modx = modx->NextSiblingElement("AquariaMod"); + + // ------------------- + + if (descstr.size() > 255) + descstr.resize(255); + + std::string localIcon = remoteToLocalName(iconurl); + + size_t localIconSize = 0; + if(ttvfs::VFSFile *vf = vfs.GetFile(localIcon.c_str())) + { + localIconSize = vf->size(); + } + + debugLog("NetMods: " + namestr); + + ModIconOnline *ico = NULL; + if(grid) + { + ico = new ModIconOnline; + ico->iconfile = localIcon; + ico->packageUrl = pkgurl; + ico->namestr = namestr; + ico->desc = descstr; + ico->confirmStr = confirmStr; + ico->localname = localname; + ico->label = "--[ " + namestr + " ]--\n" + descstr; + ico->isPatch = isPatch; + + if(serverSize && dsq->modIsKnown(localname)) + { + std::string modpkg = dsq->mod.getBaseModPath() + localname; + modpkg += ".aqmod"; + ttvfs::VFSFile *vf = vfs.GetFile(modpkg.c_str()); + if(vf) + { + size_t sz = vf->size(); + ico->hasUpdate = (serverSize && ((size_t)serverSize != sz)); + } + // if vf==NULL, then the mod was not installed with the mod downloader. + // There is a warning on download that's supposed to prevent this. + // However, if we end up with vf==NULL, there's not much to do about it. + } + + // try to set texture, if its not there, download it. + // download a new icon if file size changed. + if(!ico->fixIcon() || !localIconSize || (serverIconSize && (size_t)serverIconSize != localIconSize)) + { + ico->setDownloadProgress(0, 10); + GetIcon(iconurl, localIcon); + // we do not pass the ico ptr to the call above; otherwise it will crash if the mod menu is closed + // while a download is in progress + } + + grid->add(ico); + } + } + + return true; +} + +void ModDL::GetMod(const std::string& url, const std::string& localname) +{ + ModRequest *rq = new ModRequest; + + if(localname.empty()) + rq->modname = _PathToModName(url); + else + rq->modname = localname; + + rq->tempFilename = remoteToLocalName(url); + rq->finalFilename = rq->tempFilename; // we will fix this later on + rq->url = url; + + debugLog("ModDL::GetMod: " + rq->finalFilename); + + Network::download(rq); +} + +void ModDL::GetIcon(const std::string& url, const std::string& localname) +{ + if(url.empty()) + return; + IconRequest *rq = new IconRequest; + rq->url = url; + rq->finalFilename = localname; + rq->tempFilename = localname; + debugLog("ModDL::GetIcon: " + localname); + Network::download(rq); +} + +void ModDL::NotifyIcon(IconRequest *rq, NetEvent ev, size_t recvd, size_t total) +{ + ModIconOnline *ico = _FindModIconOnline(rq->finalFilename, _CompareByIcon); + if(ico) + { + float perc = -1; // no progress bar + if(ev == NE_FINISH) + ico->fixIcon(); + else if(ev == NE_UPDATE) + perc = total ? ((float)recvd / (float)total) : 0.0f; + + ico->setDownloadProgress(perc, 10); // must be done after setting the new texture for proper visuals + } +} + +void ModDL::NotifyMod(ModRequest *rq, NetEvent ev, size_t recvd, size_t total) +{ + if(ev == NE_ABORT) + dsq->sound->playSfx("denied"); + else if(ev == NE_FINISH) + dsq->sound->playSfx("gem-collect"); + + ModIconOnline *ico = _FindModIconOnline(rq->url, _CompareByPackageURL); + if(!ico) + { + if(ev == NE_FINISH) + dsq->centerMessage("Finished downloading mod " + rq->modname, 420); // TODO: -> stringbank + return; + } + + float perc = -1; + if(ev == NE_UPDATE) + perc = total ? ((float)recvd / (float)total) : 0.0f; + + ico->setDownloadProgress(perc); + ico->clickable = (ev == NE_ABORT || ev == NE_FINISH); + + if(ev == NE_FINISH) + { + const std::string& localname = ico->localname; + std::string moddir = dsq->mod.getBaseModPath() + localname; + // the mod file can already exist, and if it does, it will most likely be mounted. + // zip archives are locked and cannot be deleted/replaced, so we need to unload it first. + // this effectively closes the file handle only, nothing else. + ttvfs::VFSDir *vd = vfs.GetDir(moddir.c_str()); + if(vd) + { + ttvfs::VFSBase *origin = vd->getOrigin(); + if(origin) + origin->close(); + } + + std::string archiveFile = moddir + ".aqmod"; + + // At least on win32 rename() fails when the destination file already exists + remove(archiveFile.c_str()); + if(rename(rq->tempFilename.c_str(), archiveFile.c_str())) + { + debugLog("Could not rename mod " + rq->tempFilename + " to " + archiveFile); + return; + } + else + debugLog("ModDownloader: Renamed mod " + rq->tempFilename + " to " + archiveFile); + + if(vd) + { + // Dir already exists, just remount everything + vfs.Reload(); + } + else if(!dsq->mountModPackage(archiveFile)) + { + // make package readable (so that the icon can be shown) + // But only if it wasn't mounted before! + dsq->screenMessage("Failed to mount archive: " + archiveFile); + return; + } + + // if it is already known, the file was re-downloaded + if(!dsq->modIsKnown(localname)) + { + // yay, got something new! + DSQ::loadModsCallback(archiveFile, 0); // does not end in ".xml" but thats no problem here + if(dsq->modSelectorScr) + dsq->modSelectorScr->initModAndPatchPanel(); // HACK + } + + ico->hasUpdate = false; + ico->fixIcon(); + } +} + +#endif // BBGE_BUILD_VFS diff --git a/Aquaria/ModDownloader.h b/Aquaria/ModDownloader.h new file mode 100644 index 0000000..0988c34 --- /dev/null +++ b/Aquaria/ModDownloader.h @@ -0,0 +1,45 @@ +#ifndef MODDOWNLOADER_H +#define MODDOWNLOADER_H +#ifdef BBGE_BUILD_VFS + +#include +#include +#include "Network.h" + +#define DEFAULT_MASTER_SERVER "fg.wzff.de/aqmods/" + +class ModlistRequest; +class ModRequest; +class IconRequest; + +class ModDL +{ +public: + ModDL(); + ~ModDL(); + void init(); + + void GetModlist(const std::string& url, bool allowChaining, bool first); + void NotifyModlist(ModlistRequest *rq, Network::NetEvent ev, size_t recvd, size_t total); + bool ParseModXML(const std::string& fn, bool allowChaining); + + void GetMod(const std::string& url, const std::string& localname); + void NotifyMod(ModRequest *rq, Network::NetEvent ev, size_t recvd, size_t total); + + void GetIcon(const std::string& url, const std::string& localname); + void NotifyIcon(IconRequest *rq, Network::NetEvent ev, size_t recvd, size_t total); + + + std::string remoteToLocalName(const std::string& url); + bool hasUrlFileCached(const std::string& url); + + + std::set knownServers; + std::string tempDir; +}; + +extern ModDL moddl; + + +#endif +#endif diff --git a/Aquaria/ModSelector.cpp b/Aquaria/ModSelector.cpp index 9d404c8..8bbe23b 100644 --- a/Aquaria/ModSelector.cpp +++ b/Aquaria/ModSelector.cpp @@ -21,111 +21,916 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "../BBGE/DebugFont.h" #include "DSQ.h" +#include "AquariaProgressBar.h" +#include "tinyxml.h" +#include "ModSelector.h" + +#ifdef BBGE_BUILD_VFS +#include "ModDownloader.h" +#endif + +#define MOD_ICON_SIZE 150 +#define MINI_ICON_SIZE 32 -ModSelector::ModSelector() : AquariaGuiQuad(), label(0) +static bool _modname_cmp(const ModIcon *a, const ModIcon *b) { - label = new BitmapText(&dsq->smallFont); - //label->position = Vector(-200, 160); - label->position = Vector(0, 160); - label->setWidth(400); - addChild(label, PM_POINTER); - - refreshTexture(); - - mouseDown = false; - refreshing = false; - - - shareAlphaWithChildren = 1; + return a->fname < b->fname; } -void ModSelector::refreshTexture() +ModSelectorScreen::ModSelectorScreen() : Quad(), ActionMapper(), +currentPanel(-1), gotServerList(false), dlText(&dsq->smallFont), subtext(&dsq->subsFont) { - float t = 0.2; - bool doit=false; - refreshing = true; - if (texture) + followCamera = 1; + shareAlphaWithChildren = false; + alpha = 1; + alphaMod = 0.1f; + color = 0; + globeIcon = NULL; + modsIcon = NULL; + subFadeT = -1; +} + +void ModSelectorScreen::moveUp() +{ + move(5); +} + +void ModSelectorScreen::moveDown() +{ + move(-5); +} + +void ModSelectorScreen::move(int ud, bool instant /* = false */) +{ + IconGridPanel *grid = panels[currentPanel]; + InterpolatedVector& v = grid->position; + const float ch = ud * 42; + const float t = instant ? 0.0f : 0.2f; + if(!instant && v.isInterpolating()) { - alpha.interpolateTo(0, t); - scale.interpolateTo(Vector(0.5, 0.5), t); - dsq->main(t); - doit = true; - } - ModEntry *e = dsq->getSelectedModEntry(); - if (e) - { - std::string texToLoad = e->path + "/" + "mod-icon"; - texToLoad = dsq->mod.getBaseModPath() + texToLoad; - setTexture(texToLoad); - width = 256; - height = 256; + v.data->from = v; + v.data->target.y += ch; + v.data->timePassed = 0; + + if(v.data->target.y > 150) + v.data->target.y = 150; + else if(v.data->target.y < -grid->getUsedY() / 2) + v.data->target.y = -grid->getUsedY() / 2; } else { - return; + Vector v2 = grid->position; + v2.y += ch; // scroll down == grid pos y gets negative (grid scrolls up) + + if(v2.y > 150) + grid->position.interpolateTo(Vector(v2.x, 150), t); + else if(v2.y < -grid->getUsedY() / 2) + grid->position.interpolateTo(Vector(v2.x, -grid->getUsedY() / 2), t); + else + grid->position.interpolateTo(v2, t, 0, false, true); } - - TiXmlDocument d; - - dsq->mod.loadModXML(&d, e->path); - - if (label) - { - label->setText("No Description"); - TiXmlElement *top = d.FirstChildElement("AquariaMod"); - if (top) - { - TiXmlElement *desc = top->FirstChildElement("Description"); - if (desc) - { - if (desc->Attribute("text")) - { - std::string txt = desc->Attribute("text"); - if (txt.size() > 255) - txt.resize(255); - label->setText(txt); - } - } - } - } - if (doit) - { - alpha.interpolateTo(1, t); - scale.interpolateTo(Vector(1, 1), t); - dsq->main(t); - } - refreshing = false; } -void ModSelector::onUpdate(float dt) +void ModSelectorScreen::onUpdate(float dt) { - AquariaGuiQuad::onUpdate(dt); + Quad::onUpdate(dt); - if (!refreshing) + // mouse wheel scroll + if(dsq->mouse.scrollWheelChange) { - if (isCoordinateInside(core->mouse.position)) + move(dsq->mouse.scrollWheelChange); + } + + if(subFadeT >= 0) + { + subFadeT = subFadeT - dt; + if(subFadeT <= 0) { - scale.interpolateTo(Vector(1.1, 1.1), 0.1); - const bool anyButton = core->mouse.buttons.left || core->mouse.buttons.right; - if (anyButton && !mouseDown) + subbox.alpha.interpolateTo(0, 1.0f); + subtext.alpha.interpolateTo(0, 1.2f); + } + } + + if(!AquariaGuiElement::currentFocus && dsq->inputMode == INPUT_JOYSTICK) + { + AquariaGuiElement *closest = AquariaGuiElement::getClosestGuiElement(core->mouse.position); + if(closest) + { + debugLog("Lost focus, setting nearest gui element"); + closest->setFocus(true); + } + } +} + +void ModSelectorScreen::showPanel(int id) +{ + if(id == currentPanel) + return; + + const float t = 0.2f; + IconGridPanel *newgrid = panels[id]; + + // fade in selected panel + if(currentPanel < 0) // just bringing up? + { + newgrid->scale = Vector(0.8f,0.8f); + newgrid->alpha = 0; + } + + currentPanel = id; + + updateFade(); +} + +void ModSelectorScreen::updateFade() +{ + // fade out background panels + // necessary to do all of them, that icon alphas are 0... they would trigger otherwise, even if invisible because parent panel is not shown + for(int i = 0; i < panels.size(); ++i) + panels[i]->fade(i == currentPanel, true); +} + +static void _MenuIconClickCallback(int id, void *user) +{ + ModSelectorScreen *ms = (ModSelectorScreen*)user; + switch(id) // see MenuIconBar::init() + { + case 2: // network + ms->initNetPanel(); + break; + + case 3: // exit + dsq->quitNestedMain(); + return; + } + + ms->showPanel(id); +} + +// can be called multiple times without causing trouble +void ModSelectorScreen::init() +{ + leftbar.width = 100; + leftbar.height = height; + leftbar.alpha = 0; + leftbar.alpha.interpolateTo(1, 0.2f); + leftbar.position = Vector((leftbar.width - width) / 2, 0); + leftbar.followCamera = 1; + if(!leftbar.getParent()) + { + leftbar.init(); + addChild(&leftbar, PM_STATIC); + + panels.resize(leftbar.icons.size()); + std::fill(panels.begin(), panels.end(), (IconGridPanel*)NULL); + } + + rightbar.width = 100; + rightbar.height = height; + rightbar.alpha = 0; + rightbar.alpha.interpolateTo(1, 0.2f); + rightbar.position = Vector(((width - rightbar.width) / 2), 0); + rightbar.followCamera = 1; + if(!rightbar.getParent()) + { + rightbar.init(); + addChild(&rightbar, PM_STATIC); + } + + for(int i = 0; i < panels.size(); ++i) + { + if(panels[i]) + continue; + panels[i] = new IconGridPanel(); + panels[i]->followCamera = 1; + panels[i]->width = width - leftbar.width - rightbar.width; + panels[i]->height = 750; + panels[i]->position = Vector(0, 0); + panels[i]->alpha = 0; + panels[i]->spacing = 20; // for the grid + panels[i]->scale = Vector(0.8f, 0.8f); + leftbar.icons[i]->cb = _MenuIconClickCallback; + leftbar.icons[i]->cb_data = this; + addChild(panels[i], PM_POINTER); + } + + arrowUp.useQuad("Gui/arrow-left"); + arrowUp.useSound("click"); + arrowUp.useGlow("particles/glow", 128, 64); + arrowUp.position = Vector(0, -230); + arrowUp.followCamera = 1; + arrowUp.rotation.z = 90; + arrowUp.event.set(MakeFunctionEvent(ModSelectorScreen, moveUp)); + arrowUp.guiInputLevel = 100; + arrowUp.alpha = 0; + arrowUp.alpha.interpolateTo(1, 0.2f); + arrowUp.setDirMove(DIR_DOWN, &arrowDown); + rightbar.addChild(&arrowUp, PM_STATIC); + + arrowDown.useQuad("Gui/arrow-right"); + arrowDown.useSound("click"); + arrowDown.useGlow("particles/glow", 128, 64); + arrowDown.position = Vector(0, 170); + arrowDown.followCamera = 1; + arrowDown.rotation.z = 90; + arrowDown.event.set(MakeFunctionEvent(ModSelectorScreen, moveDown)); + arrowDown.guiInputLevel = 100; + arrowDown.alpha = 0; + arrowDown.alpha.interpolateTo(1, 0.2f); + arrowDown.setDirMove(DIR_UP, &arrowUp); + rightbar.addChild(&arrowDown, PM_STATIC); + + dlText.alpha = 0; + dlText.position = Vector(0, 0); + dlText.setFontSize(15); + dlText.scale = Vector(1.5f, 1.5f); + dlText.followCamera = 1; + addChild(&dlText, PM_STATIC); + + initModAndPatchPanel(); + // net panel inited on demand + + showPanel(0); + + subbox.position = Vector(0,260); + subbox.alpha = 0; + subbox.alphaMod = 0.7; + subbox.followCamera = 1; + subbox.autoWidth = AUTO_VIRTUALWIDTH; + subbox.setHeight(80); + subbox.color = Vector(0, 0, 0); + addChild(&subbox, PM_STATIC); + + subtext.position = Vector(0,230); + subtext.followCamera = 1; + subtext.alpha = 0; + subtext.setFontSize(12); + subtext.setWidth(800); + subtext.setAlign(ALIGN_CENTER); + addChild(&subtext, PM_STATIC); + + dsq->toggleVersionLabel(false); + + modsIcon->setFocus(true); + + // TODO: keyboard/gamepad control +} + +void ModSelectorScreen::initModAndPatchPanel() +{ + IconGridPanel *modgrid = panels[0]; + IconGridPanel *patchgrid = panels[1]; + ModIcon *ico; + std::vector tv; // for sorting + tv.resize(dsq->modEntries.size()); + for(unsigned int i = 0; i < tv.size(); ++i) + { + ico = NULL; + for(RenderObject::Children::iterator it = modgrid->children.begin(); it != modgrid->children.end(); ++it) + if(ModIcon* other = dynamic_cast(*it)) + if(other->modId == i) + { + ico = other; + break; + } + + if(!ico) + { + for(RenderObject::Children::iterator it = patchgrid->children.begin(); it != patchgrid->children.end(); ++it) + if(ModIcon* other = dynamic_cast(*it)) + if(other->modId == i) + { + ico = other; + break; + } + + if(!ico) // ok, its really not there. { - mouseDown = true; - } - else if (!anyButton && mouseDown) - { - core->quitNestedMain(); - dsq->modIsSelected = true; - dsq->sound->playSfx("click"); - dsq->sound->playSfx("pet-on"); - mouseDown = false; + ico = new ModIcon; + ico->followCamera = 1; + std::ostringstream os; + os << "Created ModIcon " << i; + debugLog(os.str()); } } - else + + tv[i] = ico; + ico->loadEntry(dsq->modEntries[i]); + } + std::sort(tv.begin(), tv.end(), _modname_cmp); + + for(int i = 0; i < tv.size(); ++i) + { + if(!tv[i]->getParent()) // ensure it was not added earlier { - scale.interpolateTo(Vector(1, 1), 0.1); + if(tv[i]->modType == MODTYPE_PATCH) + patchgrid->add(tv[i]); + else + modgrid->add(tv[i]); + } + } + updateFade(); +} + +void ModSelectorScreen::initNetPanel() +{ +#ifdef BBGE_BUILD_VFS + if(!gotServerList) + { + // FIXME: demo should be able to see downloadable mods imho +#ifndef AQUARIA_DEMO + moddl.init(); + std::string serv = dsq->user.network.masterServer; + if(serv.empty()) + serv = DEFAULT_MASTER_SERVER; + moddl.GetModlist(serv, true, true); +#endif + gotServerList = true; // try this only once (is automatically reset on failure) + } +#endif +} + +void ModSelectorScreen::setSubText(const std::string& s) +{ + subtext.setText(s); + subtext.alpha.interpolateTo(1, 0.2f); + subbox.alpha.interpolateTo(1, 0.2f); + subFadeT = 1; +} + +static void _FadeOutAll(RenderObject *r, float t) +{ + //r->shareAlphaWithChildren = true; + r->alpha.interpolateTo(0, t); + for(RenderObject::Children::iterator it = r->children.begin(); it != r->children.end(); ++it) + _FadeOutAll(*it, t); +} + +void ModSelectorScreen::close() +{ + /*for(int i = 0; i < panels.size(); ++i) + if(i != currentPanel) + panels[i]->setHidden(true);*/ + + const float t = 0.5f; + _FadeOutAll(this, t); + //panels[currentPanel]->scale.interpolateTo(Vector(0.9f, 0.9f), t); // HMM + dsq->user.save(); + dsq->toggleVersionLabel(true); + + // kinda hackish + /*dlText.setHidden(true); + arrowDown.glow->setHidden(true); + arrowUp.glow->setHidden(true); + subbox.setHidden(true); + subtext.setHidden(true);*/ +} + +JuicyProgressBar::JuicyProgressBar() : Quad(), txt(&dsq->smallFont) +{ + setTexture("modselect/tube"); + //shareAlphaWithChildren = true; + followCamera = 1; + alpha = 1; + + juice.setTexture("loading/juice"); + juice.alpha = 0.8; + juice.followCamera = 1; + addChild(&juice, PM_STATIC); + + txt.alpha = 0.7; + txt.followCamera = 1; + addChild(&txt, PM_STATIC); + + progress(0); +} + +void JuicyProgressBar::progress(float p) +{ + juice.width = p * width; + juice.height = height - 4; + perc = p; +} + +BasicIcon::BasicIcon() +: mouseDown(false), scaleNormal(1,1), scaleBig(scaleNormal * 1.1f) +{ + // HACK: Because AquariaMenuItem assigns onClick() in it's ctor, + // but we handle this ourselves. + clearCreatedEvents(); + clearActions(); + shareAlpha = true; + guiInputLevel = 100; +} + +bool BasicIcon::isGuiVisible() +{ + return !isHidden() && alpha.x > 0.1f && alphaMod > 0.1f && (!parent || parent->alpha.x == 1); +} + +bool BasicIcon::isCursorInMenuItem() +{ + if(quad) + return quad->isCoordinateInside(core->mouse.position); + return AquariaMenuItem::isCursorInMenuItem(); +} + +void BasicIcon::onUpdate(float dt) +{ + AquariaMenuItem::onUpdate(dt); + + // Autoscroll if selecting icon outside of screen + if(hasFocus && dsq->modSelectorScr) + { + Vector pos = getRealPosition(); + if(pos.y < 20 || pos.y > 580) + { + if(pos.y < 300) + dsq->modSelectorScr->move(5, true); + else + dsq->modSelectorScr->move(-5, true); + core->main(FRAME_TIME); // HACK: this is necessary to correctly position the mouse on the object after mofing the panel + setFocus(true); // re-position mouse + } + } + + if(!quad) + return; + + if (hasInput() && quad->isCoordinateInside(core->mouse.position)) + { + scale.interpolateTo(scaleBig, 0.1f); + const bool anyButton = core->mouse.buttons.left || core->mouse.buttons.right; + if (anyButton && !mouseDown) + { + mouseDown = true; + } + else if (!anyButton && mouseDown) + { + if(isGuiVisible()) // do not trigger if invis + onClick(); mouseDown = false; } } + else + { + scale.interpolateTo(scaleNormal, 0.1f); + mouseDown = false; + } +} + +void SubtitleIcon::onUpdate(float dt) +{ + BasicIcon::onUpdate(dt); + + if (dsq->modSelectorScr && isGuiVisible() && quad && quad->isCoordinateInside(core->mouse.position)) + dsq->modSelectorScr->setSubText(label); +} + +void BasicIcon::onClick() +{ + dsq->sound->playSfx("denied"); +} + +MenuIcon::MenuIcon(int id) : SubtitleIcon(), iconId(id), cb(0), cb_data(0) +{ +} + +void MenuIcon::onClick() +{ + dsq->sound->playSfx("click"); + if(cb) + cb(iconId, cb_data); +} + + +ModIcon::ModIcon(): SubtitleIcon(), modId(-1) +{ +} + +void ModIcon::onClick() +{ + dsq->sound->playSfx("click"); + +#ifdef AQUARIA_DEMO + dsq->nag(NAG_TOTITLE); + return; +#endif + + switch(modType) + { + case MODTYPE_MOD: + { + dsq->sound->playSfx("pet-on"); + core->quitNestedMain(); + dsq->modIsSelected = true; + dsq->selectedMod = modId; + break; + } + + case MODTYPE_PATCH: + { + std::set::iterator it = dsq->activePatches.find(fname); + if(it != dsq->activePatches.end()) + { + dsq->sound->playSfx("pet-off"); + dsq->unapplyPatch(fname); + //dsq->screenMessage(modname + " - deactivated"); // DEBUG + } + else + { + dsq->sound->playSfx("pet-on"); + dsq->applyPatch(fname); + //dsq->screenMessage(modname + " - activated"); // DEBUG + } + updateStatus(); + break; + } + + default: + errorLog("void ModIcon::onClick() -- unknown modType"); + } +} + +void ModIcon::loadEntry(const ModEntry& entry) +{ + modId = entry.id; + modType = entry.type; + fname = entry.path; + + std::string texToLoad = entry.path + "/" + "mod-icon"; + + texToLoad = dsq->mod.getBaseModPath() + texToLoad; + + if(!quad) + useQuad(texToLoad); + quad->setWidthHeight(MOD_ICON_SIZE, MOD_ICON_SIZE); + + TiXmlDocument d; + + dsq->mod.loadModXML(&d, entry.path); + + std::string ds = "No Description"; + + TiXmlElement *top = d.FirstChildElement("AquariaMod"); + if (top) + { + TiXmlElement *desc = top->FirstChildElement("Description"); + if (desc) + { + if (desc->Attribute("text")) + { + ds = desc->Attribute("text"); + //if (label.size() > 255) + // label.resize(255); + } + } + TiXmlElement *fullname = top->FirstChildElement("Fullname"); + if (fullname) + { + if (fullname->Attribute("text")) + { + modname = fullname->Attribute("text"); + if (modname.size() > 60) + modname.resize(60); + } + } + } + + label = "--[ " + modname + " ]--\n" + ds; + + updateStatus(); +} + +void ModIcon::updateStatus() +{ + if(modType == MODTYPE_PATCH) + { + if(dsq->isPatchActive(fname)) + { + // enabled + quad->color.interpolateTo(Vector(1,1,1), 0.1f); + alpha.interpolateTo(1, 0.2f); + scaleNormal = Vector(1,1); + } + else + { + // disabled + quad->color.interpolateTo(Vector(0.5f, 0.5f, 0.5f), 0.1f); + alpha.interpolateTo(0.6f, 0.2f); + scaleNormal = Vector(0.8f,0.8f); + } + scaleBig = scaleNormal * 1.1f; + } +} + + +#ifdef BBGE_BUILD_VFS + + +ModIconOnline::ModIconOnline() +: SubtitleIcon(), pb(0), extraIcon(0), statusIcon(0), clickable(true), isPatch(false), hasUpdate(false) +{ + label = desc; +} + +bool ModIconOnline::hasPkgOnDisk() +{ + if(localname.empty()) + return false; + std::string modfile = dsq->mod.getBaseModPath() + localname + ".aqmod"; + return exists(modfile.c_str(), false, true); +} + +// return true if the desired texture could be set +bool ModIconOnline::fixIcon() +{ + bool result = false; + if(exists(iconfile, false, true)) + { + if(quad) + { + quad->fadeAlphaWithLife = true; + quad->setLife(1); + quad->setDecayRate(2); + quad = 0; + } + useQuad(iconfile); + result = Texture::textureError == TEXERR_OK; + } + if(!quad) + { + //useQuad("bitblot/logo"); + int i = (rand() % 7) + 1; + std::stringstream ss; + ss << "fish-000" << i; + useQuad(ss.str()); + } + + quad->alpha = 0.001; + quad->setWidthHeight(MOD_ICON_SIZE, MOD_ICON_SIZE); + quad->alpha.interpolateTo(1, 0.5f); + + if(!extraIcon && isPatch) + { + Vector pos(-MOD_ICON_SIZE/2 + MINI_ICON_SIZE/2, MOD_ICON_SIZE/2 - MINI_ICON_SIZE/2); + extraIcon = new Quad("modselect/ico_patch", pos); + extraIcon->setWidthHeight(MINI_ICON_SIZE, MINI_ICON_SIZE); + quad->addChild(extraIcon, PM_POINTER); + } + + if(statusIcon) + { + statusIcon->fadeAlphaWithLife = true; + statusIcon->setLife(1); + statusIcon->setDecayRate(2); + statusIcon = 0; + } + + if(!statusIcon) + { + Vector pos(MOD_ICON_SIZE/2 - MINI_ICON_SIZE/2, MOD_ICON_SIZE/2 - MINI_ICON_SIZE/2); + if(dsq->modIsKnown(localname)) + { + // installed manually? + if(!hasPkgOnDisk()) + { + statusIcon = new Quad("modselect/ico_locked", pos); + } + else if(hasUpdate) + { + statusIcon = new Quad("modselect/ico_update", pos); + statusIcon->alpha.interpolateTo(0.5f, 0.5f, -1, true, true); + } + else + statusIcon = new Quad("modselect/ico_check", pos); + } + + if(statusIcon) + { + statusIcon->setWidthHeight(MINI_ICON_SIZE, MINI_ICON_SIZE); + quad->addChild(statusIcon, PM_POINTER); + } + } + + return result; +} + +void ModIconOnline::onClick() +{ + dsq->sound->playSfx("click"); + +#ifdef AQUARIA_DEMO + dsq->nag(NAG_TOTITLE); + return; +#endif + + bool success = false; + + if(clickable && !packageUrl.empty()) + { + bool proceed = true; + if(dsq->modIsKnown(localname)) + { + mouseDown = false; // HACK: do this here else stack overflow! + if(hasPkgOnDisk()) + { + if(hasUpdate) + proceed = dsq->confirm("Download update?"); // TODO: -> stringbank + else + proceed = dsq->confirm("Mod already exists. Re-download?"); + } + else + { + dsq->confirm("This mod was installed manually,\nnot messing with it.", "", true); + proceed = false; + } + + } + + if(proceed && confirmStr.length()) + { + mouseDown = false; // HACK: do this here else stack overflow! + dsq->sound->playSfx("spirit-beacon"); + proceed = dsq->confirm(confirmStr); + } + + if(proceed) + { + moddl.GetMod(packageUrl, localname); + setDownloadProgress(0); + success = true; + clickable = false; + } + else + success = true; // we didn't want, anyway + } + + if(!success) + { + SubtitleIcon::onClick(); // denied + } +} + +void ModIconOnline::setDownloadProgress(float p, float barheight /* = 20 */) +{ + if(p >= 0 && p <= 1) + { + if(!pb) + { + pb = new JuicyProgressBar; + addChild(pb, PM_POINTER); + pb->width = quad->width; + pb->height = 0; + pb->alpha = 0; + } + + if(barheight != pb->height) + { + pb->height = barheight; + pb->width = quad->width; + pb->position = Vector(0, (quad->height - pb->height + 1) / 2); // +1 skips a pixel row and looks better + } + + pb->alpha.interpolateTo(1, 0.2f); + pb->progress(p); + } + else if(pb) + { + pb->fadeAlphaWithLife = true; + pb->setLife(1); + pb->setDecayRate(2); + pb = 0; + } +} + +#endif // BBGE_BUILD_VFS + +MenuBasicBar::MenuBasicBar() +{ + setTexture("modselect/bar"); + repeatTextureToFill(true); + shareAlphaWithChildren = false; +} + +void MenuBasicBar::init() +{ +} + +void MenuIconBar::init() +{ + MenuIcon *ico; + int y = (-height / 2) - 35; + + + ico = new MenuIcon(0); + ico->label = "\nBrowse installed mods"; // TODO: -> stringbank + ico->useQuad("modselect/hdd"); + y += ico->quad->height; + ico->position = Vector(0, y); + add(ico); + dsq->modSelectorScr->modsIcon = ico; // HACK + + MenuIcon *prev = ico; + ico = new MenuIcon(1); + ico->label = "\nBrowse & enable/disable installed patches"; + ico->useQuad("modselect/patch"); + y += ico->quad->height; + ico->position = Vector(0, y); + ico->setDirMove(DIR_UP, prev); + prev->setDirMove(DIR_DOWN, ico); + add(ico); + + prev = ico; + ico = new MenuIcon(2); + ico->label = "\nBrowse mods online"; + ico->useQuad("modselect/globe"); + y += ico->quad->height; + ico->position = Vector(0, y); + ico->setDirMove(DIR_UP, prev); + prev->setDirMove(DIR_DOWN, ico); + add(ico); + dsq->modSelectorScr->globeIcon = ico; // HACK + + prev = ico; + ico = new MenuIcon(3); + ico->label = "\nReturn to title"; + ico->useQuad("gui/wok-drop"); + ico->repeatTextureToFill(false); + y += ico->quad->height; + ico->position = Vector(0, y); + ico->setDirMove(DIR_UP, prev); + prev->setDirMove(DIR_DOWN, ico); + add(ico); +} + +void MenuIconBar::add(MenuIcon *ico) +{ + ico->quad->setWidthHeight(width, width); + ico->followCamera = 1; + icons.push_back(ico); + addChild(ico, PM_POINTER); +} + +void MenuArrowBar::init() +{ + // TODO: up/down arrow +} + +IconGridPanel::IconGridPanel() +: spacing(0), y(0), x(0) +{ + shareAlphaWithChildren = false; // patch selection icons need their own alpha, use fade() instead + alphaMod = 0.01f; + color = 0; +} + +void IconGridPanel::add(BasicIcon *obj) +{ + const int xoffs = (-width / 2) + (obj->quad->width / 2) + spacing; + const int yoffs = (-height / 2) + obj->quad->height + spacing; + const int xlim = width - obj->quad->width; + Vector newpos; + + if(x >= xlim) + { + x = 0; + y += (obj->quad->height + spacing); + } + + newpos = Vector(x + xoffs, y + yoffs); + x += (obj->quad->width + spacing); + + obj->position = newpos; + addChild(obj, PM_POINTER); +} + +void IconGridPanel::fade(bool in, bool sc) +{ + const float t = 0.2f; + Vector newalpha; + if(in) + { + newalpha.x = 1; + if(sc) + scale.interpolateTo(Vector(1, 1), t); + } + else + { + newalpha.x = 0; + if(sc) + scale.interpolateTo(Vector(0.8f, 0.8f), t); + } + alpha.interpolateTo(newalpha, t); + + for(Children::iterator it = children.begin(); it != children.end(); ++it) + { + (*it)->alpha.interpolateTo(newalpha, t); + + if(in) + if(ModIcon *ico = dynamic_cast(*it)) + ico->updateStatus(); + } } diff --git a/Aquaria/ModSelector.h b/Aquaria/ModSelector.h new file mode 100644 index 0000000..2676a98 --- /dev/null +++ b/Aquaria/ModSelector.h @@ -0,0 +1,178 @@ +#ifndef AQ_MOD_SELECTOR_H +#define AQ_MOD_SELECTOR_H + +#include "AquariaMenuItem.h" +#include "DSQ.h" + +class JuicyProgressBar : public Quad +{ +public: + JuicyProgressBar(); + + void progress(float p); + void showText(bool b); + void setText(const std::string& s); + inline float progress() { return perc; } + BitmapText txt; + Quad juice; + +protected: + float perc; +}; + +class BasicIcon : public AquariaMenuItem +{ +public: + BasicIcon(); + std::string label; + virtual bool isGuiVisible(); + virtual bool isCursorInMenuItem(); + +protected: + bool mouseDown; + Vector scaleNormal; + Vector scaleBig; + virtual void onUpdate(float dt); + virtual void onClick(); +}; + +class SubtitleIcon : public BasicIcon +{ +protected: + virtual void onUpdate(float dt); +}; + +class ModIcon : public SubtitleIcon +{ +public: + ModIcon(); + void loadEntry(const ModEntry& entry); + virtual void updateStatus(); + std::string fname; // internal mod name (file/folder name) + std::string modname; // mod name as given by author + unsigned int modId; + ModType modType; + +protected: + virtual void onClick(); +}; + +class MenuIcon : public SubtitleIcon +{ +public: + typedef void (*callback)(int, void*); + MenuIcon(int id); + + callback cb; + void *cb_data; + +protected: + int iconId; + virtual void onClick(); +}; + +#ifdef BBGE_BUILD_VFS + +class ModIconOnline : public SubtitleIcon +{ +public: + ModIconOnline(); + bool fixIcon(); + bool hasPkgOnDisk(); + std::string namestr; // name of the mod: + std::string desc; // + std::string iconfile; // expected local texture file name + std::string packageUrl; // where to download: + + std::string localname; // _mods/.aqmod - under which name the file should be stored if downloaded. Can be empty. + std::string confirmStr; // -- pops up confirmation dialog before download if not empty. + void setDownloadProgress(float p, float barheight = 20); + JuicyProgressBar *pb; // visible if downloading + Quad *extraIcon; // installed or update available + Quad *statusIcon; + bool clickable; + bool isPatch; + bool hasUpdate; + +protected: + virtual void onClick(); +}; + +#endif // BBGE_BUILD_VFS + + +class MenuBasicBar : public Quad +{ +public: + MenuBasicBar(); + virtual void init(); +}; + +class MenuIconBar : public MenuBasicBar +{ +public: + virtual void init(); + std::vector icons; + +protected: + void add(MenuIcon *ico); +}; + +class MenuArrowBar : public MenuBasicBar +{ +public: + virtual void init(); +}; + + +class IconGridPanel : public Quad +{ +public: + IconGridPanel(); + void fade(bool in, bool sc); + void add(BasicIcon *obj); + int spacing; + int getUsedX() const { return x; } + int getUsedY() const { return y; } + +protected: + int x, y; +}; + +class ModSelectorScreen : public Quad, public ActionMapper +{ +public: + ModSelectorScreen(); + + void init(); + void close(); + + void showPanel(int id); + void updateFade(); + + void initModAndPatchPanel(); + void initNetPanel(); + + void moveUp(); + void moveDown(); + void move(int ud, bool instant = false); + + std::vector panels; + MenuIcon *globeIcon, *modsIcon; + BitmapText dlText; + bool gotServerList; + AquariaMenuItem arrowUp, arrowDown; + + void setSubText(const std::string& s); + +protected: + virtual void onUpdate(float dt); + MenuIconBar leftbar; + MenuArrowBar rightbar; + int currentPanel; + BitmapText subtext; + Quad subbox; + float subFadeT; +}; + +#endif diff --git a/Aquaria/Network.cpp b/Aquaria/Network.cpp new file mode 100644 index 0000000..5d09349 --- /dev/null +++ b/Aquaria/Network.cpp @@ -0,0 +1,246 @@ +#include "minihttp.h" +#include "DSQ.h" +#include "Network.h" +#include "ByteBuffer.h" +//#include "VFSTools.h" +#include "MT.h" +#include +#include +#include +#include "SDL.h" + +using namespace minihttp; + +namespace Network { + +struct RequestDataHolder +{ + RequestDataHolder() {} + RequestDataHolder(RequestData *rq) : rq(rq), recvd(rq->_th_recvd), total(rq->_th_total) + { + if(rq->_th_aborted || rq->fail) + ev = NE_ABORT; + else if(rq->_th_finished) + ev = NE_FINISH; + else + ev = NE_UPDATE; + } + RequestData *rq; + NetEvent ev; + size_t recvd; + size_t total; +}; + +// Stores requests which have something interesting +static LockedQueue notifyRequests; + + +class HttpDumpSocket : public HttpSocket +{ +public: + + virtual ~HttpDumpSocket() {} + +protected: + virtual void _OnClose() + { + //puts("_OnClose()"); + minihttp::HttpSocket::_OnClose(); + + const Request& r = GetCurrentRequest(); + RequestData *data = (RequestData*)(r.user); + if(!data->_th_finished) + { + data->_th_aborted = true; + notifyRequests.push(RequestDataHolder(data)); + } + } + virtual void _OnOpen() + { + //puts("_OnOpen()"); + minihttp::HttpSocket::_OnOpen(); + + const Request& r = GetCurrentRequest(); + // TODO ?? + } + + virtual void _OnRequestDone() + { + const Request& r = GetCurrentRequest(); + RequestData *data = (RequestData*)(r.user); + //printf("_OnRequestDone(): %s\n", r.resource.c_str()); + if(data->fp) + { + fclose(data->fp); + data->fp = NULL; + } + if(data->tempFilename != data->finalFilename) + { + if(rename(data->tempFilename.c_str(), data->finalFilename.c_str())) + { + perror("SOCKET: _OnRequestDone() failed to rename file"); + data->fail = true; + } + } + data->_th_finished = true; + data->_th_aborted = (GetStatusCode() != minihttp::HTTP_OK); + notifyRequests.push(RequestDataHolder(data)); + } + + virtual void _OnRecv(char *buf, unsigned int size) + { + if(!size) + return; + /*if(GetStatusCode() != minihttp::HTTP_OK) + { + printf("NETWORK: Got %u bytes with status code %u", size, GetStatusCode()); + return; + }*/ + const Request& r = GetCurrentRequest(); + RequestData *data = (RequestData*)(r.user); + if(!data->fp && !data->fail) + { + data->fp = fopen(data->tempFilename.c_str(), "wb"); + if(!data->fp) + { + fprintf(stderr, "SOCKET: Failed to save %u bytes, file not open"); + data->fail = true; + // TODO: and now? + return; + } + } + fwrite(buf, 1, size, data->fp); + data->_th_recvd += size; + data->_th_total = GetContentLen(); // 0 if chunked transfer encoding is used. + notifyRequests.push(RequestDataHolder(data)); + } +}; + +// for first-time init, and signal to shut down worker thread +static volatile bool netUp = false; + +// Used when sending a HTTP request +static std::string userAgent; + +// socket updater thread +static SDL_Thread *worker = NULL; + +// Request Queue (filled by download(), emptied by the worker thread) +static LockedQueue RQ; + +static int _NetworkWorkerThread(void *); // pre-decl + +static void init() +{ + if(netUp) + return; + + puts("NETWORK: Init"); + + std::ostringstream os; + os << "Aquaria"; +#ifdef AQUARIA_DEMO + os << " Demo"; +#endif + os << " v" << VERSION_MAJOR << "." << VERSION_MINOR << "." << VERSION_REVISION; +#ifdef AQUARIA_CUSTOM_BUILD_ID + os << AQUARIA_CUSTOM_BUILD_ID; +#endif + + userAgent = os.str(); + + if(!worker) + worker = SDL_CreateThread(_NetworkWorkerThread, NULL); +} + +void shutdown() +{ + if(netUp) + { + netUp = false; + puts("NETWORK: Waiting for thread to exit..."); + SDL_WaitThread(worker, NULL); + worker = NULL; + } +} + +// stores all sockets currently in use +// Accessed by worker thread ONLY! +static minihttp::SocketSet sockets; + + +static HttpDumpSocket *th_CreateSocket() +{ + HttpDumpSocket *sock = new HttpDumpSocket; + sock->SetAlwaysHandle(false); // only handle incoming data on success + sock->SetBufsizeIn(1024 * 16); + sock->SetKeepAlive(0); + sock->SetNonBlocking(true); + sock->SetUserAgent(userAgent); + sock->SetFollowRedirect(true); + sockets.add(sock, true); + return sock; +} + + +// must only be run by _NetworkWorkerThread +static bool th_DoSendRequest(RequestData *rq) +{ + Request get; + SplitURI(rq->url, get.host, get.resource, get.port); + if(get.port < 0) + get.port = 80; + + std::ostringstream hostdesc; + hostdesc << get.host << ':' << get.port; + + HttpDumpSocket *sock = th_CreateSocket(); + + get.user = rq; + return sock->SendGet(get, false); +} + +static int _NetworkWorkerThread(void *) +{ + // Init & shutdown networking from the same thread. + // I vaguely remember this could cause trouble on win32 otherwise. -- fg + if(!(netUp = InitNetwork())) + { + fprintf(stderr, "NETWORK: Failed to init network\n"); + return -1; + } + + RequestData *rq; + while(netUp) + { + while(RQ.pop(rq)) + { + if(!th_DoSendRequest(rq)) + { + rq->_th_aborted = true; + notifyRequests.push(RequestDataHolder(rq)); + } + } + while(sockets.update()) {} + SDL_Delay(10); + } + puts("Network worker thread exiting"); + StopNetwork(); + return 0; +} + +void download(RequestData *rq) +{ + init(); + RQ.push(rq); +} + +void update() +{ + RequestDataHolder h; + while(notifyRequests.pop(h)) + h.rq->notify(h.ev, h.recvd, h.total); +} + + +} // namespace Network diff --git a/Aquaria/Network.h b/Aquaria/Network.h new file mode 100644 index 0000000..a96a708 --- /dev/null +++ b/Aquaria/Network.h @@ -0,0 +1,44 @@ +#ifndef AQ_NETWORK_H +#define AQ_NETWORK_H + +namespace Network +{ + enum NetEvent + { + NE_UPDATE, + NE_FINISH, + NE_ABORT, + }; + + class RequestData + { + public: + RequestData() : fp(0), fail(false), _th_finished(false), _th_aborted(false), _th_recvd(0), _th_total(0) {} + virtual ~RequestData() {} + virtual void notify(NetEvent ev, size_t recvd, size_t total) = 0; + + std::string url; + unsigned int port; + std::string tempFilename; // file name to write to while downloading + std::string finalFilename; // name under which the file should be stored when finished + FILE *fp; + bool fail; // FIXME: really need this? + + // used internally by network namespace. + // to avoid MT issues, these may not be accessed from outside! + bool _th_finished; + bool _th_aborted; + size_t _th_recvd; + size_t _th_total; + }; + + // Download a file described by rq. + // Once the download is finished, rq's notify() method will be called in the next update. + void download(RequestData *rq); + void update(); + void shutdown(); +}; + + + +#endif diff --git a/Aquaria/ScriptInterface.cpp b/Aquaria/ScriptInterface.cpp index a1f5a17..673348e 100644 --- a/Aquaria/ScriptInterface.cpp +++ b/Aquaria/ScriptInterface.cpp @@ -713,6 +713,26 @@ static bool findFile_helper(const char *rawname, std::string &fname) return exists(fname); } +static int loadFile_helper(lua_State *L, const char *fn) +{ +#ifdef BBGE_BUILD_VFS + VFILE *vf = vfs.GetFile(fn); + if (!vf) + { + lua_pushfstring(L, "cannot open %s", fn); + return LUA_ERRFILE; + } + else + { + int result = luaL_loadbuffer(L, (const char*)vf->getBuf(), vf->size(), fn); + vf->dropBuf(true); + return result; + } +#else + return luaL_loadfile(L, fn); +#endif +} + luaFunc(dofile_caseinsensitive) { // This is Lua's dofile(), with some tweaks. --ryan. @@ -720,7 +740,7 @@ luaFunc(dofile_caseinsensitive) findFile_helper(luaL_checkstring(L, 1), fname); int n = lua_gettop(L); - if (luaL_loadfile(L, fname.c_str()) != 0) + if (loadFile_helper(L, fname.c_str()) != 0) lua_error(L); lua_call(L, 0, LUA_MULTRET); return lua_gettop(L) - n; @@ -732,7 +752,7 @@ luaFunc(loadfile_caseinsensitive) std::string fname; findFile_helper(luaL_checkstring(L, 1), fname); - if (luaL_loadfile(L, fname.c_str()) == 0) /* OK? */ + if (loadFile_helper(L, fname.c_str()) == 0) /* OK? */ return 1; else { @@ -763,7 +783,7 @@ MakeTypeCheckFunc(isText, SCO_TEXT) #undef MakeTypeCheckFunc // special, because it would return true on almost everything that is RenderObject based. -// Instead, return true only for stuff created with createQuad() +// Instead, return true only for stuff created with createQuad() luaFunc(isQuad) { RenderObject *r = robj(L); @@ -2192,7 +2212,7 @@ luaFunc(getWorldType) { luaReturnNum((int)dsq->continuity.getWorldType()); } - + luaFunc(getNearestNodeByType) { int x = lua_tonumber(L, 1); @@ -2703,7 +2723,7 @@ luaFunc(spawnIngredient) if (times == 0) times = 1; bool out = getBool(L, 5); Entity *e = dsq->game->spawnIngredient(getString(L, 1), Vector(lua_tonumber(L, 2), lua_tonumber(L, 3)), times, out); - + luaReturnPtr(e); } @@ -2728,8 +2748,8 @@ luaFunc(spawnParticleEffect) if (!layer) layer = LR_PARTICLES; float follow = lua_tonumber(L, 7); - ParticleEffect *pe = dsq->spawnParticleEffect(getString(L, 1), Vector(lua_tonumber(L, 2), lua_tonumber(L, 3)), - rot, t, layer, follow); + ParticleEffect *pe = dsq->spawnParticleEffect(getString(L, 1), Vector(lua_tonumber(L, 2), lua_tonumber(L, 3)), + rot, t, layer, follow); luaReturnPtr(pe); } @@ -5896,7 +5916,7 @@ luaFunc(entity_isPositionInRange) if (e) { if ((e->position - Vector(x,y)).isLength2DIn(lua_tonumber(L, 4))) - { + { v = true; } } @@ -7101,7 +7121,7 @@ luaFunc(setLiPower) { float m = lua_tonumber(L, 1); float t = lua_tonumber(L, 2); - dsq->continuity.setLiPower(m, t); + dsq->continuity.setLiPower(m, t); luaReturnNil(); } @@ -7118,10 +7138,10 @@ luaFunc(getPetPower) luaFunc(appendUserDataPath) { std::string path = getString(L, 1); - + if (!dsq->getUserDataFolder().empty()) path = dsq->getUserDataFolder() + "/" + path; - + luaReturnStr(path.c_str()); } @@ -8928,7 +8948,7 @@ Script *ScriptInterface::openScript(const std::string &file, bool ignoremissing lua_getglobal(baseState, "v"); // Load the file itself. This leaves the Lua chunk on the stack. - int result = luaL_loadfile(baseState, realFile.c_str()); + int result = loadFile_helper(baseState, realFile.c_str()); if (result != 0) { if(result != LUA_ERRFILE || (result == LUA_ERRFILE && !ignoremissing)) @@ -9250,7 +9270,7 @@ bool Script::call(const char *name, void *param1, void *param2, void *param3, fl int Script::callVariadic(const char *name, lua_State *fromL, int nparams, void *param) { int oldtop = lua_gettop(L); - + lookupFunc(name); luaPushPointer(L, param); diff --git a/Aquaria/StatsAndAchievements.cpp b/Aquaria/StatsAndAchievements.cpp index e820ecc..cb39565 100644 --- a/Aquaria/StatsAndAchievements.cpp +++ b/Aquaria/StatsAndAchievements.cpp @@ -171,15 +171,15 @@ void StatsAndAchievements::RunFrame() requestedStats = true; const size_t max_achievements = ARRAYSIZE(g_rgAchievements); - FILE *io = NULL; + VFILE *io = NULL; // Get generic achievement data... std::string fname = dsq->user.localisePath("data/achievements.txt"); - io = fopen(fname.c_str(), "r"); + io = vfopen(fname.c_str(), "r"); char line[1024]; for (size_t i = 0; i < max_achievements; i++) { - if (!io || (fgets(line, sizeof (line), io) == NULL)) + if (!io || (vfgets(line, sizeof (line), io) == NULL)) snprintf(line, sizeof (line), "Achievement #%d", (int) i); else { @@ -189,7 +189,7 @@ void StatsAndAchievements::RunFrame() line[sizeof (g_rgAchievements[i].name) - 1] = '\0'; // just in case. strcpy(g_rgAchievements[i].name, line); - if (!io || (fgets(line, sizeof (line), io) == NULL)) + if (!io || (vfgets(line, sizeof (line), io) == NULL)) snprintf(line, sizeof (line), "[Description of Achievement #%d is missing!]", (int) i); else { @@ -204,20 +204,20 @@ void StatsAndAchievements::RunFrame() } if (io != NULL) - fclose(io); + vfclose(io); // See what this specific player has achieved... unsigned char *buf = new unsigned char[max_achievements]; size_t br = 0; fname = (core->getUserDataFolder() + "/achievements.bin"); - io = fopen(fname.c_str(), "rb"); - if (io == NULL) + FILE *u = fopen(fname.c_str(), "rb"); + if (u == NULL) statsValid = true; // nothing to report. else { - br = fread(buf, sizeof (buf[0]), max_achievements, io); - fclose(io); + br = fread(buf, sizeof (buf[0]), max_achievements, u); + fclose(u); } if (br == max_achievements) diff --git a/Aquaria/StringBank.cpp b/Aquaria/StringBank.cpp index 2ccdbc7..42f5e10 100644 --- a/Aquaria/StringBank.cpp +++ b/Aquaria/StringBank.cpp @@ -41,7 +41,7 @@ void StringBank::_load(const std::string &file) { //debugLog("StringBank::load("+file+")"); - std::ifstream in(file.c_str()); + InStream in(file.c_str()); std::string line; int idx; diff --git a/Aquaria/SubtitlePlayer.cpp b/Aquaria/SubtitlePlayer.cpp index cf79a27..c6469b6 100644 --- a/Aquaria/SubtitlePlayer.cpp +++ b/Aquaria/SubtitlePlayer.cpp @@ -63,7 +63,7 @@ void SubtitlePlayer::go(const std::string &subs) } } - std::ifstream in(f.c_str()); + InStream in(f.c_str()); std::string line; while (std::getline(in, line)) { diff --git a/Aquaria/UserSettings.cpp b/Aquaria/UserSettings.cpp index 057e9a9..652d06f 100644 --- a/Aquaria/UserSettings.cpp +++ b/Aquaria/UserSettings.cpp @@ -126,17 +126,17 @@ void UserSettings::save() xml_fpsSmoothing.SetAttribute("v", video.fpsSmoothing); } xml_video.InsertEndChild(xml_fpsSmoothing); - + TiXmlElement xml_parallax("Parallax"); std::ostringstream os; os << video.parallaxOn0 << " " << video.parallaxOn1 << " " << video.parallaxOn2; xml_parallax.SetAttribute("on", os.str()); xml_video.InsertEndChild(xml_parallax); - + TiXmlElement xml_numParticles("NumParticles"); xml_numParticles.SetAttribute("v", video.numParticles); xml_video.InsertEndChild(xml_numParticles); - + TiXmlElement xml_screenMode("ScreenMode"); { xml_screenMode.SetAttribute("resx", video.resx); @@ -202,7 +202,7 @@ void UserSettings::save() xml_joyAxes.SetDoubleAttribute("s2dead", double(control.s2dead)); } xml_control.InsertEndChild(xml_joyAxes); - + TiXmlElement xml_actionSet("ActionSet"); { for (int i = 0; i < control.actionSet.inputSet.size(); i++) @@ -232,7 +232,7 @@ void UserSettings::save() xml_intro.SetAttribute("on", demo.intro); } xml_demo.InsertEndChild(xml_intro); - + TiXmlElement xml_shortLogos("ShortLogos"); { xml_shortLogos.SetAttribute("on", demo.shortLogos); @@ -240,16 +240,27 @@ void UserSettings::save() xml_demo.InsertEndChild(xml_shortLogos); } doc.InsertEndChild(xml_demo); - + TiXmlElement xml_data("Data"); { xml_data.SetAttribute("savePage", data.savePage); xml_data.SetAttribute("saveSlot", data.saveSlot); - xml_data.SetAttribute("lastSelectedMod", data.lastSelectedMod); + + std::ostringstream ss; + for (std::set::iterator it = dsq->activePatches.begin(); it != dsq->activePatches.end(); ++it) + ss << *it << " "; + xml_data.SetAttribute("activePatches", ss.str()); } doc.InsertEndChild(xml_data); + + TiXmlElement xml_net("Network"); + { + xml_net.SetAttribute("masterServer", network.masterServer); + } + doc.InsertEndChild(xml_net); + } - + #if defined(BBGE_BUILD_UNIX) doc.SaveFile(dsq->getPreferencesFolder() + "/" + userSettingsFilename); #elif defined(BBGE_BUILD_WINDOWS) @@ -315,7 +326,7 @@ void UserSettings::loadDefaults(bool doApply) void UserSettings::load(bool doApply, const std::string &overrideFile) { TiXmlDocument doc; - + #if defined(BBGE_BUILD_UNIX) doc.LoadFile(dsq->getPreferencesFolder() + "/" + userSettingsFilename); #elif defined(BBGE_BUILD_WINDOWS) @@ -324,7 +335,7 @@ void UserSettings::load(bool doApply, const std::string &overrideFile) else doc.LoadFile(userSettingsFilename); #endif - + version.settingsVersion = 0; TiXmlElement *xml_version = doc.FirstChildElement("Version"); @@ -415,7 +426,7 @@ void UserSettings::load(bool doApply, const std::string &overrideFile) readInt(xml_video, "NoteEffects", "on", &video.noteEffects); readInt(xml_video, "FpsSmoothing", "v", &video.fpsSmoothing); - + /* readInt(xml_video, "Parallax", "on", &video.parallaxOn); */ @@ -428,7 +439,7 @@ void UserSettings::load(bool doApply, const std::string &overrideFile) is >> video.parallaxOn0 >> video.parallaxOn1 >> video.parallaxOn2; } } - + readInt(xml_video, "NumParticles", "v", &video.numParticles); TiXmlElement *xml_screenMode = xml_video->FirstChildElement("ScreenMode"); @@ -489,16 +500,16 @@ void UserSettings::load(bool doApply, const std::string &overrideFile) if (!name.empty()) { ActionInput *ai = control.actionSet.addActionInput(name); - + ai->fromString(xml_action->Attribute("input")); } xml_action = xml_action->NextSiblingElement(); } } - + readInt(xml_control, "ToolTipsOn", "on", &control.toolTipsOn); } - + TiXmlElement *xml_demo = doc.FirstChildElement("Demo"); if (xml_demo) { @@ -506,13 +517,30 @@ void UserSettings::load(bool doApply, const std::string &overrideFile) readInt(xml_demo, "Intro2", "on", &demo.intro); readInt(xml_demo, "ShortLogos", "on", &demo.shortLogos); } - + TiXmlElement *xml_data = doc.FirstChildElement("Data"); if (xml_data) { readIntAtt(xml_data, "savePage", &data.savePage); readIntAtt(xml_data, "saveSlot", &data.saveSlot); - readIntAtt(xml_data, "lastSelectedMod", &data.lastSelectedMod); + + if(const char *patchlist = xml_data->Attribute("activePatches")) + { + SimpleIStringStream ss(patchlist, SimpleIStringStream::REUSE); + std::string tmp; + while(ss) + { + ss >> tmp; + if(tmp.length()) + dsq->activePatches.insert(tmp); + } + } + } + + TiXmlElement *xml_net = doc.FirstChildElement("Network"); + if (xml_net) + { + network.masterServer = xml_net->Attribute("masterServer"); } if (system.locale.empty()) @@ -553,10 +581,10 @@ void UserSettings::apply() if (dsq->game->avatar) { - dsq->game->avatar->updateHeartbeatSfx(); + dsq->game->avatar->updateHeartbeatSfx(); } } - + dsq->bindInput(); core->settings.prebufferSounds = audio.prebuffer; diff --git a/Aquaria/UserSettings.h b/Aquaria/UserSettings.h index 6d877b8..6be8e60 100644 --- a/Aquaria/UserSettings.h +++ b/Aquaria/UserSettings.h @@ -162,10 +162,9 @@ public: struct Data { - Data() { savePage=0; saveSlot=0; lastSelectedMod=0; } + Data() { savePage=0; saveSlot=0; } int savePage; int saveSlot; - int lastSelectedMod; } data; struct Version @@ -174,6 +173,11 @@ public: int settingsVersion; } version; + struct Network + { + std::string masterServer; + } network; + void loadDefaults(bool doApply=true); void load(bool doApply=true, const std::string &overrideFile=""); void save(); diff --git a/Aquaria/WorldMapTiles.cpp b/Aquaria/WorldMapTiles.cpp index 038505f..e7db959 100644 --- a/Aquaria/WorldMapTiles.cpp +++ b/Aquaria/WorldMapTiles.cpp @@ -254,7 +254,7 @@ void WorldMap::_load(const std::string &file) std::string line; - std::ifstream in(file.c_str()); + InStream in(file.c_str()); while (std::getline(in, line)) { diff --git a/BBGE/ActionMapper.cpp b/BBGE/ActionMapper.cpp index 84003ef..c7e3e2d 100644 --- a/BBGE/ActionMapper.cpp +++ b/BBGE/ActionMapper.cpp @@ -440,21 +440,3 @@ void ActionMapper::removeAllActions() } actionData.clear(); } -// -//void ActionMapper::loadActionSet(const std::string &fn) -//{ -// std::ifstream in(std::string("actionSets/"+fn+".txt").c_str()); -// std::string key; -// std::string action; -// while (in >> key) -// { -// in >> action; -// if (key != " " && !key.empty() && key.size()==1) -// { -// char ckey = key[0]; -// addAction(action, ckey); -// //msg (action+" key:"+ckey); -// } -// } -// in.close(); -//} diff --git a/BBGE/BBGECompileConfig.h b/BBGE/BBGECompileConfig.h index 62fb262..762a1c6 100644 --- a/BBGE/BBGECompileConfig.h +++ b/BBGE/BBGECompileConfig.h @@ -5,11 +5,12 @@ #define BBGE_BUILD_SDL 1 #define BBGE_BUILD_FRAMEBUFFER 1 -#define BBGE_BUILD_SHADERS 1 +//#define BBGE_BUILD_SHADERS 1 #define BBGE_BUILD_OPENGL 1 #define BBGE_BUILD_OPENGL_DYNAMIC 1 #define BBGE_BUILD_FMOD_OPENAL_BRIDGE 1 #define BBGE_BUILD_ACHIEVEMENTS_INTERNAL 1 +#define BBGE_BUILD_VFS 1 #endif diff --git a/BBGE/Base.cpp b/BBGE/Base.cpp index 132f049..888a960 100644 --- a/BBGE/Base.cpp +++ b/BBGE/Base.cpp @@ -20,6 +20,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "Base.h" #include "Core.h" +#include #ifdef BBGE_BUILD_WINDOWS #include @@ -272,31 +273,35 @@ std::string upperCase(const std::string &s1) return ret; } -bool exists(const std::string &f, bool makeFatal) +bool exists(const std::string &f, bool makeFatal, bool skipVFS) { - /* - if (!PHYSFS_exists(f.c_str())) - { - */ - /* - std::ostringstream os; - os << "checking to see if [" << f << "] exists"; - debugLog(os.str()); - */ + bool e = false; - FILE *file = fopen(core->adjustFilenameCase(f).c_str(), "rb"); - if (!file) +#ifdef BBGE_BUILD_VFS + if (!skipVFS) + { + e = !!vfs.GetFile(f.c_str()); + } + else +#endif + if (!e) + { + std::string tmp = core->adjustFilenameCase(f); + FILE *file = fopen(tmp.c_str(), "rb"); + if (file) { - if (makeFatal) - { - errorLog(std::string("Could not open [" + f + "]")); - exit(0); - } - return false; + e = true; + fclose(file); } - fclose(file); - //} - return true; + } + + if (makeFatal && !e) + { + errorLog(std::string("Could not open [" + f + "]")); + exit(0); + } + + return e; } void drawCircle(float radius, int stepSize) @@ -446,13 +451,22 @@ void debugLog(const std::string &s) // also obtain the data length by passing a pointer to an unsigned long // as the (optional) second parameter. The buffer should be freed with // delete[] when no longer needed. -char *readFile(std::string path, unsigned long *size_ret) +char *readFile(const std::string& path, unsigned long *size_ret) { + long fileSize; +#ifdef BBGE_BUILD_VFS + VFILE *vf = vfs.GetFile(path.c_str()); + if (!vf) + return NULL; + fileSize = vf->size(); + char *buffer = (char*)vf->getBuf(NULL, NULL); + vf->dropBuf(false); +#else FILE *f = fopen(path.c_str(), "rb"); if (!f) return NULL; - long fileSize; + if (fseek(f, 0, SEEK_END) != 0 || (fileSize = ftell(f)) < 0 || fseek(f, 0, SEEK_SET) != 0) @@ -483,11 +497,12 @@ char *readFile(std::string path, unsigned long *size_ret) fclose(f); return NULL; } - fclose(f); + buffer[fileSize] = 0; +#endif + if (size_ret) *size_ret = fileSize; - buffer[fileSize] = 0; return buffer; } @@ -551,13 +566,56 @@ std::string stripEndlineForUnix(const std::string &in) return out; } +#ifdef BBGE_BUILD_VFS + +struct vfscallback_s +{ + std::string *path; + const char *ext; + intptr_t param; + void (*callback)(const std::string &filename, intptr_t param); +}; + +void forEachFile_vfscallback(VFILE *vf, void *user) +{ + vfscallback_s *d = (vfscallback_s*)user; + if(d->ext) + { + const char *e = strrchr(vf->name(), '.'); + if(e && nocasecmp(d->ext, e)) + return; + } + d->callback(*(d->path) + vf->name(), d->param); +} + +#endif + void forEachFile(std::string path, std::string type, void callback(const std::string &filename, intptr_t param), intptr_t param) { if (path.empty()) return; - path = core->adjustFilenameCase(path); + +#ifdef BBGE_BUILD_VFS + ttvfs::VFSDir *vd = vfs.GetDir(path.c_str(), true); // add to tree if it wasn't loaded before + if(!vd) + { + debugLog("Path '" + path + "' does not exist"); + return; + } + vd->load(false); + vfscallback_s dat; + dat.path = &path; + dat.ext = type.length() ? type.c_str() : NULL; + dat.param = param; + dat.callback = callback; + vd->forEachFile(forEachFile_vfscallback, &dat, true); + + return; + // ------------------------------------- +#endif + stringToLower(type); - //HACK: MAC: + path = core->adjustFilenameCase(path); debugLog("forEachFile - path: " + path + " type: " + type); #if defined(BBGE_BUILD_UNIX) @@ -856,6 +914,8 @@ float lerp(const float &v1, const float &v2, float dt, int lerpType) } +#if 0 + #include #include @@ -1000,6 +1060,7 @@ int unpackFile(const std::string &sourcef, const std::string &destf) return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR; } +#endif void openURL(const std::string &url) { diff --git a/BBGE/Base.h b/BBGE/Base.h index 0cd1cc2..5a09f74 100644 --- a/BBGE/Base.h +++ b/BBGE/Base.h @@ -137,6 +137,11 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "Rect.h" #include "math.h" +#include "FileAPI.h" + +// dumb win32 includes/defines cleanup +#undef GetCharWidth + enum Align { ALIGN_CENTER=0, ALIGN_LEFT }; @@ -195,10 +200,10 @@ void stringToLower(std::string &s); void stringToLowerUserData(std::string &s); void glColor3_256(int r, int g, int b); float sqr(float x); -bool exists(const std::string &f, bool makeFatal = false); +bool exists(const std::string &f, bool makeFatal = false, bool skipVFS = false); void errorLog(const std::string &s); void debugLog(const std::string &s); -char *readFile(std::string path, unsigned long *size_ret = 0); +char *readFile(const std::string& path, unsigned long *size_ret = 0); char *readCompressedFile(std::string path, unsigned long *size_ret = 0); void forEachFile(std::string path, std::string type, void callback(const std::string &filename, intptr_t param), intptr_t param); std::string stripEndlineForUnix(const std::string &in); @@ -280,8 +285,8 @@ enum LerpType float lerp(const float &v1, const float &v2, float dt, int lerpType); -int packFile(const std::string &sourcef, const std::string &destf, int level); -int unpackFile(const std::string &sourcef, const std::string &destf); +//int packFile(const std::string &sourcef, const std::string &destf, int level); +//int unpackFile(const std::string &sourcef, const std::string &destf); void openURL(const std::string &url); diff --git a/BBGE/BitmapFont.cpp b/BBGE/BitmapFont.cpp index 9b53053..dca022e 100644 --- a/BBGE/BitmapFont.cpp +++ b/BBGE/BitmapFont.cpp @@ -100,7 +100,7 @@ void BitmapText::autoKern() void BitmapText::loadSpacingMap(const std::string &file) { spacingMap.clear(); - std::ifstream inFile(file.c_str()); + InStream inFile(file.c_str()); std::string line; while (std::getline(inFile, line)) { diff --git a/BBGE/Core.cpp b/BBGE/Core.cpp index 12331fd..ccf67f5 100644 --- a/BBGE/Core.cpp +++ b/BBGE/Core.cpp @@ -1252,6 +1252,8 @@ bool Core::isShuttingDown() void Core::init() { + setupFileAccess(); + flags.set(CF_CLEARBUFFERS); quitNestedMainFlag = false; #ifdef BBGE_BUILD_GLFW @@ -2880,7 +2882,7 @@ void Core::main(float runTime) */ } -#if (!defined(_DEBUG) || defined(BBGE_BUILD_UNIX)) && defined(BBGE_BUILD_SDL) +#if !defined(_DEBUG) && defined(BBGE_BUILD_SDL) if (verbose) debugLog("checking window active"); if (lib_graphics && (wasInactive || !settings.runInBackground)) @@ -4078,6 +4080,11 @@ void Core::shutdown() debugLog("OK"); #endif +#ifdef BBGE_BUILD_VFS + debugLog("Unload VFS..."); + vfs.Clear(); + debugLog("OK"); +#endif #ifdef BBGE_BUILD_SDL @@ -4757,3 +4764,103 @@ int Core::tgaSaveSeries(char *filename, // ilutGLScreenie(); } + + #include "DeflateCompressor.h" + + // saves an array of pixels as a TGA image (frees the image data passed in) +int Core::zgaSave( const char *filename, + short int w, + short int h, + unsigned char depth, + unsigned char *imageData) { + + ByteBuffer::uint8 type,mode,aux, pixelDepth = depth; + ByteBuffer::uint8 cGarbage = 0; + ByteBuffer::uint16 iGarbage = 0; + ByteBuffer::uint16 width = w, height = h; + +// open file and check for errors + FILE *file = fopen(adjustFilenameCase(filename).c_str(), "wb"); + if (file == NULL) { + delete [] imageData; + return (int)false; + } + +// compute image type: 2 for RGB(A), 3 for greyscale + mode = pixelDepth / 8; + if ((pixelDepth == 24) || (pixelDepth == 32)) + type = 2; + else + type = 3; + +// convert the image data from RGB(A) to BGR(A) + if (mode >= 3) + for (int i=0; i < width * height * mode ; i+= mode) { + aux = imageData[i]; + imageData[i] = imageData[i+2]; + imageData[i+2] = aux; + } + + ZlibCompressor z; + z.SetForceCompression(true); + z.reserve(width * height * mode + 30); + z << cGarbage + << cGarbage + << type + << iGarbage + << iGarbage + << cGarbage + << iGarbage + << iGarbage + << width + << height + << pixelDepth + << cGarbage; + + z.append(imageData, width * height * mode); + z.Compress(3); + +// save the image data + if (fwrite(z.contents(), 1, z.size(), file) != z.size()) + { + fclose(file); + delete [] imageData; + return (int)false; + } + + fclose(file); + delete [] imageData; + + return (int)true; +} + + + +#include "ttvfs_zip/VFSZipArchiveLoader.h" + +void Core::setupFileAccess() +{ +#ifdef BBGE_BUILD_VFS + debugLog("Init VFS..."); + + if(!ttvfs::checkCompat()) + exit(1); + + vfs.AddArchiveLoader(new ttvfs::VFSZipArchiveLoader); + + if(!vfs.LoadFileSysRoot(false)) + { + errorLog("Failed to setup file access"); + exit(1); + } + + vfs.Prepare(); + + // TODO: mount and other stuff + + //vfs.AddArchive("aqfiles.zip", false, ""); + + + debugLog("Done"); +#endif +} diff --git a/BBGE/Core.h b/BBGE/Core.h index 76f6738..79e0d1a 100644 --- a/BBGE/Core.h +++ b/BBGE/Core.h @@ -1306,6 +1306,7 @@ public: CoreSettings settings; int tgaSave(const char *filename, short int width, short int height, unsigned char pixelDepth, unsigned char *imageData); + int zgaSave(const char *filename, short int width, short int height, unsigned char pixelDepth, unsigned char *imageData); volatile int dbg_numThreadDecoders; @@ -1398,6 +1399,8 @@ protected: int tgaSaveSeries(char *filename, short int width, short int height, unsigned char pixelDepth, unsigned char *imageData); virtual void onUpdate(float dt); virtual void onRender(){} + + void setupFileAccess(); }; extern Core *core; diff --git a/BBGE/FmodOpenALBridge.cpp b/BBGE/FmodOpenALBridge.cpp index da6dcad..1ab51b4 100644 --- a/BBGE/FmodOpenALBridge.cpp +++ b/BBGE/FmodOpenALBridge.cpp @@ -42,6 +42,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "ogg/ogg.h" #include "vorbis/vorbisfile.h" +#include "FileAPI.h" #include "MT.h" #ifndef _DEBUG @@ -55,7 +56,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. class OggDecoder { public: // Create a decoder that streams from a file. - OggDecoder(FILE *fp); + OggDecoder(VFILE *fp); // Create a decoder that streams from a memory buffer. OggDecoder(const void *data, long data_size); @@ -106,7 +107,7 @@ private: // Data source. If fp != NULL, the source is that file; otherwise, the // source is the buffer pointed to by "data" with size "data_size" bytes. - FILE *fp; + VFILE *fp; const char *data; long data_size; long data_pos; // Current read position for memory buffers @@ -145,22 +146,16 @@ private: // ov_open_callbacks() call. Note that we rename the fseek() wrapper // to avoid an identifier collision when building with more recent // versions of libvorbis. -static int BBGE_ov_header_fseek_wrap(FILE *f,ogg_int64_t off,int whence){ +static int BBGE_ov_header_fseek_wrap(VFILE *f,ogg_int64_t off,int whence){ if(f==NULL)return(-1); -#ifdef __MINGW32__ - return fseeko64(f,off,whence); -#elif defined (_WIN32) - return _fseeki64(f,off,whence); -#else - return fseek(f,off,whence); -#endif + return vfseek(f,(long int)off,whence); // no ogg file is larger than 4 GB, int-cast should be ok } static int noclose(FILE *f) {return 0;} static const ov_callbacks local_OV_CALLBACKS_NOCLOSE = { - (size_t (*)(void *, size_t, size_t, void *)) fread, + (size_t (*)(void *, size_t, size_t, void *)) vfread, (int (*)(void *, ogg_int64_t, int)) BBGE_ov_header_fseek_wrap, (int (*)(void *)) noclose, // NULL doesn't work in libvorbis-1.1.2 - (long (*)(void *)) ftell + (long (*)(void *)) vftell }; // Memory I/O callback set. @@ -256,7 +251,7 @@ void OggDecoder::decode_loop(OggDecoder *this_) } -OggDecoder::OggDecoder(FILE *fp) +OggDecoder::OggDecoder(VFILE *fp) { for (int i = 0; i < NUM_BUFFERS; i++) { @@ -586,10 +581,10 @@ namespace FMOD { class OpenALSound { public: - OpenALSound(FILE *_fp, const bool _looping); // ctor for ogg streamed from file + OpenALSound(VFILE *_fp, const bool _looping); // ctor for ogg streamed from file OpenALSound(void *_data, long _size, const bool _looping); // ctor for ogg streamed from memory OpenALSound(ALuint _bid, const bool _looping); // ctor for raw samples already assigned an opanAL buffer ID - FILE *getFile() const { return fp; } + VFILE *getFile() const { return fp; } const void *getData() const { return data; } long getSize() const { return size; } bool isLooping() const { return looping; } @@ -599,7 +594,7 @@ public: ALuint getBufferName() const { return bid; } private: - FILE * const fp; + VFILE * const fp; void * const data; // Only used if fp==NULL const long size; // Only used if fp==NULL const bool looping; @@ -608,7 +603,7 @@ private: ALuint bid; // only used if raw == true }; -OpenALSound::OpenALSound(FILE *_fp, const bool _looping) +OpenALSound::OpenALSound(VFILE *_fp, const bool _looping) : fp(_fp) , data(NULL) , size(0) @@ -654,7 +649,7 @@ FMOD_RESULT OpenALSound::release() else { if (fp) - fclose(fp); + vfclose(fp); else free(data); } @@ -1155,14 +1150,14 @@ FMOD_RESULT OpenALSystem::createDSPByType(const FMOD_DSP_TYPE type, DSP **dsp) return FMOD_ERR_INTERNAL; } -static void *decode_to_pcm(FILE *io, ALenum &format, ALsizei &size, ALuint &freq) +static void *decode_to_pcm(VFILE *io, ALenum &format, ALsizei &size, ALuint &freq) { ALubyte *retval = NULL; // Uncompress and feed to the AL. OggVorbis_File vf; memset(&vf, '\0', sizeof (vf)); - if (ov_open(io, &vf, NULL, 0) == 0) + if (ov_open_callbacks(io, &vf, NULL, 0, local_OV_CALLBACKS_NOCLOSE) == 0) { int bitstream = 0; vorbis_info *info = ov_info(&vf, -1); @@ -1222,8 +1217,7 @@ FMOD_RESULT OpenALSystem::createSound(const char *name_or_data, const FMOD_MODE strcat(fname, ".ogg"); // just in case... - #undef fopen - FILE *io = fopen(core->adjustFilenameCase(fname).c_str(), "rb"); + VFILE *io = vfopen(core->adjustFilenameCase(fname).c_str(), "rb"); if (io == NULL) return FMOD_ERR_INTERNAL; @@ -1240,7 +1234,7 @@ FMOD_RESULT OpenALSystem::createSound(const char *name_or_data, const FMOD_MODE ALsizei size = 0; ALuint freq = 0; void *data = decode_to_pcm(io, format, size, freq); - fclose(io); + vfclose(io); ALuint bid = 0; alGenBuffers(1, &bid); @@ -1255,12 +1249,12 @@ FMOD_RESULT OpenALSystem::createSound(const char *name_or_data, const FMOD_MODE else { // Create streaming memory decoder - fseek(io, 0, SEEK_END); - long size = ftell(io); - if (fseek(io, 0, SEEK_SET) != 0) + vfseek(io, 0, SEEK_END); + long size = vftell(io); + if (vfseek(io, 0, SEEK_SET) != 0) { debugLog("Seek error on " + std::string(fname)); - fclose(io); + vfclose(io); return FMOD_ERR_INTERNAL; } @@ -1268,12 +1262,13 @@ FMOD_RESULT OpenALSystem::createSound(const char *name_or_data, const FMOD_MODE if (data == NULL) { debugLog("Out of memory for " + std::string(fname)); - fclose(io); + vfclose(io); return FMOD_ERR_INTERNAL; } - long nread = fread(data, 1, size, io); - fclose(io); + long nread = vfread(data, 1, size, io); + vfclose(io); + vfclear(io); if (nread != size) { debugLog("Failed to read data from " + std::string(fname)); diff --git a/BBGE/MT.h b/BBGE/MT.h index cd915fe..710cadf 100644 --- a/BBGE/MT.h +++ b/BBGE/MT.h @@ -95,19 +95,6 @@ public: unlock(); return e; } - bool popIfPossible(T& e) - { - lock(); - if(!_q.empty()) - { - e = _q.front(); - _q.pop(); - unlock(); - return true; - } - unlock(); - return false; - } private: std::queue _q; diff --git a/BBGE/Precacher.cpp b/BBGE/Precacher.cpp index eb315d9..24e1585 100644 --- a/BBGE/Precacher.cpp +++ b/BBGE/Precacher.cpp @@ -123,7 +123,7 @@ void Precacher::precacheTex(const std::string &tex) void Precacher::precacheList(const std::string &list, void progressCallback()) { loadProgressCallback = progressCallback; - std::ifstream in(list.c_str()); + InStream in(list.c_str()); std::string t; while (std::getline(in, t)) { diff --git a/BBGE/Shader.cpp b/BBGE/Shader.cpp index 09ebbab..90c7c5e 100644 --- a/BBGE/Shader.cpp +++ b/BBGE/Shader.cpp @@ -181,7 +181,7 @@ unsigned char *readShaderFile( const char *fileName ) { debugLog("readShaderFile()"); #ifdef BBGE_BUILD_WINDOWS - FILE *file = fopen( fileName, "r" ); + FILE *file = fopen( fileName, "r" ); // FIXME: should this code ever be re-activated, adjust to VFS! -- fg if( file == NULL ) { diff --git a/BBGE/SoundManager.cpp b/BBGE/SoundManager.cpp index 94bbf7c..f6fdc50 100644 --- a/BBGE/SoundManager.cpp +++ b/BBGE/SoundManager.cpp @@ -134,17 +134,21 @@ FMOD_RESULT F_CALLBACK myopen(const char *name, int unicode, unsigned int *files { if (name) { - FILE *fp; + VFILE *fp; - fp = fopen(name, "rb"); + fp = vfopen(name, "rb"); if (!fp) { return FMOD_ERR_FILE_NOTFOUND; } - fseek(fp, 0, SEEK_END); +#ifdef BBGE_BUILD_VFS + *filesize = fp->size(); +#else + vfseek(fp, 0, SEEK_END); *filesize = ftell(fp); - fseek(fp, 0, SEEK_SET); + vfseek(fp, 0, SEEK_SET); +#endif *userdata = (void *)0x12345678; *handle = fp; @@ -160,7 +164,7 @@ FMOD_RESULT F_CALLBACK myclose(void *handle, void *userdata) return FMOD_ERR_INVALID_PARAM; } - fclose((FILE *)handle); + vfclose((VFILE *)handle); return FMOD_OK; } @@ -174,7 +178,7 @@ FMOD_RESULT F_CALLBACK myread(void *handle, void *buffer, unsigned int sizebytes if (bytesread) { - *bytesread = (int)fread(buffer, 1, sizebytes, (FILE *)handle); + *bytesread = (int)vfread(buffer, 1, sizebytes, (VFILE *)handle); if (*bytesread < sizebytes) { @@ -192,7 +196,7 @@ FMOD_RESULT F_CALLBACK myseek(void *handle, unsigned int pos, void *userdata) return FMOD_ERR_INVALID_PARAM; } - fseek((FILE *)handle, pos, SEEK_SET); + vfseek((VFILE *)handle, pos, SEEK_SET); return FMOD_OK; } diff --git a/BBGE/Texture.cpp b/BBGE/Texture.cpp index c1cd58e..c3e01fc 100644 --- a/BBGE/Texture.cpp +++ b/BBGE/Texture.cpp @@ -294,11 +294,11 @@ void Texture::reload() unload(); load(loadName); - if (ow != -1 && oh != -1) + /*if (ow != -1 && oh != -1) { width = ow; height = oh; - } + }*/ debugLog("DONE"); } @@ -466,6 +466,31 @@ void Texture::loadPNG(const std::string &file) pngType = PNG_LUMINANCEALPHA; } +#ifdef BBGE_BUILD_VFS + ttvfs::VFSFile *vf = vfs.GetFile(file.c_str()); + const char *memptr = vf ? (const char*)vf->getBuf() : NULL; + if(!memptr) + { + debugLog("Can't load PNG file: " + file); + width = 64; + height = 64; + Texture::textureError = TEXERR_FILENOTFOUND; + //exit(1); + return; + } + + int memsize = vf->size(); + if (filter == GL_NEAREST) + { + textures[0] = pngBindMem(memptr, memsize, PNG_NOMIPMAPS, pngType, &info, GL_CLAMP_TO_EDGE, filter, filter); + } + else + { + textures[0] = pngBindMem(memptr, memsize, PNG_BUILDMIPMAPS, pngType, &info, GL_CLAMP_TO_EDGE, GL_LINEAR_MIPMAP_LINEAR, filter); + } + vf->dropBuf(true); + +#else if (filter == GL_NEAREST) { textures[0] = pngBind(file.c_str(), PNG_NOMIPMAPS, pngType, &info, GL_CLAMP_TO_EDGE, filter, filter); @@ -474,6 +499,7 @@ void Texture::loadPNG(const std::string &file) { textures[0] = pngBind(file.c_str(), PNG_BUILDMIPMAPS, pngType, &info, GL_CLAMP_TO_EDGE, GL_LINEAR_MIPMAP_LINEAR, filter); } +#endif if (textures[0] != 0) { diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a93715..986363a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ ENDIF(APPLE) OPTION(AQUARIA_DEVELOPER_BUILD "Developer Build?" FALSE) OPTION(AQUARIA_DEMO_BUILD "Demo Build?" FALSE) +OPTION(AQUARIA_USE_VFS "Use Virtual File System? Required for some additional features." TRUE) # No Steamworks SDK for Linux at the moment. Roll our own achievements. ADD_DEFINITIONS(-DBBGE_BUILD_ACHIEVEMENTS_INTERNAL=1) @@ -195,6 +196,8 @@ INCLUDE_DIRECTORIES(${LUA_INCLUDE_DIR}) INCLUDE_DIRECTORIES(${OGGVORBIS_INCLUDE_DIRS}) INCLUDE_DIRECTORIES(${SDL_INCLUDE_DIR}) INCLUDE_DIRECTORIES(${OPENAL_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${EXTLIBDIR}) +INCLUDE_DIRECTORIES(${EXTLIBDIR}/ttvfs) # Custom build ID: e.g. "-custom", " (my very own build)" @@ -216,6 +219,9 @@ ADD_DEFINITIONS(-DBBGE_BUILD_FRAMEBUFFER=1) ADD_DEFINITIONS(-DBBGE_BUILD_OPENGL=1) ADD_DEFINITIONS(-DBBGE_BUILD_OPENGL_DYNAMIC=1) ADD_DEFINITIONS(-DBBGE_BUILD_FMOD_OPENAL_BRIDGE=1) +IF(AQUARIA_USE_VFS) + ADD_DEFINITIONS(-DBBGE_BUILD_VFS=1) +ENDIF(AQUARIA_USE_VFS) IF(AQUARIA_DEVELOPER_BUILD) message(STATUS "Developer build.") @@ -246,9 +252,7 @@ ENDIF(MACOSX) IF(WIN32) ADD_DEFINITIONS(-DBBGE_BUILD_WINDOWS=1) - IF(AQUARIA_NO_CONSOLE) - SET(EXETYPE WIN32) - ENDIF(AQUARIA_NO_CONSOLE) + SET(EXETYPE WIN32) SET(OPTIONAL_SRCS ${OPTIONAL_SRCS} aquaria.rc) ENDIF(WIN32) @@ -317,6 +321,8 @@ SET(AQUARIA_SRCS ${SRCDIR}/MiniMapRender.cpp ${SRCDIR}/Mod.cpp ${SRCDIR}/ModSelector.cpp + ${SRCDIR}/ModDownloader.cpp + ${SRCDIR}/Network.cpp ${SRCDIR}/ParticleEditor.cpp ${SRCDIR}/Path.cpp ${SRCDIR}/PathFinding.cpp @@ -371,7 +377,6 @@ SET(BBGE_SRCS ${BBGEDIR}/Cube.cpp ${BBGEDIR}/DarkLayer.cpp ${BBGEDIR}/DebugFont.cpp - ${BBGEDIR}/DeflateCompressor.cpp ${BBGEDIR}/Effects.cpp ${BBGEDIR}/Emitter.cpp ${BBGEDIR}/Event.cpp @@ -410,11 +415,15 @@ SET(BBGE_SRCS ${BBGEDIR}/Vector.cpp ${BBGEDIR}/FmodOpenALBridge.cpp ${COCOA_SRCS} + ${EXTLIBDIR}/DeflateCompressor.cpp + ${EXTLIBDIR}/FileAPI.cpp ${EXTLIBDIR}/glfont2/glfont2.cpp ${EXTLIBDIR}/glpng/glpng.c ${EXTLIBDIR}/tinyxml.cpp ${EXTLIBDIR}/tinyxmlerror.cpp ${EXTLIBDIR}/tinyxmlparser.cpp + ${EXTLIBDIR}/FileAPI.cpp + ${EXTLIBDIR}/minihttp.cpp ) SET(ZLIB_SRCS @@ -603,6 +612,13 @@ SET(LUA_SRCS ${LUASRCDIR}/lmathlib.c ) +IF(AQUARIA_USE_VFS) + ADD_SUBDIRECTORY(${EXTLIBDIR}/ttvfs) + ADD_SUBDIRECTORY(${EXTLIBDIR}/ttvfs_zip) + SET(OPTIONAL_LIBS ${OPTIONAL_LIBS} "ttvfs") + SET(OPTIONAL_LIBS ${OPTIONAL_LIBS} "ttvfs_zip") +ENDIF(AQUARIA_USE_VFS) + IF(MACOSX) SET(OPTIONAL_LIBS ${OPTIONAL_LIBS} "-framework Carbon") diff --git a/ExternalLibs/FileAPI.cpp b/ExternalLibs/FileAPI.cpp new file mode 100644 index 0000000..6e8ae82 --- /dev/null +++ b/ExternalLibs/FileAPI.cpp @@ -0,0 +1,125 @@ +#ifdef BBGE_BUILD_VFS + +#include "FileAPI.h" +#include "ttvfs_zip/VFSZipArchiveLoader.h" +#include + + +ttvfs::VFSHelper vfs; + + +VFILE *vfopen(const char *fn, const char *mode) +{ + if (strchr(mode, 'w')) + { + fprintf(stderr, "FileAPI.h: File writing via VFS not yet supported!"); + return NULL; + } + + VFILE *vf = vfs.GetFile(fn); + if (!vf || !vf->open(mode)) + return NULL; + ++(vf->ref); // keep the file alive until closed. + return vf; +} + +size_t vfread(void *ptr, size_t size, size_t count, VFILE *vf) +{ + return vf->read(ptr, size * count) / size; +} + +int vfclose(VFILE *vf) +{ + bool closed = vf->close(); + vf->ref--; + return closed ? 0 : EOF; +} + +size_t vfwrite(const void *ptr, size_t size, size_t count, VFILE *vf) +{ + return vf->write(ptr, size * count) / size; +} + +// return 0 on success, -1 on error +int vfseek(VFILE *vf, long int offset, int origin) +{ + bool ok = false; + switch(origin) + { + case SEEK_SET: ok = vf->seek(offset); break; + case SEEK_CUR: ok = vf->seekRel(offset); break; + case SEEK_END: ok = vf->seek((long int)(vf->size() - offset)); break; + } + return ok ? 0 : -1; +} + +char *vfgets(char *str, int num, VFILE *vf) +{ + char *s = str; + if (vf->iseof()) + return NULL; + char *ptr = (char*)vf->getBuf() + vf->getpos(); + unsigned int remain = int(vf->size() - vf->getpos()); + if (remain < (unsigned int)num) + num = remain; + else + --num; // be sure to keep space for the final null char + int i = 0; + char c; + for( ; i < num && *ptr; ++i) + { + c = (*s++ = *ptr++); + if(c == '\n' || c == '\r') + { + ++i; + c = *ptr++; // because windows linebreaks suck. + if(c == '\n' || c == '\r') + ++i; + break; + } + } + + vf->seekRel(i); + *s++ = 0; + return str; +} + +void vfclear(VFILE *vf) +{ + vf->dropBuf(true); +} + +long int vftell(VFILE *vf) +{ + return (long int)vf->getpos(); +} + + +InStream::InStream(const std::string& fn) +: std::istringstream() +{ + open(fn.c_str()); +} + +InStream::InStream(const char *fn) +: std::istringstream() +{ + open(fn); +} + +bool InStream::open(const char *fn) +{ + ttvfs::VFSFile *vf = vfs.GetFile(fn); + if(vf) + { + vf->open("r"); + str((char*)vf->getBuf()); // stringstream will always make a copy + vf->close(); + vf->dropBuf(true); + return true; + } + setstate(std::ios_base::failbit); + return false; +} + +#endif diff --git a/ExternalLibs/FileAPI.h b/ExternalLibs/FileAPI.h new file mode 100644 index 0000000..6641c2c --- /dev/null +++ b/ExternalLibs/FileAPI.h @@ -0,0 +1,55 @@ +#ifndef FILE_API_H +#define FILE_API_H + +// TODO: need VFS output functions? + +#ifdef BBGE_BUILD_VFS + +#include "ttvfs/VFS.h" +#include + +extern ttvfs::VFSHelper vfs; +typedef ttvfs::VFSFile VFILE; + + +VFILE *vfopen(const char *fn, const char *mode); +size_t vfread(void *ptr, size_t size, size_t count, VFILE *vf); +int vfclose(VFILE *vf); +size_t vfwrite(const void *ptr, size_t size, size_t count, VFILE *vf); +int vfseek(VFILE *vf, long int offset, int origin); +char *vfgets(char *str, int num, VFILE *vf); +void vfclear(VFILE *vf); +long int vftell(VFILE *vf); + +// This class is a minimal adapter to support STL-like read-only file streams for VFS files, using std::istringstream. +class InStream : public std::istringstream +{ +public: + InStream(const char *fn); + InStream(const std::string& fn); + bool open(const char *fn); + inline bool is_open() { return good(); } + inline void close() {} +private: + void _init(const char *fn); +}; + +#else // BBGE_BUILD_VFS + +#include +#include +typedef std::ifstream InStream; +typedef FILE VFILE; +#define vfopen fopen +#define vfread fread +#define vfclose fclose +#define vfwrite fwrite +#define vfseek fseek +#define vfgets fgets +#define vftell ftell +#define vfclear + +#endif // BBGE_BUILD_VFS + + +#endif // FILE_API_H diff --git a/ExternalLibs/tinyxml.cpp b/ExternalLibs/tinyxml.cpp index a4a6056..e44627c 100644 --- a/ExternalLibs/tinyxml.cpp +++ b/ExternalLibs/tinyxml.cpp @@ -22,6 +22,8 @@ must not be misrepresented as being the original software. distribution. */ +// see tinyxml.h for changes + #include #ifdef TIXML_USE_STL @@ -923,12 +925,12 @@ bool TiXmlDocument::LoadFile( const char* _filename, TiXmlEncoding encoding ) value = filename; // reading in binary mode so that tinyxml can normalize the EOL - FILE* file = TiXmlFOpen( value.c_str (), "rb" ); + VFILE* file = vfopen( value.c_str (), "rb" ); if ( file ) { bool result = LoadFile( file, encoding ); - fclose( file ); + vfclose( file ); return result; } else @@ -938,7 +940,7 @@ bool TiXmlDocument::LoadFile( const char* _filename, TiXmlEncoding encoding ) } } -bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding ) +bool TiXmlDocument::LoadFile( VFILE* file, TiXmlEncoding encoding ) { if ( !file ) { @@ -952,9 +954,13 @@ bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding ) // Get the file size, so we can pre-allocate the string. HUGE speed impact. long length = 0; +#ifdef BBGE_BUILD_VFS + length = file->size(); +#else fseek( file, 0, SEEK_END ); length = ftell( file ); fseek( file, 0, SEEK_SET ); +#endif // Strange case, but good to handle up front. if ( length <= 0 ) @@ -984,14 +990,24 @@ bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding ) } */ +#ifdef BBGE_BUILD_VFS + char *buf = (char*)file->getBuf(); + file->dropBuf(false); + if (!buf) + { + SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } +#else char* buf = new char[ length+1 ]; buf[0] = 0; - if ( fread( buf, length, 1, file ) != 1 ) { + if ( vfread( buf, length, 1, file ) != 1 ) { delete [] buf; SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); return false; } +#endif return LoadMem(buf, length, encoding); } diff --git a/ExternalLibs/tinyxml.h b/ExternalLibs/tinyxml.h index dca5f15..dd0b7a9 100644 --- a/ExternalLibs/tinyxml.h +++ b/ExternalLibs/tinyxml.h @@ -24,6 +24,7 @@ distribution. // EDIT: // - added LoadMem() function +// - added VFS stuff #ifndef TINYXML_INCLUDED @@ -35,11 +36,13 @@ distribution. #pragma warning( disable : 4786 ) #endif +#include "FileAPI.h" + #include -#include #include #include #include +#include // Help out windows: #if defined( _DEBUG ) && !defined( DEBUG ) @@ -1420,7 +1423,7 @@ public: will be interpreted as an XML file. TinyXML doesn't stream in XML from the current file location. Streaming may be added in the future. */ - bool LoadFile( FILE*, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + bool LoadFile( VFILE*, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); /// Save a file using the given FILE*. Returns true if successful. bool SaveFile( FILE* ) const;