mirror of
https://github.com/TerryCavanagh/VVVVVV.git
synced 2024-06-26 06:28:30 +02:00
efbaeeffde
If there's a resource that doesn't otherwise need to be cleaned up and is still alive upon program shutdown, then it should go in cleanup(). This cleans up Screen, GraphicsResources, Graphics buffers, Graphics tiles, and musicclass audio upon program shutdown. Even we technically don't NEED to clean these resources up ourselves (the kernel is going to get rid of all of it anyway, else it'd be a security problem), I'm doing this because otherwise Valgrind will complain about these, and then it'd be difficult to see which memory leaks are real and which are just "well this isn't really a leak but you haven't freed this thing when the process exited, and that's technically what a memory leak is". These are all resources whose cleanup functions can be safely called even if they haven't initialized anything yet.
662 lines
16 KiB
C++
662 lines
16 KiB
C++
#include <SDL.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "editor.h"
|
|
#include "Enums.h"
|
|
#include "Entity.h"
|
|
#include "FileSystemUtils.h"
|
|
#include "Game.h"
|
|
#include "Graphics.h"
|
|
#include "Input.h"
|
|
#include "KeyPoll.h"
|
|
#include "Logic.h"
|
|
#include "Map.h"
|
|
#include "Music.h"
|
|
#include "Network.h"
|
|
#include "preloader.h"
|
|
#include "Render.h"
|
|
#include "RenderFixed.h"
|
|
#include "Screen.h"
|
|
#include "Script.h"
|
|
#include "SoundSystem.h"
|
|
#include "UtilityClass.h"
|
|
|
|
scriptclass script;
|
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
|
std::vector<edentities> edentity;
|
|
editorclass ed;
|
|
#endif
|
|
|
|
UtilityClass help;
|
|
Graphics graphics;
|
|
musicclass music;
|
|
Game game;
|
|
KeyPoll key;
|
|
mapclass map;
|
|
entityclass obj;
|
|
Screen gameScreen;
|
|
|
|
static bool startinplaytest = false;
|
|
static bool savefileplaytest = false;
|
|
static int savex = 0;
|
|
static int savey = 0;
|
|
static int saverx = 0;
|
|
static int savery = 0;
|
|
static int savegc = 0;
|
|
static int savemusic = 0;
|
|
static std::string playassets;
|
|
|
|
static std::string playtestname;
|
|
|
|
static volatile Uint32 time_ = 0;
|
|
static volatile Uint32 timePrev = 0;
|
|
static volatile Uint32 accumulator = 0;
|
|
static volatile Uint32 f_time = 0;
|
|
static volatile Uint32 f_timePrev = 0;
|
|
|
|
static inline Uint32 get_framerate(const int slowdown)
|
|
{
|
|
switch (slowdown)
|
|
{
|
|
case 30:
|
|
return 34;
|
|
case 24:
|
|
return 41;
|
|
case 18:
|
|
return 55;
|
|
case 12:
|
|
return 83;
|
|
}
|
|
|
|
return 34;
|
|
}
|
|
|
|
static void inline deltaloop();
|
|
static void inline fixedloop();
|
|
|
|
static void cleanup();
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
char* baseDir = NULL;
|
|
char* assetsPath = NULL;
|
|
|
|
for (int i = 1; i < argc; ++i)
|
|
{
|
|
#define ARG(name) (SDL_strcmp(argv[i], name) == 0)
|
|
#define ARG_INNER(code) \
|
|
if (i + 1 < argc) \
|
|
{ \
|
|
code \
|
|
} \
|
|
else \
|
|
{ \
|
|
printf("%s option requires one argument.\n", argv[i]); \
|
|
return 1; \
|
|
}
|
|
|
|
if (ARG("-renderer"))
|
|
{
|
|
ARG_INNER({
|
|
i++;
|
|
SDL_SetHintWithPriority(SDL_HINT_RENDER_DRIVER, argv[i], SDL_HINT_OVERRIDE);
|
|
})
|
|
}
|
|
else if (ARG("-basedir"))
|
|
{
|
|
ARG_INNER({
|
|
i++;
|
|
baseDir = argv[i];
|
|
})
|
|
}
|
|
else if (ARG("-assets"))
|
|
{
|
|
ARG_INNER({
|
|
i++;
|
|
assetsPath = argv[i];
|
|
})
|
|
}
|
|
else if (ARG("-playing") || ARG("-p"))
|
|
{
|
|
ARG_INNER({
|
|
i++;
|
|
startinplaytest = true;
|
|
playtestname = std::string("levels/");
|
|
playtestname.append(argv[i]);
|
|
playtestname.append(std::string(".vvvvvv"));
|
|
})
|
|
}
|
|
else if (ARG("-playx") || ARG("-playy") ||
|
|
ARG("-playrx") || ARG("-playry") ||
|
|
ARG("-playgc") || ARG("-playmusic"))
|
|
{
|
|
ARG_INNER({
|
|
savefileplaytest = true;
|
|
int v = help.Int(argv[i+1]);
|
|
if (ARG("-playx")) savex = v;
|
|
else if (ARG("-playy")) savey = v;
|
|
else if (ARG("-playrx")) saverx = v;
|
|
else if (ARG("-playry")) savery = v;
|
|
else if (ARG("-playgc")) savegc = v;
|
|
else if (ARG("-playmusic")) savemusic = v;
|
|
i++;
|
|
})
|
|
}
|
|
else if (ARG("-playassets"))
|
|
{
|
|
ARG_INNER({
|
|
i++;
|
|
// Even if this is a directory, FILESYSTEM_mountassets() expects '.vvvvvv' on the end
|
|
playassets = "levels/" + std::string(argv[i]) + ".vvvvvv";
|
|
})
|
|
}
|
|
#undef ARG_INNER
|
|
#undef ARG
|
|
else
|
|
{
|
|
printf("Error: invalid option: %s\n", argv[i]);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if(!FILESYSTEM_init(argv[0], baseDir, assetsPath))
|
|
{
|
|
puts("Unable to initialize filesystem!");
|
|
return 1;
|
|
}
|
|
|
|
SDL_Init(
|
|
SDL_INIT_VIDEO |
|
|
SDL_INIT_AUDIO |
|
|
SDL_INIT_JOYSTICK |
|
|
SDL_INIT_GAMECONTROLLER
|
|
);
|
|
if (SDL_IsTextInputActive() == SDL_TRUE)
|
|
{
|
|
SDL_StopTextInput();
|
|
}
|
|
|
|
NETWORK_init();
|
|
|
|
printf("\t\t\n");
|
|
printf("\t\t\n");
|
|
printf("\t\t VVVVVV\n");
|
|
printf("\t\t\n");
|
|
printf("\t\t\n");
|
|
printf("\t\t 8888888888888888 \n");
|
|
printf("\t\t88888888888888888888\n");
|
|
printf("\t\t888888 8888 88\n");
|
|
printf("\t\t888888 8888 88\n");
|
|
printf("\t\t88888888888888888888\n");
|
|
printf("\t\t88888888888888888888\n");
|
|
printf("\t\t888888 88\n");
|
|
printf("\t\t88888888 8888\n");
|
|
printf("\t\t 8888888888888888 \n");
|
|
printf("\t\t 88888888 \n");
|
|
printf("\t\t 8888888888888888 \n");
|
|
printf("\t\t88888888888888888888\n");
|
|
printf("\t\t88888888888888888888\n");
|
|
printf("\t\t88888888888888888888\n");
|
|
printf("\t\t8888 88888888 8888\n");
|
|
printf("\t\t8888 88888888 8888\n");
|
|
printf("\t\t 888888888888 \n");
|
|
printf("\t\t 8888 8888 \n");
|
|
printf("\t\t 888888 888888 \n");
|
|
printf("\t\t 888888 888888 \n");
|
|
printf("\t\t 888888 888888 \n");
|
|
printf("\t\t\n");
|
|
printf("\t\t\n");
|
|
|
|
//Set up screen
|
|
|
|
|
|
|
|
|
|
// Load Ini
|
|
|
|
|
|
graphics.init();
|
|
|
|
game.init();
|
|
|
|
// This loads music too...
|
|
graphics.reloadresources();
|
|
|
|
game.gamestate = PRELOADER;
|
|
|
|
game.menustart = false;
|
|
game.mainmenu = 0;
|
|
|
|
// Initialize title screen to cyan
|
|
graphics.titlebg.colstate = 10;
|
|
map.nexttowercolour();
|
|
|
|
map.ypos = (700-29) * 8;
|
|
graphics.towerbg.bypos = map.ypos / 2;
|
|
graphics.titlebg.bypos = map.ypos / 2;
|
|
|
|
{
|
|
// Prioritize unlock.vvv first (2.2 and below),
|
|
// but settings have been migrated to settings.vvv (2.3 and up)
|
|
ScreenSettings screen_settings;
|
|
game.loadstats(&screen_settings);
|
|
game.loadsettings(&screen_settings);
|
|
gameScreen.init(screen_settings);
|
|
}
|
|
graphics.screenbuffer = &gameScreen;
|
|
|
|
graphics.create_buffers(gameScreen.GetFormat());
|
|
|
|
if (game.skipfakeload)
|
|
game.gamestate = TITLEMODE;
|
|
if (game.slowdown == 0) game.slowdown = 30;
|
|
|
|
//Check to see if you've already unlocked some achievements here from before the update
|
|
if (game.swnbestrank > 0){
|
|
if(game.swnbestrank >= 1) game.unlockAchievement("vvvvvvsupgrav5");
|
|
if(game.swnbestrank >= 2) game.unlockAchievement("vvvvvvsupgrav10");
|
|
if(game.swnbestrank >= 3) game.unlockAchievement("vvvvvvsupgrav15");
|
|
if(game.swnbestrank >= 4) game.unlockAchievement("vvvvvvsupgrav20");
|
|
if(game.swnbestrank >= 5) game.unlockAchievement("vvvvvvsupgrav30");
|
|
if(game.swnbestrank >= 6) game.unlockAchievement("vvvvvvsupgrav60");
|
|
}
|
|
|
|
if(game.unlock[5]) game.unlockAchievement("vvvvvvgamecomplete");
|
|
if(game.unlock[19]) game.unlockAchievement("vvvvvvgamecompleteflip");
|
|
if(game.unlock[20]) game.unlockAchievement("vvvvvvmaster");
|
|
|
|
if (game.bestgamedeaths > -1) {
|
|
if (game.bestgamedeaths <= 500) {
|
|
game.unlockAchievement("vvvvvvcomplete500");
|
|
}
|
|
if (game.bestgamedeaths <= 250) {
|
|
game.unlockAchievement("vvvvvvcomplete250");
|
|
}
|
|
if (game.bestgamedeaths <= 100) {
|
|
game.unlockAchievement("vvvvvvcomplete100");
|
|
}
|
|
if (game.bestgamedeaths <= 50) {
|
|
game.unlockAchievement("vvvvvvcomplete50");
|
|
}
|
|
}
|
|
|
|
if(game.bestrank[0]>=3) game.unlockAchievement("vvvvvvtimetrial_station1_fixed");
|
|
if(game.bestrank[1]>=3) game.unlockAchievement("vvvvvvtimetrial_lab_fixed");
|
|
if(game.bestrank[2]>=3) game.unlockAchievement("vvvvvvtimetrial_tower_fixed");
|
|
if(game.bestrank[3]>=3) game.unlockAchievement("vvvvvvtimetrial_station2_fixed");
|
|
if(game.bestrank[4]>=3) game.unlockAchievement("vvvvvvtimetrial_warp_fixed");
|
|
if(game.bestrank[5]>=3) game.unlockAchievement("vvvvvvtimetrial_final_fixed");
|
|
|
|
obj.init();
|
|
|
|
#if !defined(NO_CUSTOM_LEVELS)
|
|
if (startinplaytest) {
|
|
game.levelpage = 0;
|
|
game.playcustomlevel = 0;
|
|
game.playassets = playassets;
|
|
game.menustart = true;
|
|
|
|
ed.directoryList.clear();
|
|
ed.directoryList.push_back(playtestname);
|
|
|
|
LevelMetaData meta;
|
|
if (ed.getLevelMetaData(playtestname, meta)) {
|
|
ed.ListOfMetaData.clear();
|
|
ed.ListOfMetaData.push_back(meta);
|
|
} else {
|
|
ed.loadZips();
|
|
if (ed.getLevelMetaData(playtestname, meta)) {
|
|
ed.ListOfMetaData.clear();
|
|
ed.ListOfMetaData.push_back(meta);
|
|
} else {
|
|
printf("Level not found\n");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
game.loadcustomlevelstats();
|
|
game.customleveltitle=ed.ListOfMetaData[game.playcustomlevel].title;
|
|
game.customlevelfilename=ed.ListOfMetaData[game.playcustomlevel].filename;
|
|
if (savefileplaytest) {
|
|
game.playx = savex;
|
|
game.playy = savey;
|
|
game.playrx = saverx;
|
|
game.playry = savery;
|
|
game.playgc = savegc;
|
|
game.playmusic = savemusic;
|
|
game.cliplaytest = true;
|
|
script.startgamemode(23);
|
|
} else {
|
|
script.startgamemode(22);
|
|
}
|
|
|
|
graphics.fademode = 0;
|
|
}
|
|
#endif
|
|
|
|
key.isActive = true;
|
|
game.gametimer = 0;
|
|
|
|
while(!key.quitProgram)
|
|
{
|
|
f_time = SDL_GetTicks();
|
|
|
|
const Uint32 f_timetaken = f_time - f_timePrev;
|
|
if (!game.over30mode && f_timetaken < 34)
|
|
{
|
|
const volatile Uint32 f_delay = 34 - f_timetaken;
|
|
SDL_Delay(f_delay);
|
|
f_time = SDL_GetTicks();
|
|
}
|
|
|
|
f_timePrev = f_time;
|
|
|
|
timePrev = time_;
|
|
time_ = SDL_GetTicks();
|
|
|
|
deltaloop();
|
|
}
|
|
|
|
cleanup();
|
|
return 0;
|
|
}
|
|
|
|
static void cleanup()
|
|
{
|
|
/* Order matters! */
|
|
game.savestatsandsettings();
|
|
gameScreen.destroy();
|
|
graphics.grphx.destroy();
|
|
graphics.destroy_buffers();
|
|
graphics.destroy();
|
|
music.destroy();
|
|
NETWORK_shutdown();
|
|
SDL_Quit();
|
|
FILESYSTEM_deinit();
|
|
}
|
|
|
|
static void inline deltaloop()
|
|
{
|
|
//timestep limit to 30
|
|
const float rawdeltatime = static_cast<float>(time_ - timePrev);
|
|
accumulator += rawdeltatime;
|
|
|
|
Uint32 timesteplimit;
|
|
if (game.gamestate == EDITORMODE)
|
|
{
|
|
timesteplimit = 24;
|
|
}
|
|
else if (game.gamestate == GAMEMODE)
|
|
{
|
|
timesteplimit = get_framerate(game.slowdown);
|
|
}
|
|
else
|
|
{
|
|
timesteplimit = 34;
|
|
}
|
|
|
|
while (accumulator >= timesteplimit)
|
|
{
|
|
accumulator = SDL_fmodf(accumulator, timesteplimit);
|
|
|
|
fixedloop();
|
|
}
|
|
const float alpha = game.over30mode ? static_cast<float>(accumulator) / timesteplimit : 1.0f;
|
|
graphics.alpha = alpha;
|
|
|
|
if (key.isActive)
|
|
{
|
|
switch (game.gamestate)
|
|
{
|
|
case PRELOADER:
|
|
preloaderrender();
|
|
break;
|
|
#if !defined(NO_CUSTOM_LEVELS) && !defined(NO_EDITOR)
|
|
case EDITORMODE:
|
|
graphics.flipmode = false;
|
|
editorrender();
|
|
break;
|
|
#endif
|
|
case TITLEMODE:
|
|
titlerender();
|
|
break;
|
|
case GAMEMODE:
|
|
gamerender();
|
|
break;
|
|
case MAPMODE:
|
|
maprender();
|
|
break;
|
|
case TELEPORTERMODE:
|
|
teleporterrender();
|
|
break;
|
|
case GAMECOMPLETE:
|
|
gamecompleterender();
|
|
break;
|
|
case GAMECOMPLETE2:
|
|
gamecompleterender2();
|
|
break;
|
|
case CLICKTOSTART:
|
|
help.updateglow();
|
|
break;
|
|
}
|
|
gameScreen.FlipScreen();
|
|
}
|
|
}
|
|
|
|
static void inline fixedloop()
|
|
{
|
|
// Update network per frame.
|
|
NETWORK_update();
|
|
|
|
key.Poll();
|
|
if(key.toggleFullscreen)
|
|
{
|
|
gameScreen.toggleFullScreen();
|
|
key.toggleFullscreen = false;
|
|
|
|
key.keymap.clear(); //we lost the input due to a new window.
|
|
if (game.glitchrunnermode)
|
|
{
|
|
game.press_left = false;
|
|
game.press_right = false;
|
|
game.press_action = true;
|
|
game.press_map = false;
|
|
}
|
|
}
|
|
|
|
if(!key.isActive)
|
|
{
|
|
Mix_Pause(-1);
|
|
Mix_PauseMusic();
|
|
|
|
if (!game.blackout)
|
|
{
|
|
FillRect(graphics.backBuffer, 0x00000000);
|
|
graphics.bprint(5, 110, "Game paused", 196 - help.glow, 255 - help.glow, 196 - help.glow, true);
|
|
graphics.bprint(5, 120, "[click to resume]", 196 - help.glow, 255 - help.glow, 196 - help.glow, true);
|
|
graphics.bprint(5, 220, "Press M to mute in game", 164 - help.glow, 196 - help.glow, 164 - help.glow, true);
|
|
graphics.bprint(5, 230, "Press N to mute music only", 164 - help.glow, 196 - help.glow, 164 - help.glow, true);
|
|
}
|
|
graphics.render();
|
|
gameScreen.FlipScreen();
|
|
//We are minimised, so lets put a bit of a delay to save CPU
|
|
SDL_Delay(100);
|
|
}
|
|
else
|
|
{
|
|
Mix_Resume(-1);
|
|
Mix_ResumeMusic();
|
|
game.gametimer++;
|
|
graphics.cutscenebarstimer();
|
|
|
|
switch(game.gamestate)
|
|
{
|
|
case PRELOADER:
|
|
preloaderinput();
|
|
preloaderrenderfixed();
|
|
break;
|
|
#if !defined(NO_CUSTOM_LEVELS) && !defined(NO_EDITOR)
|
|
case EDITORMODE:
|
|
//Input
|
|
editorinput();
|
|
////Logic
|
|
editorlogic();
|
|
|
|
editorrenderfixed();
|
|
break;
|
|
#endif
|
|
case TITLEMODE:
|
|
//Input
|
|
titleinput();
|
|
////Logic
|
|
titlelogic();
|
|
|
|
titlerenderfixed();
|
|
break;
|
|
case GAMEMODE:
|
|
// WARNING: If updating this code, don't forget to update Map.cpp mapclass::twoframedelayfix()
|
|
|
|
// Ugh, I hate this kludge variable but it's the only way to do it
|
|
if (script.dontrunnextframe)
|
|
{
|
|
script.dontrunnextframe = false;
|
|
}
|
|
else
|
|
{
|
|
script.run();
|
|
}
|
|
|
|
//Update old lerp positions of entities - has to be done BEFORE gameinput!
|
|
for (size_t i = 0; i < obj.entities.size(); i++)
|
|
{
|
|
obj.entities[i].lerpoldxp = obj.entities[i].xp;
|
|
obj.entities[i].lerpoldyp = obj.entities[i].yp;
|
|
}
|
|
|
|
gameinput();
|
|
gamerenderfixed();
|
|
gamelogic();
|
|
|
|
|
|
break;
|
|
case MAPMODE:
|
|
mapinput();
|
|
maplogic();
|
|
maprenderfixed();
|
|
break;
|
|
case TELEPORTERMODE:
|
|
if(game.useteleporter)
|
|
{
|
|
teleporterinput();
|
|
}
|
|
else
|
|
{
|
|
script.run();
|
|
gameinput();
|
|
}
|
|
maplogic();
|
|
maprenderfixed();
|
|
break;
|
|
case GAMECOMPLETE:
|
|
//Input
|
|
gamecompleteinput();
|
|
//Logic
|
|
gamecompletelogic();
|
|
|
|
gamecompleterenderfixed();
|
|
break;
|
|
case GAMECOMPLETE2:
|
|
//Input
|
|
gamecompleteinput2();
|
|
//Logic
|
|
gamecompletelogic2();
|
|
break;
|
|
case CLICKTOSTART:
|
|
break;
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//Screen effects timers
|
|
if (key.isActive && game.flashlight > 0)
|
|
{
|
|
game.flashlight--;
|
|
}
|
|
if (key.isActive && game.screenshake > 0)
|
|
{
|
|
game.screenshake--;
|
|
graphics.updatescreenshake();
|
|
}
|
|
|
|
if (graphics.screenbuffer->badSignalEffect)
|
|
{
|
|
UpdateFilter();
|
|
}
|
|
|
|
//We did editorinput, now it's safe to turn this off
|
|
key.linealreadyemptykludge = false;
|
|
|
|
//Mute button
|
|
if (key.isDown(KEYBOARD_m) && game.mutebutton<=0 && !key.textentry())
|
|
{
|
|
game.mutebutton = 8;
|
|
if (game.muted)
|
|
{
|
|
game.muted = false;
|
|
}
|
|
else
|
|
{
|
|
game.muted = true;
|
|
}
|
|
}
|
|
if(game.mutebutton>0)
|
|
{
|
|
game.mutebutton--;
|
|
}
|
|
|
|
if (key.isDown(KEYBOARD_n) && game.musicmutebutton <= 0 && !key.textentry())
|
|
{
|
|
game.musicmutebutton = 8;
|
|
game.musicmuted = !game.musicmuted;
|
|
}
|
|
if (game.musicmutebutton > 0)
|
|
{
|
|
game.musicmutebutton--;
|
|
}
|
|
|
|
if (game.muted)
|
|
{
|
|
Mix_VolumeMusic(0) ;
|
|
Mix_Volume(-1,0);
|
|
}
|
|
else
|
|
{
|
|
Mix_Volume(-1,MIX_MAX_VOLUME);
|
|
|
|
if (game.musicmuted)
|
|
{
|
|
Mix_VolumeMusic(0);
|
|
}
|
|
else
|
|
{
|
|
Mix_VolumeMusic(music.musicVolume);
|
|
}
|
|
}
|
|
|
|
if (key.resetWindow)
|
|
{
|
|
key.resetWindow = false;
|
|
gameScreen.ResizeScreen(-1, -1);
|
|
}
|
|
|
|
music.processmusic();
|
|
graphics.processfade();
|
|
game.gameclock();
|
|
}
|