diff --git a/.gitignore b/.gitignore index 9f96a79..3bb5150 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ log.txt +test_log.txt Spylike-v* lib/bin lib/include \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 424e246..7e40f51 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/PDCurses"] path = lib/PDCurses url = https://github.com/wmcbrine/PDCurses.git +[submodule "lib/miniaudio"] + path = lib/miniaudio + url = https://github.com/mackron/miniaudio diff --git a/Makefile b/Makefile index 6a97e07..9a75c41 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CXX=g++ -CPPFLAGS=-std=c++11 -Iinclude +CPPFLAGS=-std=c++2a -Iinclude -Igame/include -Ilib/include -DMA_NO_PULSEAUDIO LDLIBS= -OBJS=graphics/*.cpp logging/*.cpp models/*.cpp level/*.cpp game/*.cpp game/UI/*.cpp main.cpp +OBJS=graphics/*.cpp logging/*.cpp models/*.cpp level/*.cpp audio/*.cpp game/*.cpp game/entities/*.cpp game/UI/*.cpp util/*.cpp main.cpp VER=vA1 ifndef PDCURSES_BACKEND @@ -14,15 +14,15 @@ ifndef PDCURSES_BACKEND USE_NCURSES=1 endif PDCURSES_BACKEND=x11 + LDLIBS+= -lpthread -lm -ldl endif endif endif ifeq ($(USE_NCURSES), 1) - LDLIBS+= -lncurses + LDLIBS+= -lncursesw else LDLIBS+= -Llib/bin -lpdcurses - CPPFLAGS+= -Ilib/include endif ifndef PDCURSES_BACKEND @@ -31,9 +31,10 @@ endif build: build-pdcurses build: $(OBJS) + cp lib/miniaudio/miniaudio.h lib/include/miniaudio.h $(CXX) $(CPPFLAGS) -o Spylike-$(VER) $(OBJS) $(LDLIBS) -debug: CPPFLAGS+= -g +debug: CPPFLAGS+= -g -O0 debug: build build-pdcurses: @@ -45,7 +46,7 @@ else # BEGIN PDCURSES BUILD BLOCK cp lib/PDCurses/curses.h lib/include/curses.h cp lib/PDCurses/panel.h lib/include/panel.h ifeq ($(PDCURSES_BACKEND), wincon) - cd lib/PDCurses/wincon && $(MAKE) + cd lib/PDCurses/wincon && $(MAKE) UTF8=Y WIDE=Y cp lib/PDCurses/wincon/pdcurses.a lib/bin/libpdcurses.a else ifeq ($(PDCURSES_BACKEND), x11) cd lib/PDCurses/x11 && ./configure diff --git a/Spylike-TestSuite-vA1.exe b/Spylike-TestSuite-vA1.exe new file mode 100644 index 0000000..00cace7 Binary files /dev/null and b/Spylike-TestSuite-vA1.exe differ diff --git a/audio/audio.cpp b/audio/audio.cpp new file mode 100644 index 0000000..8236e39 --- /dev/null +++ b/audio/audio.cpp @@ -0,0 +1,67 @@ +#define MINIAUDIO_IMPLEMENTATION +#include "logger.h" +#include "miniaudio.h" + +#include "audio.h" + +#include + +extern SpylikeLogger LOGGER; + +void AudioManager::on_event(Event& e) { + if (e.type == "AUDIO_PlayMusic") { + SpylikeEvents::AudioPlayEvent& ap = dynamic_cast(e); + playMusic(ap.sound, ap.volume); + } + else if (e.type == "AUDIO_PauseMusic") { + pauseMusic(); + } +} + +MiniaudioManager::MiniaudioManager(std::string rootPath) : rootPath{rootPath} { + LOGGER.log("Initalizing audio manager", DEBUG); + engine = new ma_engine; + ma_result result = ma_engine_init(NULL, engine); + if (result != MA_SUCCESS) throw "Error initalizing audio engine."; +} + +void MiniaudioManager::playMusic(std::string track, float volume) { + LOGGER.log("Playing music " + track, DEBUG); + if (playing) stopMusic(); + playing = true; + cMusic = new ma_sound; + std::string path = rootPath + "music/" + track; + const char* path_str = path.c_str(); + ma_result res = ma_sound_init_from_file(engine, path_str, 0, NULL, NULL, cMusic); + ma_sound_set_volume(cMusic, volume); + ma_sound_set_looping(cMusic, true); + ma_sound_start(cMusic); +} + +void MiniaudioManager::playSound(std::string sound, float volume) { + const char* path = (rootPath + "sound/" + sound).c_str(); + ma_engine_play_sound(engine, path, NULL); +} + +void MiniaudioManager::stopMusic() { + LOGGER.log("Stopping music", DEBUG); + if (cMusic && engine && playing) { + ma_sound_uninit(cMusic); + delete cMusic; + } + cMusic = nullptr; + playing = false; +} + +void MiniaudioManager::pauseMusic() { + LOGGER.log("Pausing music", DEBUG); + if (cMusic) ma_sound_stop(cMusic); +} + +void MiniaudioManager::resumeMusic() { + if (cMusic) ma_sound_start(cMusic); +} + +void MiniaudioManager::setMusicVolume(float volume) { + if (cMusic) ma_sound_set_volume(cMusic, volume); +} diff --git a/events.txt b/events.txt new file mode 100644 index 0000000..ab9a76e --- /dev/null +++ b/events.txt @@ -0,0 +1,27 @@ +STANDARD EVENT TYPES + +Input: +INPUT_KeyPress : Of type KeyInputEvent. For any keypress, emitted by InputManager. + +Graphics: +CAMERA_MoveUp: Moves the camera up by one unit. (CameraEvent) +CAMERA_MoveDown: Moves the camera down by one unit. (CameraEvent) +CAMERA_MoveRight: Moves the camera right by one unit. (CameraEvent) +CAMERA_MoveLeft: Moves the camera left by one unit. (CameraEvent) +CAMERA_Move: Moves the camera to pos specified in CameraEvent.pos. + +Audio: +AUDIO_PlayMusic: Plays the music with filepath [rootPath]/AudioPlayEvent.sound and volume AudioPlayEvent.volume +AUDIO_PauseMusic: Pauses any currently playing music + +Game: +GAME_PlayerDeath: Emitted when the Player reaches 0 health (or some other death-causing event) +GAME_PlayerHurt: Emitted when the player is hurt, PlayerHurtEvent.health gives *current* health (after hurt) +MENU_Show: Shows the menu with id specified in MenuEvent.menuID +MENU_Close: Closes any active menus +MENU_ButtonClick: Calls when any button is clicked, ID given in MenuButtonEvent.buttonID + Standard button ID behaviors: Buttons with id "close" close the menu +LEVEL_change: Changes the level to LevelChangeEvent.levelPath +GAME_KeyCollect: The key for the final boss door was collected. +GAME_DoorRequest: Event for the door to check whether it can be opened. +GAME_DoorResponse: Response from the game manager about GAME_DoorRequest, result in bool DoorResponse.result. \ No newline at end of file diff --git a/game/UI/menus.cpp b/game/UI/menus.cpp index 81eb5c9..44938a4 100644 --- a/game/UI/menus.cpp +++ b/game/UI/menus.cpp @@ -2,15 +2,17 @@ #include "rendering.h" #include "event.h" #include "logger.h" +#include #include #include +#include extern SpylikeLogger LOGGER; -MenuButton::MenuButton(int width, int height, std::string buttonText, std::string buttonID) : width{width}, height{height}, text{buttonText}, buttonID{buttonID} {} +MenuButton::MenuButton(Coordinate pos, int width, int height, std::string buttonText, std::string buttonID) : pos{pos}, width{width}, height{height}, text{buttonText}, buttonID{buttonID} {} void MenuButton::click() { - auto ev = SpylikeEvents::MenuButtonEvent("ButtonClickEvent", buttonID); + auto ev = SpylikeEvents::MenuButtonEvent("MENU_ButtonClick", buttonID); eventManager->emit(ev); } @@ -25,11 +27,11 @@ void MenuButton::draw(GeometryRenderer& painter) { drawnText = text.substr(0, width - 5) + "..."; } } - Coordinate topLeft = Coordinate(tile->pos.x, tile->pos.y); - Coordinate bottomRight = Coordinate(tile->pos.x + width - 1, tile->pos.y + height - 1); + Coordinate topLeft = pos; + Coordinate bottomRight = Coordinate(pos.x + width - 1, pos.y + height - 1); painter.drawBox(topLeft, bottomRight, "UI"); - Coordinate drawOffset = Coordinate(width/2 - drawnText.length()/2 + tile->pos.x, - height/2 + tile->pos.y); + Coordinate drawOffset = Coordinate(width/2 - drawnText.length()/2 + pos.x, + height/2 + pos.y); painter.drawString(drawOffset, drawnText, "UI"); } @@ -41,19 +43,51 @@ void MenuButton::on_event(Event& e) {} void MenuButton::on_update() {} +void Menu::draw(GeometryRenderer& painter) { + Coordinate selectionPos = buttons.find(currentSelection)->second.pos; + Coordinate arrowPos = Coordinate(selectionPos.x + buttons.find(currentSelection)->second.width/2, selectionPos.y - 1); + painter.draw(arrowPos, 'v', "UI"); + painter.draw(Coordinate(arrowPos.x, arrowPos.y-1), '|', "UI"); + for (auto& buttonPair : buttons) { + buttonPair.second.draw(painter); + } +} + +void PauseMenu::draw(GeometryRenderer& painter) { + /* + painter.drawString(Coordinate(1, 1), "" + "|||\n" +"| |\n" +"| | || | | ||||| |||||||\n" +"|||| | | | | | | \n" +"| |||||| | | ||||| |||||||\n" +"| | | | | | | \n" +"| | | |||||| ||||| |||||||\n", "UI"); +*/ + painter.drawString(Coordinate(painter.getScreenWidth()/2-5, 1), "PAUSED!", "UI"); + Menu::draw(painter); +} + +void GameOverMenu::draw(GeometryRenderer& painter) { + painter.drawString(Coordinate(painter.getScreenWidth()/2-8, 1), "GAME OVER!", "UI"); + Menu::draw(painter); +} + +void StartMenu::draw(GeometryRenderer& painter) { + painter.drawString(Coordinate(painter.getScreenWidth()/2-12, 1), "WELCOME TO SPYLIKE!", "UI"); + painter.drawString(Coordinate(painter.getScreenWidth()/2-7, 2), "CONTROLS:", "UI"); + painter.drawString(Coordinate(painter.getScreenWidth()/2-10, 3), "W,A,S,D MOVEMENT", "UI"); + painter.drawString(Coordinate(painter.getScreenWidth()/2-9, 4), "V,B,N,G ATTACK", "UI"); + painter.drawString(Coordinate(painter.getScreenWidth()/2-9, 5), "ESC PAUSE", "UI"); + Menu::draw(painter); +} -void Menu::draw(GeometryRenderer& painter) {} // pos is *relative* to menu -void Menu::addButton(std::shared_ptr button, Coordinate pos) { - if (!tile) { - throw std::runtime_error("Menu must be registered before adding buttons"); - } - buttons.insert({button->getButtonID(), button}); - selectionList.push(button->getButtonID()); - world->registerEntity(button, tile->pos + pos); - addChild(button); - currentSelection = button->getButtonID(); +void Menu::addButton(MenuButton button) { + buttons.insert({button.getButtonID(), button}); + selectionList.push(button.getButtonID()); + selectNext(); } void Menu::setSelection(std::string buttonID) { @@ -70,10 +104,73 @@ void Menu::selectNext() { void Menu::click() { if (currentSelection != "") { - buttons[currentSelection]->click(); + buttons.find(currentSelection)->second.click(); } } -void Menu::on_event(Event& e) {} +void Menu::on_event(Event& e) { + if (e.type == "INPUT_KeyPress") { + SpylikeEvents::KeyInputEvent& ke = dynamic_cast(e); + if (ke.c == ' ' || ke.c == KEY_ENTER) { + click(); + } + else if (ke.c == 'w' || ke.c == 'a' || ke.c == 's' || ke.c == 'd') { + selectNext(); + } + } +} + +void Menu::on_update() { + for (auto& buttonPair : buttons) { + buttonPair.second.update(); + } +} + +// must set ID before initalization +void Menu::on_init() { + assert(ID != -1); + int nextID = ID+1; + for (auto& buttonPair : buttons) { + buttonPair.second.setID(nextID); + nextID++; + buttonPair.second.init(eventManager); + } +} + +namespace SpylikeMenus { + std::shared_ptr testMenu() { + Menu menu(80, 40); + MenuButton button(Coordinate(4, 4), 10, 5, "hello!", "close"); + MenuButton button2(Coordinate(25, 4), 10, 5, "world!", "testButton2"); + menu.addButton(button); + menu.addButton(button2); + return std::make_shared(menu); + } + std::shared_ptr pauseMenu() { + PauseMenu menu(80, 40); + MenuButton button(Coordinate(12, 10), 15, 5, "Resume", "close"); + MenuButton button2(Coordinate(45, 10), 15, 5, "Quit", "quit"); + menu.addButton(button); + menu.addButton(button2); + return std::make_shared(menu); + } + std::shared_ptr gameOver() { + GameOverMenu menu(80, 40); + MenuButton button(Coordinate(12, 10), 15, 5, "Start over", "restart"); + MenuButton button2(Coordinate(45, 10), 15, 5, "Quit", "quit"); + menu.addButton(button); + menu.addButton(button2); + return std::make_shared(menu); + } + std::shared_ptr startMenu() { + StartMenu menu(80, 40); + MenuButton button(Coordinate(12, 10), 15, 5, "Start game", "restart"); + MenuButton button2(Coordinate(45, 10), 15, 5, "Quit", "quit"); + menu.addButton(button); + menu.addButton(button2); + return std::make_shared(menu); + } + +} -void Menu::on_update() {} + diff --git a/game/entities/character.cpp b/game/entities/character.cpp new file mode 100644 index 0000000..ee981ea --- /dev/null +++ b/game/entities/character.cpp @@ -0,0 +1,436 @@ +#include "character.h" +#include "rendering.h" +#include "logger.h" +#include "event.h" +#include +#include +#include + +extern SpylikeLogger LOGGER; + +void Player::on_init() { + eventManager->subscribe(shared_from_this(), "INPUT_KeyPress"); + SpylikeEvents::CameraEvent ce("CAMERA_Move", getPos()); + eventManager->emit(ce); +} + +void Player::on_event(Event& e) { + if (e.type == "INPUT_KeyPress") { + SpylikeEvents::KeyInputEvent& ke = dynamic_cast(e); + Coordinate pos = getPos(); + if (state == PState::Idle && (ke.c == 'v' || ke.c == 'b' || ke.c == 'n' || ke.c == 'g')) { + Coordinate attackPos = pos; + switch(ke.c) { + case ('g'): { + attackPos.y--; + break; + } + case ('v'): { + attackPos.x--; + break; + } + case ('b'): { + attackPos.y++; + break; + } + case ('n'): { + attackPos.x++; + } + } + if (world->isInMap(attackPos)) { + std::shared_ptr attackTile = world->getTile(attackPos); + if (attackTile) { + for (auto& entity : attackTile->getEntities()) { + std::shared_ptr character = std::dynamic_pointer_cast(entity); + if (character) { + character->hurt(20); + std::shared_ptr boss = std::dynamic_pointer_cast(entity); + if (!boss && character->isAlive()) { + Coordinate newCharPos = character->getPos(); + if (getPos().x < newCharPos.x) newCharPos.x++; + else if (getPos().x > newCharPos.x) newCharPos.x--; + else if (getPos().y > newCharPos.y) newCharPos.y--; + else newCharPos.y++; + if (world->isInMap(newCharPos)) { + world->moveEntity(character->getID(), newCharPos); + } + } + } + } + } + state = PState::Attacking; + attackLoc = attackPos; + attackTimer.reset(); + } + } + if (world->worldType == WorldType::Roguelike) { + if (ke.c == 'w' || ke.c == 'a' || ke.c == 's' || ke.c == 'd') { + Coordinate newPos = pos; + switch(ke.c) { + case ('w'): { + newPos.y--; + break; + } + case ('a'): { + newPos.x--; + break; + } + case ('s'): { + newPos.y++; + break; + } + case ('d'): { + newPos.x++; + } + } + if (world->isInMap(newPos)) { + bool res = world->moveEntity(getID(), newPos); + if (res) { + SpylikeEvents::CameraEvent ce("CAMERA_Move", newPos); + eventManager->emit(ce); + } + } + } + } + else { + if (ke.c == 'a' || ke.c == 'd') { + Coordinate newPos = pos; + switch(ke.c) { + case ('a'): { + newPos.x--; + xVel = -1; + break; + } + case ('d'): { + newPos.x++; + xVel = 1; + } + } + if (world->isInMap(newPos)) { + bool res = world->moveEntity(getID(), newPos); + if (res) { + SpylikeEvents::CameraEvent ce("CAMERA_Move", newPos); + eventManager->emit(ce); + slideTimer.reset(); + } + } + } + else if (ke.c == 'w' || ke.c == ' ' && (yVel == 0)) { + Coordinate below = Coordinate(pos.x, pos.y+1); + bool res = world->moveEntity(getID(), below); + if (!res) { + world->moveEntity(getID(), pos); + yVel = 6; + moveTimer.reset(); + } + } + } + } +} + +void Player::draw(GeometryRenderer& painter) { + if (state == PState::Hurt) { + painter.drawString(getPos(), hurtSprite.getCurrentFrame(), "Entity"); + hurtSprite.nextFrame(); + } + else { + if (state == PState::Attacking) { + if (attackLoc.x != getPos().x) painter.draw(attackLoc, '-', "Effect"); + else painter.draw(attackLoc, '|', "Effect"); + } + painter.draw(getPos(), '@', "Entity"); + } +} + +void Player::on_update() { + if (world->worldType == WorldType::Platform) { + moveTimer.tick(); + slideTimer.tick(); + if (yVel > 0 && (moveTimer.getElapsed() > 3/yVel)) { + Coordinate newPos = Coordinate(getPos().x+xVel, getPos().y-1); + if (world->isInMap(newPos)) { + bool res = world->moveEntity(getID(), newPos); + if (res) { + yVel--; + SpylikeEvents::CameraEvent ce("CAMERA_MoveUp", newPos); + eventManager->emit(ce); + } + } + else yVel = 0; + slideTimer.reset(); + moveTimer.reset(); + } + else { + if (moveTimer.getElapsed() > 2) { + Coordinate below = Coordinate(getPos().x, getPos().y+1); + if (world->isInMap(below)) { + bool res = world->moveEntity(getID(), below); + if (res && slideTimer.getElapsed() > 2) { + Coordinate newPos = Coordinate(below.x+xVel, below.y); + bool res = world->moveEntity(getID(), newPos); + if (!res) xVel = 0; + slideTimer.reset(); + } + } + moveTimer.reset(); + } + if (slideTimer.getElapsed() > 5) { + xVel = 0; + } + } + } + + switch(state) { + case(PState::Idle): { break; } + case(PState::Hurt): { + if (health == 0) { + Event playerDeath = Event("GAME_PlayerDeath"); + eventManager->emit(playerDeath); + } + hurtTimer.tick(); + if (hurtTimer.getElapsed() == 15) state = PState::Idle; + break; + } + case(PState::Attacking): { + attackTimer.tick(); + if (attackTimer.getElapsed() > 2) state = PState::Idle; + } + } +} + +void Player::on_collide(std::shared_ptr collider) { + if (collider->getPos().y < getPos().y) yVel = 0; +} + +void Player::hurt(int damage) { + if (state != PState::Hurt) { + health -= damage; + SpylikeEvents::PlayerHurtEvent ev("GAME_PlayerHurt", health); + eventManager->emit(ev); + state = PState::Hurt; + hurtTimer.reset(); + } +} + +void Goblin::on_update() { + switch(state) { + case (GobState::Idle): { + std::vector> res = world->findEntities(getPos(), 15); + if (res.size() > 0) { + state = GobState::Found; + seekTimer.reset(); + } + break; + } + case (GobState::Found): { + seekTimer.tick(); + if (seekTimer.getElapsed() > 8) { + state = GobState::Pursue; + seekTimer.reset(); + } + break; + } + case (GobState::Pursue): { + if (seekTimer.getElapsed() >= 7) { + seekTimer.reset(); + std::vector> res = world->findEntities(getPos(), 15); + if (res.size() > 0) { + std::shared_ptr player = res[0]; + int yDir; + int xDir; + if (player->getPos().y < getPos().y) yDir = -1; + else yDir = 1; + if (player->getPos().x < getPos().x) xDir = -1; + else xDir = 1; + if (world->worldType == WorldType::Platform) yDir = 0; + Coordinate newPos(getPos().x + xDir, getPos().y + yDir); + if (world->isInMap(newPos)) world->moveEntity(getID(), newPos); + } + else state = GobState::Idle; + } + seekTimer.tick(); + } + } + if (world->worldType == WorldType::Platform) { + if (moveTimer.getElapsed() > 2) { + Coordinate below = Coordinate(getPos().x, getPos().y+1); + if (world->isInMap(below)) { + bool res = world->moveEntity(getID(), below); + falling = res; + } + moveTimer.reset(); + } + } +} + +void Goblin::draw(GeometryRenderer& painter) { + if (state == GobState::Found) { + painter.draw(Coordinate(getPos().x, getPos().y-1), '!', "Effect"); + } + if (falling) { + painter.draw(Coordinate(getPos().x, getPos().y-1), '?', "Effect"); + } + painter.draw(getPos(), '$', "Entity"); +} + +void Goblin::on_collide(std::shared_ptr collider) { + std::shared_ptr player = std::dynamic_pointer_cast(collider); + if (player) { + player->hurt(10); + } +} + +void Goblin::hurt(int damage) { + health -= damage; + if (health <= 0) kill(); +} + +void Skeleton::draw(GeometryRenderer& painter) { + painter.draw(getPos(), '&', "Entity"); +} + +void Skeleton::on_collide(std::shared_ptr collider) { + std::shared_ptr player = std::dynamic_pointer_cast(collider); + if (player) { + player->hurt(10); + } +} + + +void Skeleton::on_update() { + fireTimer.tick(); + if (fireTimer.getElapsed() > 30) { + std::vector> res = world->findEntities(getPos(), 15); + if (res.size() > 0) { + std::shared_ptr player = res[0]; + int xVel = 0; + if (player->getPos().x > getPos().x) xVel = 100; + else if (player->getPos().x < getPos().x) xVel = -100; + int yVel = 0; + if (player->getPos().y != getPos().y) { + int yDir = abs(player->getPos().y - getPos().y)/(player->getPos().y - getPos().y); + if (player->getPos().x != getPos().x) yVel = yDir*abs((xVel*(player->getPos().y - getPos().y))/(player->getPos().x - getPos().x)); + else yVel = yDir; + } + Coordinate arrowPos = Coordinate(getPos().x+(xVel/100), getPos().y+(yVel/100)); + auto tile = world->getTile(arrowPos); + if (!tile || tile->getEntities().size() == 0) { + std::shared_ptr arrow = std::make_shared(xVel, yVel); + arrow->init(eventManager); + world->registerEntity(arrow, arrowPos); + } + } + fireTimer.reset(); + } +} + +void Skeleton::hurt(int damage) { + health -= damage; +} + +void SkeletonArrow::draw(GeometryRenderer& painter) { + painter.draw(getPos(), '#', "Entity"); +} + +void SkeletonArrow::on_update() { + moveTimer.tick(); + if (moveTimer.getElapsed() > 2) { + moveTimer.reset(); + xFlag += xVel; + yFlag += yVel; + Coordinate newPos = getPos(); + if (abs(xFlag) >= 100) { + newPos.x = newPos.x + (abs(xVel)/xVel)*(abs(xFlag)/100); + xFlag = 0; + } + if (abs(yFlag) >= 100) { + newPos.y = newPos.y + abs(yVel)/yVel*(abs(yFlag)/100); + yFlag = 0; + } + if (newPos != getPos()) { + if (world->isInMap(newPos)) world->moveEntity(getID(), newPos); + else kill(); + } + } +} + +void SkeletonArrow::on_collide(std::shared_ptr collider) { + std::shared_ptr player = std::dynamic_pointer_cast(collider); + if (player) { + player->hurt(10); + } + kill(); +} + +void Boss::on_init() { + SpylikeEvents::AudioPlayEvent ap("AUDIO_PlayMusic", "1-f.wav", 0.25); + eventManager->emit(ap); + std::vector> res = world->findEntities(getPos(), 15); + if (res.size() > 0) { + res[0]->health = 100; + } +} + +void Boss::draw(GeometryRenderer& painter) { + painter.draw(getPos(), '/', "Entity"); + painter.draw(Coordinate(getPos().x+1, getPos().y), '\\', "Entity"); + painter.draw(Coordinate(getPos().x+1, getPos().y-1), ')', "Entity"); + painter.draw(Coordinate(getPos().x, getPos().y-1), '(', "Entity"); + if (state == BossState::Alert) { + painter.drawString(Coordinate(getPos().x, getPos().y-2), alertSprite.getCurrentFrame(), "Entity"); + alertSprite.nextFrame(); + } +} + +void Boss::on_update() { + if (state == BossState::Alert) { + alertTimer.tick(); + if (alertTimer.getElapsed() > 60) { + if (rand() % 2) state = BossState::Attack1; + else state = BossState::Attack2; + fireTimer.reset(); + alertTimer.reset(); + } + } + else if (state == BossState::Attack1) { + fireTimer.tick(); + if ((fireTimer.getElapsed() % 20) < 5) { + Coordinate arrowPos = Coordinate(getPos().x-1, getPos().y); + int yVel; + if ((fireTimer.getElapsed() % 20) % 3 == 0) yVel = 0; + else if ((fireTimer.getElapsed() % 20) % 3 == 1) yVel = -30; + else yVel = -20; + auto tile = world->getTile(arrowPos); + if (!tile || tile->getEntities().size() == 0) { + std::shared_ptr arrow = std::make_shared(-300, yVel); + arrow->init(eventManager); + world->registerEntity(arrow, arrowPos); + } + } + if (fireTimer.getElapsed() > 60) { + state = BossState::Alert; + } + } + else if (state == BossState::Attack2) { + fireTimer.tick(); + if ((fireTimer.getElapsed() % 15) == 0) { + for (int y=0; y<3; y++) { + std::shared_ptr arrow = std::make_shared(-300, 0); + arrow->init(eventManager); + world->registerEntity(arrow, Coordinate(getPos().x, getPos().y-y)); + } + } + if (fireTimer.getElapsed() > 100) { + state = BossState::Alert; + } + } + if (health <= 0) { + Event ev("AUDIO_PauseMusic"); + eventManager->emit(ev); + kill(); + } +} + +void Boss::hurt(int damage) { + health -= damage; +} diff --git a/game/entities/geometry.cpp b/game/entities/geometry.cpp new file mode 100644 index 0000000..ec40b40 --- /dev/null +++ b/game/entities/geometry.cpp @@ -0,0 +1,5 @@ +#include "geometry.h" + +void Wall::draw(GeometryRenderer& painter) { + painter.draw(Coordinate(getPos().x, getPos().y), '~', "Entity"); +} diff --git a/game/entities/misc.cpp b/game/entities/misc.cpp new file mode 100644 index 0000000..c128f32 --- /dev/null +++ b/game/entities/misc.cpp @@ -0,0 +1,73 @@ +#include "misc.h" +#include "event.h" + +void LevelTransition::on_collide(std::shared_ptr collider) { + std::shared_ptr player = std::dynamic_pointer_cast(collider); + if (player) { + SpylikeEvents::LevelChangeEvent le("LEVEL_Change", levelPath); + eventManager->emit(le); + } +} + +void Key::on_collide(std::shared_ptr collider) { + std::shared_ptr player = std::dynamic_pointer_cast(collider); + if (player) { + Event ev("GAME_KeyCollect"); + eventManager->emit(ev); + } + collectedTimer.reset(); + state = KeyState::Collected; +} + +void Key::on_update() { + if (state == KeyState::Collected) { + collectedTimer.tick(); + if (collectedTimer.getElapsed() > 30) kill(); + } +} + +void Key::draw(GeometryRenderer& painter) { + switch (state) { + case (KeyState::Idle): { + painter.draw(getPos(), '%', "Entity"); + break; + } + case (KeyState::Collected): { + painter.drawString(Coordinate(getPos().x-10, getPos().y-1), "Key Collected!", "Effect"); + } + } +} + +void Door::on_init() { + eventManager->subscribe(shared_from_this(), "GAME_DoorResponse"); +} + +void Door::on_event(Event& e) { + if (e.type == "GAME_DoorResponse") { + SpylikeEvents::DoorResponseEvent& dr = dynamic_cast(e); + if (dr.res) kill(); + else { displayTimer.reset(); state = DoorState::FailedOpen; } + } +} + +void Door::on_collide(std::shared_ptr collider) { + std::shared_ptr player = std::dynamic_pointer_cast(collider); + if (player) { + Event ev("GAME_DoorRequest"); + eventManager->emit(ev); + } +} + +void Door::on_update() { + if (state == DoorState::FailedOpen) { + displayTimer.tick(); + if (displayTimer.getElapsed() > 30) state = DoorState::Idle; + } +} + +void Door::draw(GeometryRenderer& painter) { + painter.draw(getPos(), '=', "Entity"); + if (state == DoorState::FailedOpen) { + painter.drawString(Coordinate(getPos().x-10, getPos().y-1), "Get a key first!", "Effect"); + } +} diff --git a/game/entities/obstacle.cpp b/game/entities/obstacle.cpp new file mode 100644 index 0000000..eba764c --- /dev/null +++ b/game/entities/obstacle.cpp @@ -0,0 +1,59 @@ +#include "obstacle.h" +#include "character.h" +#include +#include + +void Lava::draw(GeometryRenderer& painter) { + painter.draw(getPos(), '#', "Entity"); +} + +void Lava::on_collide(std::shared_ptr collider) { + std::shared_ptr player = std::dynamic_pointer_cast(collider); + if (player) { + player->hurt(5); + } +} + +void Lava::on_update() { + moveTimer.tick(); + if (moveTimer.getElapsed() > 2) { + moveTimer.reset(); + Coordinate newPos = Coordinate(getPos().x, getPos().y-1); + if (world->isInMap(newPos)) { + bool res = world->moveEntity(getID(), newPos); + } + count += 1; + } + if (count > 7) kill(); + +} + +void LavaGenerator::draw(GeometryRenderer& painter) { + painter.draw(getPos(), '#', "Entity"); +} + +void LavaGenerator::on_collide(std::shared_ptr collider) { + std::shared_ptr player = std::dynamic_pointer_cast(collider); + if (player) { + player->hurt(10); + } +} + +void LavaGenerator::on_update() { + if (rand() % 60 == 17) { + std::shared_ptr lava = std::make_shared(); + lava->init(eventManager); + world->registerEntity(lava, Coordinate(getPos().x, getPos().y-1)); + } +} + +void Spike::draw(GeometryRenderer& painter) { + painter.draw(getPos(), '#', "Entity"); +} + +void Spike::on_collide(std::shared_ptr collider) { + std::shared_ptr player = std::dynamic_pointer_cast(collider); + if (player) { + player->hurt(5); + } +} diff --git a/game/entities/worldentities.cpp b/game/entities/worldentities.cpp deleted file mode 100644 index 386fdf5..0000000 --- a/game/entities/worldentities.cpp +++ /dev/null @@ -1,3 +0,0 @@ -void GeometryTile::on_draw(GeometryRenderer& painter) { - painter.drawString(tile->pos, sprite, "geometry"); -} diff --git a/game/game.cpp b/game/game.cpp index 27cf018..334cc1a 100644 --- a/game/game.cpp +++ b/game/game.cpp @@ -1,6 +1,7 @@ #include #include #include "rendering.h" +#include "scheduling.h" #include "camera.h" #include "screen.h" #include "sprites.h" @@ -9,7 +10,10 @@ #include "logger.h" #include "menus.h" #include "input.h" -using namespace std; +#include "character.h" +#include "obstacle.h" +#include "game.h" +#include "geometry.h" #include #include #include @@ -18,75 +22,179 @@ using namespace std; extern SpylikeLogger LOGGER; -class epicEntity : public TileEntity { - Sprite sprite; - public: - epicEntity(Sprite sprite) : sprite(sprite) {} - void on_update() { - sprite.nextFrame(); - } - void draw(GeometryRenderer& painter) { - painter.drawString(tile->pos, sprite.getCurrentFrame(), "pog"); +std::string formatSeconds(int seconds) { + int minutes = seconds/60; + seconds = seconds - 60*minutes; + return std::to_string(minutes) + ":" + std::to_string(seconds); +} + +void GameManager::RunLevelTask::update() { + manager.camera->clearScreen(); + Coordinate origin = manager.camera->getOrigin(); + int yOff = manager.camera->getYOffset(); + int xOff = manager.camera->getXOffset(); + for (int y=(origin.y+yOff); y>(origin.y+yOff-(2*manager.camera->getScreenHeight())); y--) { + for (int x=(origin.x-xOff); x<(origin.x-xOff+(2*manager.camera->getScreenWidth())); x++) { + if (manager.map->isInMap(Coordinate(x, y))) { + manager.map->updateTile(Coordinate(x, y)); + manager.map->drawTile(Coordinate(x, y), *manager.gameRenderer); + } } - - void on_event(Event& e) { - SpylikeEvents::KeyInputEvent &ke = dynamic_cast(e); - LOGGER.log(std::to_string(ke.c), DEBUG); + } + //manager.menuRenderer->drawBox(Coordinate(0, 0), Coordinate(manager.menuManager->getScreenWidth(), manager.menuManager->getScreenHeight()), "Overlay"); + //manager.menuManager->renderToScreen(); + manager.camera->toggleAbsolute(); + manager.gameRenderer->drawString(Coordinate(0, 0), "Health: " + std::to_string(manager.playerHealth), "UI"); + manager.gameRenderer->drawString(Coordinate(0, 1), "Time elapsed: " + formatSeconds(manager.scheduler.timeElapsed()), "UI"); + manager.camera->toggleAbsolute(); + manager.camera->renderToScreen(); + manager.inputManager->update(); + +} + +void GameManager::TickTask::update() {} + +void GameManager::MenuTask::update() { + manager.menuManager->clearScreen(); + manager.activeMenu->draw(*manager.menuRenderer); + manager.activeMenu->update(); + manager.menuRenderer->drawBox(Coordinate(0, 0), Coordinate(manager.menuManager->getScreenWidth(), manager.menuManager->getScreenHeight()-1), "Overlay"); + manager.menuManager->renderToScreen(); + manager.menuInputManager->update(); +} + +void GameManager::StartupTask::update() { + manager.showMenu(SpylikeMenus::startMenu(), true); + manager.scheduler.pauseTask("StartupTask"); +} + + +void GameManager::pause() { + paused = true; +} + +void GameManager::quit() { + screen.end(); + exit(0); +} + +void GameManager::loadLevel(Level level) { + LOGGER.log("Loading level", DEBUG); + eventManager->clear(); + eventManager->subscribe(camera, "CAMERA_MoveUp"); + eventManager->subscribe(camera, "CAMERA_MoveDown"); + eventManager->subscribe(camera, "CAMERA_MoveLeft"); + eventManager->subscribe(camera, "CAMERA_MoveRight"); + eventManager->subscribe(camera, "CAMERA_Move"); + eventManager->subscribe(audioManager, "AUDIO_PlayMusic"); + eventManager->subscribe(audioManager, "AUDIO_PauseMusic"); + eventManager->subscribe(shared_from_this(), "MENU_Show"); + eventManager->subscribe(shared_from_this(), "INPUT_KeyPress"); + eventManager->subscribe(shared_from_this(), "MENU_ButtonClick"); + eventManager->subscribe(shared_from_this(), "LEVEL_Change"); + eventManager->subscribe(shared_from_this(), "GAME_PlayerHurt"); + eventManager->subscribe(shared_from_this(), "GAME_KeyCollect"); + eventManager->subscribe(shared_from_this(), "GAME_DoorRequest"); + IDBlock idAllocation = {0, 1024}; + map = std::make_shared(level.width, level.height, eventManager, idAllocation, level.worldType); + for (auto entPair : level.entities) { + std::shared_ptr player = std::dynamic_pointer_cast(entPair.first); + if (player) { + player->health = playerHealth; } -}; - - -namespace Game { - void run() { - SpriteDelta d1 = {0, "lol"}; - SpriteDelta d2 = {1, "e"}; - SpriteDelta d3 = {0, "p"}; - SpriteDelta d4 = {2, "gg"}; - SpriteDelta d5 = {0, "o\b"}; - SpriteFrame f1 = {d1}; - SpriteFrame f2 = {d2}; - SpriteFrame f3 = {d3, d4}; - SpriteFrame f4 = {d5}; - - vector frames = {f1, f2, f3, f4}; - Sprite coolSprite(frames, 4); - - vector layers = {RenderLayer("pog", 1), RenderLayer("UI", 2)}; - NcursesTerminalScreen screen(60, 20); - - Camera camera(screen, 60, 20, layers); - GeometryRenderer renderer(camera); - - std::shared_ptr manager(new EventManager()); - std::shared_ptr inputManager(new InputManager(manager, screen)); - std::shared_ptr ent = std::make_shared(coolSprite); - - std::shared_ptr button = std::make_shared(20, 4, "mug moment", "pog"); - std::shared_ptr menu = std::make_shared(60, 20); - menu->addChild(button); - - IDBlock idAllocation = {0, 1024}; - std::shared_ptr map = std::make_shared(60, 20, manager, idAllocation); - map->registerEntity(ent, Coordinate(5,5)); - map->registerEntity(menu, Coordinate(1,1)); - menu->addButton(button, Coordinate(0,0)); - map->moveEntity(menu, Coordinate(3,3)); - menu->click(); - - bool flag = true; - while (true) { - //camera.setOrigin(Coordinate(camera.getOrigin().x + 1, camera.getOrigin().y + 1)); - for (int y=0; y<20; y++) { - for (int x=0; x<60; x++) { - map->updateTile(Coordinate(x, y)); - map->drawTile(Coordinate(x, y), renderer); - inputManager->update(); - } - } - usleep(500000); - camera.clearScreen(); + map->registerEntity(entPair.first, entPair.second); + } + if (!audioManager->isPlaying()) audioManager->playMusic("1-1.wav", 0.25); +} + +// Note: You must close any active menus, before showing a new one. +void GameManager::showMenu(std::shared_ptr menu, bool pause) { + audioManager->pauseMusic(); + if (!scheduler.isRunning("MenuTask")) { + activeMenu = menu; + activeMenu->setID(1025); + activeMenu->init(eventManager); + menuEventManager->subscribe(activeMenu, "INPUT_KeyPress"); + if (pause) { + scheduler.pauseTask("RunLevel"); } + camera->clearScreen(); + camera->renderToScreen(); + camera->lock(); + scheduler.resumeTask("MenuTask"); + } +} + +void GameManager::closeMenu() { + menuEventManager->unsubscribe(activeMenu); + activeMenu = nullptr; + scheduler.resumeTask("RunLevel"); + scheduler.pauseTask("MenuTask"); + camera->unlock(); + audioManager->resumeMusic(); +} - screen.end(); +void GameManager::on_event(Event& e) { + if (e.type == "MENU_Show") { + showMenu(SpylikeMenus::testMenu(), true); + } + if (e.type == "MENU_ButtonClick") { + SpylikeEvents::MenuButtonEvent& mb = dynamic_cast(e); + if (mb.buttonID == "close") closeMenu(); + if (mb.buttonID == "restart") {closeMenu(); playerHealth=100; keyCollected=false; loadLevel(load_from_file("game/resource/levels/1-1.spm"));} + if (mb.buttonID == "quit") quit(); + } + if (e.type == "LEVEL_Change") { + SpylikeEvents::LevelChangeEvent& lc = dynamic_cast(e); + Level level = load_from_file(lc.levelPath); + loadLevel(level); + } + if (e.type == "INPUT_KeyPress") { + SpylikeEvents::KeyInputEvent& ke = dynamic_cast(e); + if (ke.c == 27) { + showMenu(SpylikeMenus::pauseMenu()); + } + } + if (e.type == "GAME_PlayerHurt") { + SpylikeEvents::PlayerHurtEvent& ph = dynamic_cast(e); + playerHealth = ph.health; + if (playerHealth <= 0) showMenu(SpylikeMenus::gameOver()); + } + if (e.type == "GAME_KeyCollect") { + keyCollected = true; + } + if (e.type == "GAME_DoorRequest") { + SpylikeEvents::DoorResponseEvent ev("GAME_DoorResponse", keyCollected); + eventManager->emit(ev); } } + + +void GameManager::run() { + std::vector layers {RenderLayer("Entity", 1), RenderLayer("Effect", 2), RenderLayer("UI", 3), RenderLayer("Overlay", 4)}; + camera = std::make_shared(screen, 80, 30, layers); + camera->setOffset(30, 20); + menuManager = std::make_shared(screen, layers); + + + eventManager = std::make_shared(); + menuEventManager = std::make_shared(); + inputManager = std::make_shared(eventManager, screen); + menuInputManager = std::make_shared(menuEventManager, screen); + GeometryRenderer theRenderer = GeometryRenderer(*camera); + gameRenderer = &theRenderer; + GeometryRenderer theMenuRenderer = GeometryRenderer(*menuManager); + menuRenderer = &theMenuRenderer; + + audioManager = std::make_shared("game/resource/audio/"); + + Level level = load_from_file("game/resource/levels/1-3.spm"); + loadLevel(level); + + scheduler.addTask(std::make_unique(*this)); + scheduler.addTask(std::make_unique(*this)); + scheduler.addTask(std::make_unique(*this)); + scheduler.addTask(std::make_unique(*this)); + scheduler.pauseTask("MenuTask"); + scheduler.run(); +} diff --git a/game/include/character.h b/game/include/character.h new file mode 100644 index 0000000..c07817c --- /dev/null +++ b/game/include/character.h @@ -0,0 +1,93 @@ +#ifndef SPYLIKE_GENTITIES_H +#define SPYLIKE_GENTITIES_H +#include "levelmap.h" +#include "event.h" +#include "camera.h" +#include "timer.h" +#include "sprites.h" + +class Character : public TileEntity { + public: + Character() : TileEntity(true) {} + virtual void hurt(int damage) = 0; + int health = 100; // should use hurt, but game manager needs to change it manually too +}; + +class Player : public Character { + enum PState { Idle, Hurt, Attacking }; + PState state = Idle; + Timer hurtTimer; + Timer moveTimer; + Timer slideTimer; + Timer attackTimer; + std::vector hurtFrames{SpriteFrame{SpriteDelta(0, "")}, SpriteFrame{SpriteDelta(0, "@")}}; + Sprite hurtSprite{hurtFrames, 1}; + Coordinate attackLoc = Coordinate(0, 0); + int yVel = 0; + int xVel = 0; + void on_event(Event& e) override; + void on_update() override; + void on_init() override; + void draw(GeometryRenderer& painter) override; + void on_collide(std::shared_ptr collider) override; + public: + void hurt(int damage) override; +}; + +class Goblin : public Character { + enum GobState {Idle, Found, Pursue}; + GobState state = Idle; + Timer seekTimer; + Timer moveTimer; + bool falling = false; + void on_event(Event& e) override {} + void on_update() override; + void draw(GeometryRenderer& painter) override; + void on_collide(std::shared_ptr collider) override; + public: + void hurt(int damage) override; +}; + +class Skeleton : public Character { + Timer fireTimer; + void on_event(Event& e) override {} + void on_update() override; + void draw(GeometryRenderer& painter) override; + void on_collide(std::shared_ptr collider) override; + public: + void hurt(int damage) override; +}; + +class SkeletonArrow : public TileEntity { + void on_event(Event& e) override {} + void on_update() override; + void draw(GeometryRenderer& painter) override; + void on_collide(std::shared_ptr collider) override; + Timer moveTimer; + Coordinate initialPos; + int xFlag=0; + int yFlag=0; + int xVel; + int yVel; + public: + SkeletonArrow(int xVel, int yVel) : TileEntity(true), xVel{xVel}, yVel{yVel} {} +}; + +class Boss : public Character { + enum BossState { Alert, Attack1, Attack2 }; + BossState state = BossState::Alert; + Timer alertTimer; + Timer fireTimer; + std::vector alertFrames{SpriteFrame{SpriteDelta(0, "")}, SpriteFrame{SpriteDelta(0, "!")}, SpriteFrame{SpriteDelta(0, "")}, SpriteFrame{SpriteDelta(0, "!")}, SpriteFrame{SpriteDelta(0, "")}, SpriteFrame{SpriteDelta(0, "")}}; + Sprite alertSprite{alertFrames, 1}; + void on_event(Event& e) override {} + void on_update() override; + void draw(GeometryRenderer& painter) override; + void on_collide(std::shared_ptr collider) override {} + void on_init() override; + public: + void hurt(int damage) override; + int health = 240; +}; + +#endif diff --git a/game/include/geometry.h b/game/include/geometry.h new file mode 100644 index 0000000..4589a26 --- /dev/null +++ b/game/include/geometry.h @@ -0,0 +1,19 @@ +#ifndef SPYLIKE_GEOMETRY_H +#define SPYLIKE_GEOMETRY_H + +#include "levelmap.h" + +class Geometry: public TileEntity { + public: + Geometry() : TileEntity(true) {} +}; + +class Wall : public Geometry { + void on_update() override {}; + void on_event(Event& e) override {}; + void draw(GeometryRenderer& painter) override; + void on_collide(std::shared_ptr collider) override {}; +}; + + +#endif diff --git a/game/include/menus.h b/game/include/menus.h new file mode 100644 index 0000000..9a261e8 --- /dev/null +++ b/game/include/menus.h @@ -0,0 +1,71 @@ +#ifndef SPYLIKE_MENUS_H +#define SPYLIKE_MENUS_H + +#include "levelmap.h" +#include "event.h" +#include +#include +#include +#include +#include + +class MenuButton : public SpritedObject { + void on_update(); + void on_event(Event& e); + std::string text; + std::string buttonID; + public: + const int width; + const int height; + Coordinate pos; // top left + MenuButton(Coordinate pos, int width, int height, std::string buttonText, std::string buttonID); + void draw(GeometryRenderer& painter); + void click(); + std::string getButtonID(); +}; + +class Menu : public SpritedObject { + int width; + int height; + int nextID = -1; + std::map buttons; + std::queue selectionList; + std::string currentSelection = ""; + void on_update(); + void on_event(Event& e); + void on_init() override; + public: + Menu(int width, int height): width{width}, height{height} {} + virtual void draw(GeometryRenderer& painter); + void addButton(MenuButton button); + void setSelection(std::string buttonID); + void selectNext(); + void click(); +}; + +class PauseMenu : public Menu { + public: + PauseMenu(int width, int height): Menu(width, height) {} + void draw(GeometryRenderer& painter) override; +}; + +class GameOverMenu : public Menu { + public: + GameOverMenu(int width, int height): Menu(width, height) {} + void draw(GeometryRenderer& painter) override; +}; + +class StartMenu : public Menu { + public: + StartMenu(int width, int height): Menu(width, height) {} + void draw(GeometryRenderer& painter) override; +}; + +namespace SpylikeMenus { + std::shared_ptr testMenu(); + std::shared_ptr pauseMenu(); + std::shared_ptr gameOver(); + std::shared_ptr startMenu(); +} + +#endif diff --git a/game/include/misc.h b/game/include/misc.h new file mode 100644 index 0000000..6e98bac --- /dev/null +++ b/game/include/misc.h @@ -0,0 +1,47 @@ +#ifndef SPYLIKE_ENT_MISC_H +#define SPYLIKE_ENT_MISC_H + +#include "levelmap.h" +#include "event.h" +#include "character.h" +#include "rendering.h" +#include "timer.h" +#include + +class LevelTransition : public TileEntity { + std::string levelPath; + void on_collide(std::shared_ptr collider) override; + void on_event(Event& e) override {} + void on_update() override {} + void draw(GeometryRenderer& painter) override {} + public: + LevelTransition(std::string levelPath) : TileEntity(true), levelPath{levelPath} {} +}; + +class Key : public TileEntity { + Timer collectedTimer; + enum KeyState { Idle, Collected }; + KeyState state = KeyState::Idle; + void on_collide(std::shared_ptr collider) override; + void on_event(Event& e) override {} + void on_update() override; + void draw(GeometryRenderer& painter) override; + public: + Key() : TileEntity(true) {} + +}; + +class Door : public TileEntity { + Timer displayTimer; + enum DoorState { Idle, FailedOpen }; + DoorState state = DoorState::Idle; + void on_init() override; + void on_event(Event& e) override; + void on_collide(std::shared_ptr collider) override; + void on_update() override; + void draw(GeometryRenderer& painter) override; + public: + Door() : TileEntity(true) {} +}; + +#endif diff --git a/game/include/obstacle.h b/game/include/obstacle.h new file mode 100644 index 0000000..3a04316 --- /dev/null +++ b/game/include/obstacle.h @@ -0,0 +1,36 @@ +#ifndef SPYLIKE_OBSTACLE_H +#define SPYLIKE_OBSTACLE_H + +#include "levelmap.h" +#include "timer.h" + +class Lava : public TileEntity { + void on_event(Event& e) override {} + void on_update() override; + void draw(GeometryRenderer& painter) override; + void on_collide(std::shared_ptr collider) override; + int count = 0; + Timer moveTimer; + public: + Lava() : TileEntity(true) {} +}; + +class LavaGenerator : public TileEntity { + void on_event(Event& e) override {} + void on_update() override; + void draw(GeometryRenderer& painter) override; + void on_collide(std::shared_ptr collider) override; + public: + LavaGenerator() : TileEntity(true) {} +}; + +class Spike : public TileEntity { + void on_event(Event& e) override {} + void on_update() override {} + void draw(GeometryRenderer& painter) override; + void on_collide(std::shared_ptr collider) override; + public: + Spike() : TileEntity(true) {} +}; + +#endif diff --git a/game/resource/audio/music/1-1.wav b/game/resource/audio/music/1-1.wav new file mode 100644 index 0000000..ca1d598 Binary files /dev/null and b/game/resource/audio/music/1-1.wav differ diff --git a/game/resource/audio/music/1-f.wav b/game/resource/audio/music/1-f.wav new file mode 100644 index 0000000..ca4424a Binary files /dev/null and b/game/resource/audio/music/1-f.wav differ diff --git a/game/resource/levels/1-1.spm b/game/resource/levels/1-1.spm new file mode 100644 index 0000000..f981cb9 --- /dev/null +++ b/game/resource/levels/1-1.spm @@ -0,0 +1,390 @@ +Roguelike +100 +50 +Wall 64,0 +Wall 70,0 +Wall 64,1 +Wall 70,1 +Wall 64,2 +Wall 70,2 +Wall 64,3 +Wall 70,3 +Wall 64,4 +Wall 70,4 +Wall 63,5 +Wall 64,5 +Wall 71,5 +Wall 63,6 +Wall 71,6 +Wall 62,7 +Wall 63,7 +Wall 71,7 +Wall 62,8 +Wall 71,8 +Wall 62,9 +Wall 71,9 +Wall 62,10 +Wall 70,10 +Wall 71,10 +Wall 62,11 +Wall 70,11 +Wall 62,12 +Wall 70,12 +Wall 62,13 +Wall 70,13 +Wall 62,14 +Wall 70,14 +Wall 10,15 +Wall 11,15 +Wall 12,15 +Wall 13,15 +Wall 14,15 +Wall 15,15 +Wall 62,15 +Wall 70,15 +Wall 71,15 +Wall 8,16 +Wall 9,16 +Wall 10,16 +Wall 15,16 +Wall 16,16 +Wall 17,16 +Wall 18,16 +Wall 62,16 +Wall 71,16 +Wall 5,17 +Wall 6,17 +Wall 7,17 +Wall 8,17 +Wall 18,17 +Wall 19,17 +Wall 20,17 +Wall 21,17 +Wall 22,17 +Wall 23,17 +Wall 24,17 +Wall 25,17 +Wall 26,17 +Wall 27,17 +Wall 28,17 +Wall 29,17 +Wall 30,17 +Wall 31,17 +Wall 32,17 +Wall 33,17 +Wall 62,17 +Wall 71,17 +Wall 1,18 +Wall 2,18 +Wall 3,18 +Wall 4,18 +Wall 5,18 +Wall 33,18 +Wall 34,18 +Wall 62,18 +Wall 71,18 +Wall 1,19 +Goblin 27,19 +Wall 35,19 +Wall 62,19 +Wall 71,19 +Wall 1,20 +Wall 35,20 +Wall 36,20 +Wall 62,20 +Wall 71,20 +Wall 1,21 +Wall 36,21 +Wall 61,21 +Wall 62,21 +Wall 64,21 +Wall 65,21 +Wall 66,21 +Wall 71,21 +Wall 0,22 +Wall 1,22 +Wall 36,22 +Wall 37,22 +Wall 38,22 +Wall 60,22 +Wall 61,22 +Wall 71,22 +Wall 0,23 +Wall 38,23 +Wall 59,23 +Wall 70,23 +Wall 71,23 +Wall 0,24 +Wall 38,24 +Wall 57,24 +Wall 58,24 +Wall 59,24 +Wall 70,24 +Wall 0,25 +Wall 37,25 +Wall 38,25 +Wall 56,25 +Wall 57,25 +Wall 69,25 +Wall 70,25 +Wall 0,26 +Wall 37,26 +Wall 38,26 +Wall 56,26 +Wall 57,26 +Wall 69,26 +Wall 0,27 +Wall 36,27 +Wall 37,27 +Wall 38,27 +Wall 57,27 +Wall 69,27 +Wall 0,28 +Wall 36,28 +Wall 57,28 +Wall 63,28 +Wall 64,28 +Wall 65,28 +Wall 66,28 +Wall 69,28 +Wall 0,29 +Wall 14,29 +Wall 15,29 +Wall 36,29 +Wall 57,29 +Wall 69,29 +Wall 70,29 +Wall 0,30 +Wall 15,30 +Wall 36,30 +Wall 57,30 +Wall 70,30 +Wall 0,31 +Wall 15,31 +Wall 36,31 +Wall 57,31 +Wall 70,31 +Wall 0,32 +Wall 15,32 +Wall 36,32 +Wall 37,32 +Wall 57,32 +Wall 59,32 +Wall 60,32 +Wall 61,32 +Wall 62,32 +Wall 70,32 +Wall 0,33 +Wall 15,33 +Wall 37,33 +Wall 57,33 +Wall 70,33 +Wall 0,34 +Wall 15,34 +Wall 16,34 +Wall 20,34 +Wall 21,34 +Wall 26,34 +Wall 27,34 +Wall 37,34 +Wall 57,34 +Wall 65,34 +Wall 66,34 +Wall 67,34 +Wall 68,34 +Wall 70,34 +Wall 0,35 +Wall 15,35 +Wall 16,35 +Wall 37,35 +Wall 38,35 +Wall 57,35 +Wall 70,35 +Wall 0,36 +Wall 15,36 +Wall 38,36 +Wall 57,36 +Wall 61,36 +Wall 62,36 +Wall 63,36 +Wall 70,36 +Wall 0,37 +Wall 15,37 +Wall 29,37 +Wall 30,37 +Wall 38,37 +Wall 57,37 +Wall 70,37 +Wall 0,38 +Wall 15,38 +Wall 38,38 +Wall 39,38 +Wall 57,38 +Wall 70,38 +Wall 0,39 +Wall 8,39 +Wall 9,39 +Wall 10,39 +Wall 15,39 +Wall 16,39 +Wall 39,39 +Wall 57,39 +Wall 70,39 +Wall 0,40 +Wall 16,40 +Wall 33,40 +Wall 34,40 +Wall 39,40 +Wall 57,40 +Wall 70,40 +Wall 0,41 +Wall 16,41 +Wall 17,41 +Wall 18,41 +Wall 19,41 +Wall 39,41 +Wall 57,41 +Wall 70,41 +Wall 0,42 +Wall 19,42 +Wall 20,42 +Wall 39,42 +Wall 45,42 +Wall 46,42 +Wall 47,42 +Wall 48,42 +Wall 49,42 +Wall 50,42 +Wall 51,42 +Wall 52,42 +Wall 53,42 +Wall 54,42 +Wall 55,42 +Wall 57,42 +Wall 70,42 +Wall 0,43 +Wall 20,43 +Wall 23,43 +Wall 24,43 +Wall 28,43 +Wall 33,43 +Wall 39,43 +Wall 40,43 +Wall 41,43 +Wall 42,43 +Wall 43,43 +Wall 44,43 +Wall 45,43 +Wall 55,43 +Wall 56,43 +Wall 57,43 +Wall 70,43 +Wall 71,43 +Wall 72,43 +Wall 73,43 +Wall 74,43 +Wall 0,44 +Wall 20,44 +Wall 39,44 +Wall 74,44 +Wall 0,45 +Wall 20,45 +Wall 74,45 +Wall 0,46 +Player 4,46 +Wall 20,46 +Wall 24,46 +Goblin 72,46 +Wall 74,46 +Wall 0,47 +Wall 20,47 +Wall 34,47 +Wall 69,47 +Wall 75,47 +Wall 0,48 +Wall 20,48 +Wall 61,48 +Wall 62,48 +Wall 63,48 +Wall 64,48 +Wall 65,48 +Wall 66,48 +Wall 68,48 +Wall 69,48 +Wall 75,48 +Wall 0,49 +Wall 1,49 +Wall 2,49 +Wall 3,49 +Wall 4,49 +Wall 5,49 +Wall 6,49 +Wall 7,49 +Wall 8,49 +Wall 9,49 +Wall 10,49 +Wall 11,49 +Wall 12,49 +Wall 13,49 +Wall 14,49 +Wall 15,49 +Wall 16,49 +Wall 17,49 +Wall 18,49 +Wall 19,49 +Wall 20,49 +Wall 21,49 +Wall 22,49 +Wall 23,49 +Wall 24,49 +Wall 25,49 +Wall 26,49 +Wall 27,49 +Wall 28,49 +Wall 29,49 +Wall 30,49 +Wall 31,49 +Wall 32,49 +Wall 33,49 +Wall 34,49 +Wall 35,49 +Wall 36,49 +Wall 37,49 +Wall 38,49 +Wall 39,49 +Wall 40,49 +Wall 41,49 +Wall 42,49 +Wall 43,49 +Wall 44,49 +Wall 45,49 +Wall 46,49 +Wall 47,49 +Wall 48,49 +Wall 49,49 +Wall 50,49 +Wall 51,49 +Wall 52,49 +Wall 53,49 +Wall 54,49 +Wall 55,49 +Wall 56,49 +Wall 57,49 +Wall 58,49 +Wall 59,49 +Wall 60,49 +Wall 61,49 +Wall 66,49 +Wall 67,49 +Wall 68,49 +Wall 70,49 +Wall 71,49 +Wall 72,49 +Wall 73,49 +Wall 74,49 +Wall 75,49 +LevelTrans 65,1 transition:1-2 +LevelTrans 66,1 transition:1-2 +LevelTrans 67,0 transition:1-2 +LevelTrans 68,1 transition:1-2 +LevelTrans 69,1 transition:1-2 diff --git a/game/resource/levels/1-2.spm b/game/resource/levels/1-2.spm new file mode 100644 index 0000000..0aecc6b --- /dev/null +++ b/game/resource/levels/1-2.spm @@ -0,0 +1,179 @@ +Roguelike +30 +20 +Wall 0,0 +Wall 1,0 +Wall 2,0 +Wall 3,0 +Wall 4,0 +Wall 11,0 +Wall 12,0 +Wall 23,0 +Wall 27,0 +Wall 0,1 +Wall 4,1 +Wall 5,1 +Wall 6,1 +Wall 7,1 +Wall 8,1 +Wall 9,1 +Wall 10,1 +Wall 11,1 +Wall 12,1 +Wall 13,1 +Wall 23,1 +Wall 27,1 +Wall 0,2 +Wall 13,2 +Wall 14,2 +Wall 15,2 +Wall 22,2 +Wall 23,2 +Wall 27,2 +Wall 0,3 +Goblin 14,3 +Wall 15,3 +Wall 22,3 +Wall 27,3 +Wall 0,4 +Wall 15,4 +Wall 22,4 +Spike 25,4 +Wall 27,4 +Wall 0,5 +Wall 5,5 +Wall 13,5 +Wall 14,5 +Wall 15,5 +Wall 22,5 +Spike 24,5 +Spike 25,5 +Wall 27,5 +Wall 0,6 +Wall 5,6 +Wall 12,6 +Wall 13,6 +Wall 22,6 +Spike 24,6 +Wall 27,6 +Wall 0,7 +Wall 5,7 +Wall 12,7 +Wall 13,7 +Wall 22,7 +Spike 24,7 +Wall 27,7 +Wall 0,8 +Wall 5,8 +Spike 6,8 +Wall 12,8 +Wall 13,8 +Wall 22,8 +Spike 24,8 +Wall 27,8 +Wall 0,9 +Wall 12,9 +Wall 22,9 +Spike 24,9 +Wall 27,9 +Wall 0,10 +Spike 6,10 +Wall 12,10 +Wall 22,10 +Spike 24,10 +Spike 25,10 +Spike 26,10 +Wall 27,10 +Wall 0,11 +Wall 5,11 +Wall 6,11 +Wall 12,11 +Wall 22,11 +Wall 27,11 +Wall 0,12 +Wall 5,12 +Wall 6,12 +Wall 12,12 +Wall 13,12 +Wall 22,12 +Spike 23,12 +Spike 25,12 +Spike 26,12 +Wall 27,12 +Wall 0,13 +Wall 6,13 +Spike 11,13 +Wall 13,13 +Wall 14,13 +Wall 22,13 +Wall 27,13 +Wall 0,14 +Wall 2,14 +Wall 3,14 +Wall 6,14 +Wall 7,14 +Wall 8,14 +Wall 9,14 +Wall 14,14 +Wall 15,14 +Wall 20,14 +Wall 21,14 +Wall 22,14 +Spike 23,14 +Spike 24,14 +Wall 27,14 +Wall 0,15 +Wall 9,15 +Wall 15,15 +Wall 18,15 +Wall 19,15 +Wall 20,15 +Wall 27,15 +Wall 0,16 +Wall 8,16 +Wall 9,16 +Wall 15,16 +Wall 16,16 +Wall 17,16 +Wall 18,16 +Wall 27,16 +Wall 0,17 +Wall 9,17 +Wall 10,17 +Wall 27,17 +Wall 0,18 +Wall 9,18 +Spike 16,18 +Wall 27,18 +Wall 0,19 +Wall 1,19 +Wall 2,19 +Wall 3,19 +Wall 4,19 +Wall 5,19 +Wall 6,19 +Wall 7,19 +Wall 8,19 +Wall 9,19 +Wall 10,19 +Wall 11,19 +Wall 12,19 +Wall 13,19 +Wall 14,19 +Wall 15,19 +Wall 16,19 +Wall 17,19 +Wall 18,19 +Wall 19,19 +Wall 20,19 +Wall 21,19 +Wall 22,19 +Wall 23,19 +Wall 24,19 +Wall 25,19 +Wall 26,19 +Wall 27,19 +Player 1,16 +LevelTrans 24,0 transition:1-3 +LevelTrans 24,0 transition:1-3 +LevelTrans 25,0 transition:1-3 diff --git a/game/resource/levels/1-3.spm b/game/resource/levels/1-3.spm new file mode 100644 index 0000000..755733c --- /dev/null +++ b/game/resource/levels/1-3.spm @@ -0,0 +1,439 @@ +Platform +151 +31 +Wall 28,0 +Wall 29,0 +Wall 30,0 +Wall 31,0 +Wall 32,0 +Wall 33,0 +Wall 34,0 +Wall 35,0 +Wall 36,0 +Wall 37,0 +Wall 38,0 +Wall 39,0 +Wall 40,0 +Wall 41,0 +Wall 42,0 +Wall 43,0 +Wall 44,0 +Wall 45,0 +Wall 46,0 +Wall 47,0 +Wall 48,0 +Wall 49,0 +Wall 50,0 +Wall 51,0 +Wall 52,0 +Wall 53,0 +Wall 54,0 +Wall 23,1 +Wall 24,1 +Wall 25,1 +Wall 26,1 +Wall 27,1 +Wall 28,1 +Spike 41,1 +Wall 54,1 +Wall 55,1 +Wall 56,1 +Wall 57,1 +Wall 20,2 +Wall 21,2 +Wall 22,2 +Wall 23,2 +Spike 26,2 +Wall 57,2 +Wall 58,2 +Wall 59,2 +Wall 18,3 +Wall 19,3 +Wall 20,3 +Wall 59,3 +Wall 60,3 +Wall 9,4 +Wall 10,4 +Wall 11,4 +Wall 12,4 +Wall 14,4 +Wall 15,4 +Wall 16,4 +Wall 17,4 +Wall 18,4 +Wall 60,4 +Wall 61,4 +Wall 62,4 +Wall 63,4 +Wall 64,4 +Wall 65,4 +Wall 69,4 +Wall 70,4 +Wall 71,4 +Wall 72,4 +Wall 73,4 +Wall 74,4 +Wall 75,4 +Wall 76,4 +Wall 77,4 +Wall 78,4 +Wall 79,4 +Wall 80,4 +Wall 7,5 +Wall 8,5 +Wall 9,5 +Wall 12,5 +Wall 13,5 +Wall 14,5 +Spike 26,5 +Wall 31,5 +Wall 32,5 +Wall 33,5 +Wall 34,5 +Wall 35,5 +Wall 36,5 +Wall 37,5 +Wall 38,5 +Wall 39,5 +Wall 40,5 +Wall 41,5 +Wall 42,5 +Wall 43,5 +Wall 44,5 +Wall 45,5 +Wall 46,5 +Wall 47,5 +Wall 65,5 +Wall 66,5 +Wall 67,5 +Wall 69,5 +Wall 80,5 +Wall 4,6 +Wall 5,6 +Wall 6,6 +Wall 7,6 +Spike 26,6 +Wall 30,6 +Wall 31,6 +Wall 47,6 +Wall 48,6 +Wall 49,6 +Wall 67,6 +Wall 68,6 +Wall 69,6 +Wall 80,6 +Wall 81,6 +Wall 82,6 +Wall 83,6 +Wall 0,7 +Wall 1,7 +Wall 2,7 +Wall 3,7 +Wall 4,7 +Wall 24,7 +Wall 25,7 +Wall 26,7 +Wall 27,7 +Wall 28,7 +Wall 29,7 +Wall 30,7 +Wall 49,7 +Wall 50,7 +Wall 51,7 +Wall 52,7 +Wall 68,7 +Wall 69,7 +Wall 83,7 +Wall 84,7 +Wall 85,7 +Wall 86,7 +Wall 87,7 +Wall 88,7 +Wall 89,7 +Wall 90,7 +Wall 91,7 +Wall 92,7 +Wall 93,7 +Wall 94,7 +Wall 95,7 +Wall 96,7 +Wall 23,8 +Wall 24,8 +Wall 52,8 +Wall 53,8 +Wall 54,8 +Wall 55,8 +Wall 56,8 +Wall 57,8 +Wall 58,8 +Wall 59,8 +Wall 60,8 +Wall 21,9 +Wall 22,9 +Wall 23,9 +Wall 60,9 +Wall 61,9 +Wall 19,10 +Wall 20,10 +Wall 21,10 +Wall 61,10 +Wall 62,10 +Wall 82,10 +Wall 83,10 +Wall 84,10 +Wall 85,10 +Wall 86,10 +Wall 87,10 +Wall 88,10 +Wall 89,10 +Wall 90,10 +Wall 91,10 +Wall 92,10 +Wall 93,10 +Wall 94,10 +Wall 95,10 +Wall 96,10 +Wall 13,11 +Wall 14,11 +Wall 15,11 +Wall 16,11 +Wall 17,11 +Wall 18,11 +Wall 19,11 +Wall 34,11 +Wall 35,11 +Wall 36,11 +Wall 37,11 +Wall 62,11 +Wall 63,11 +Skeleton 76,11 +Wall 82,11 +Wall 10,12 +Wall 11,12 +Wall 12,12 +Wall 13,12 +Wall 34,12 +Wall 35,12 +Wall 63,12 +Wall 64,12 +Wall 71,12 +Wall 72,12 +Wall 75,12 +Wall 76,12 +Wall 81,12 +Wall 82,12 +Wall 9,13 +Wall 33,13 +Wall 34,13 +Wall 35,13 +Wall 64,13 +Wall 65,13 +Wall 81,13 +Wall 9,14 +Wall 33,14 +Wall 43,14 +Wall 44,14 +Wall 45,14 +Wall 46,14 +Wall 47,14 +Wall 48,14 +Wall 65,14 +Wall 66,14 +Wall 67,14 +Wall 68,14 +Wall 69,14 +Wall 77,14 +Wall 78,14 +Wall 79,14 +Wall 80,14 +Wall 81,14 +Wall 9,15 +Skeleton 16,15 +Wall 30,15 +Wall 31,15 +Wall 32,15 +Wall 33,15 +Wall 52,15 +Wall 53,15 +Wall 54,15 +Wall 55,15 +Wall 65,15 +Wall 70,15 +Wall 71,15 +Wall 77,15 +Wall 7,16 +Wall 8,16 +Wall 9,16 +Wall 14,16 +Wall 15,16 +Wall 16,16 +Wall 29,16 +Wall 30,16 +Wall 62,16 +Wall 63,16 +Wall 64,16 +Wall 65,16 +Wall 72,16 +Wall 77,16 +Wall 5,17 +Wall 6,17 +Wall 7,17 +Wall 28,17 +Wall 29,17 +Wall 61,17 +Wall 62,17 +Wall 72,17 +Spike 73,17 +Spike 74,17 +Spike 75,17 +Spike 76,17 +Wall 77,17 +Wall 27,18 +Wall 28,18 +Wall 60,18 +Wall 61,18 +Wall 72,18 +Spike 73,18 +Spike 74,18 +Spike 75,18 +Spike 76,18 +Wall 77,18 +Wall 16,19 +Wall 17,19 +Wall 26,19 +Wall 27,19 +Wall 59,19 +Wall 60,19 +Wall 72,19 +Wall 73,19 +Wall 74,19 +Wall 75,19 +Wall 76,19 +Wall 77,19 +Wall 1,20 +Wall 2,20 +Wall 3,20 +Wall 4,20 +Wall 13,20 +Wall 14,20 +Wall 15,20 +Wall 16,20 +Wall 17,20 +Wall 18,20 +Wall 19,20 +Wall 20,20 +Wall 21,20 +Wall 22,20 +Wall 26,20 +Wall 59,20 +Wall 13,21 +Wall 23,21 +Wall 24,21 +Wall 25,21 +Wall 55,21 +Wall 56,21 +Wall 57,21 +Wall 58,21 +Wall 59,21 +Goblin 0,22 +Wall 11,22 +Wall 12,22 +Wall 13,22 +Wall 55,22 +Wall 0,23 +Wall 1,23 +Wall 2,23 +Wall 3,23 +Wall 4,23 +Wall 5,23 +Wall 6,23 +Wall 7,23 +Wall 8,23 +Wall 9,23 +Wall 10,23 +Wall 11,23 +Wall 34,23 +Wall 35,23 +Wall 36,23 +Wall 37,23 +Wall 45,23 +Wall 46,23 +Wall 52,23 +Wall 53,23 +Wall 54,23 +Wall 55,23 +Wall 34,24 +Wall 38,24 +Wall 52,24 +Wall 32,25 +Wall 33,25 +Wall 34,25 +Wall 38,25 +Wall 39,25 +Wall 51,25 +Wall 52,25 +Wall 30,26 +Wall 31,26 +Wall 32,26 +Wall 39,26 +Wall 51,26 +Player 1,27 +Wall 30,27 +Wall 39,27 +Wall 40,27 +Spike 41,27 +Spike 42,27 +Spike 43,27 +Spike 44,27 +Spike 45,27 +Spike 46,27 +Spike 47,27 +Spike 48,27 +Spike 49,27 +Spike 50,27 +Wall 51,27 +Wall 0,28 +Wall 1,28 +Wall 2,28 +Wall 3,28 +Wall 4,28 +Wall 5,28 +Wall 6,28 +Wall 7,28 +Wall 8,28 +Wall 9,28 +Wall 10,28 +Wall 11,28 +Wall 12,28 +Wall 13,28 +Wall 14,28 +Wall 21,28 +Wall 22,28 +Wall 23,28 +Wall 24,28 +Wall 25,28 +Wall 26,28 +Wall 27,28 +Wall 28,28 +Wall 29,28 +Wall 30,28 +Wall 40,28 +Wall 41,28 +Wall 42,28 +Wall 43,28 +Wall 44,28 +Wall 45,28 +Wall 46,28 +Wall 47,28 +Wall 48,28 +Wall 49,28 +Wall 50,28 +Wall 51,28 +Spike 15,29 +Spike 16,29 +Spike 17,29 +Spike 18,29 +Spike 19,29 +Spike 20,29 +LevelTrans 96,8 transition:1-4 +LevelTrans 96,9 transition:1-4 diff --git a/game/resource/levels/1-4.spm b/game/resource/levels/1-4.spm new file mode 100644 index 0000000..73d809b --- /dev/null +++ b/game/resource/levels/1-4.spm @@ -0,0 +1,174 @@ +Roguelike +61 +31 +Wall 38,0 +Wall 39,0 +Wall 40,0 +Wall 41,0 +Wall 44,0 +Wall 45,0 +Wall 46,0 +Wall 47,0 +Wall 38,1 +Wall 41,1 +Wall 44,1 +Wall 47,1 +Wall 38,2 +Wall 41,2 +Wall 44,2 +Wall 47,2 +Wall 38,3 +Wall 47,3 +Wall 38,4 +Wall 47,4 +Wall 38,5 +Wall 47,5 +Wall 38,6 +Wall 47,6 +Wall 38,7 +Wall 47,7 +Wall 38,8 +Wall 47,8 +Wall 38,9 +Wall 47,9 +Wall 0,10 +Wall 1,10 +Wall 2,10 +Wall 3,10 +Wall 4,10 +Wall 5,10 +Wall 6,10 +Wall 7,10 +Wall 8,10 +Wall 9,10 +Wall 10,10 +Wall 11,10 +Wall 12,10 +Wall 13,10 +Wall 14,10 +Wall 15,10 +Wall 16,10 +Wall 17,10 +Wall 18,10 +Wall 19,10 +Wall 20,10 +Wall 21,10 +Wall 22,10 +Wall 23,10 +Wall 24,10 +Wall 25,10 +Wall 26,10 +Wall 27,10 +Wall 28,10 +Wall 29,10 +Wall 30,10 +Wall 31,10 +Wall 32,10 +Wall 33,10 +Wall 34,10 +Wall 35,10 +Wall 36,10 +Wall 37,10 +Wall 38,10 +Wall 47,10 +Wall 47,11 +Wall 47,12 +Player 2,13 +Wall 38,13 +Wall 39,13 +Wall 40,13 +Wall 41,13 +Wall 42,13 +Wall 43,13 +Wall 44,13 +Wall 45,13 +Wall 46,13 +Wall 47,13 +Wall 47,14 +Wall 47,15 +Wall 0,16 +Wall 1,16 +Wall 2,16 +Wall 3,16 +Wall 4,16 +Wall 5,16 +Wall 6,16 +Wall 7,16 +Wall 8,16 +Wall 9,16 +Wall 10,16 +Wall 11,16 +Wall 12,16 +Wall 13,16 +Wall 14,16 +Wall 15,16 +Wall 16,16 +Wall 17,16 +Wall 18,16 +Wall 19,16 +Wall 20,16 +Wall 21,16 +Wall 22,16 +Wall 23,16 +Wall 24,16 +Wall 25,16 +Wall 26,16 +Wall 27,16 +Wall 28,16 +Wall 29,16 +Wall 30,16 +Wall 31,16 +Wall 32,16 +Wall 33,16 +Wall 34,16 +Wall 35,16 +Wall 36,16 +Wall 37,16 +Wall 38,16 +Wall 39,16 +Wall 40,16 +Wall 41,16 +Door 42,16 +Wall 43,16 +Wall 44,16 +Wall 45,16 +Wall 46,16 +Wall 47,16 +Wall 38,17 +Wall 47,17 +Wall 38,18 +Wall 47,18 +Wall 38,19 +Wall 47,19 +Wall 38,20 +Wall 47,20 +Wall 38,21 +Wall 47,21 +Wall 38,22 +Wall 47,22 +Wall 38,23 +Wall 47,23 +Wall 38,24 +Wall 47,24 +Wall 38,25 +Wall 47,25 +Wall 38,26 +Wall 47,26 +Wall 38,27 +Wall 47,27 +Wall 38,28 +Wall 41,28 +Wall 44,28 +Wall 47,28 +Wall 38,29 +Wall 39,29 +Wall 40,29 +Wall 41,29 +Wall 44,29 +Wall 45,29 +Wall 46,29 +Wall 47,29 +LevelTrans 42,30 transition:1-f +LevelTrans 43,30 transition:1-f +LevelTrans 43,0 transition:1-5 +LevelTrans 42,0 transition:1-5 diff --git a/game/resource/levels/1-5.spm b/game/resource/levels/1-5.spm new file mode 100644 index 0000000..98eab5d --- /dev/null +++ b/game/resource/levels/1-5.spm @@ -0,0 +1,357 @@ +Platform +126 +31 +Wall 101,0 +Wall 102,0 +Wall 103,0 +Wall 104,0 +Wall 108,0 +Wall 101,1 +Wall 108,1 +Wall 100,2 +Wall 101,2 +Wall 107,2 +Wall 108,2 +Wall 44,3 +Wall 45,3 +Wall 46,3 +Wall 47,3 +Wall 48,3 +Wall 49,3 +Wall 50,3 +Wall 51,3 +Wall 52,3 +Wall 53,3 +Wall 54,3 +Wall 55,3 +Wall 56,3 +Wall 57,3 +Wall 99,3 +Wall 100,3 +Wall 106,3 +Wall 107,3 +Wall 36,4 +Wall 37,4 +Wall 38,4 +Wall 39,4 +Wall 40,4 +Wall 41,4 +Wall 42,4 +Wall 43,4 +Wall 44,4 +Wall 57,4 +Wall 58,4 +Wall 59,4 +Wall 60,4 +Wall 61,4 +Wall 62,4 +Wall 63,4 +Wall 64,4 +Wall 65,4 +Wall 66,4 +Wall 67,4 +Wall 68,4 +Wall 69,4 +Wall 70,4 +Wall 71,4 +Wall 72,4 +Wall 73,4 +Wall 74,4 +Wall 75,4 +Wall 76,4 +Wall 77,4 +Wall 78,4 +Wall 79,4 +Wall 80,4 +Wall 81,4 +Wall 82,4 +Wall 83,4 +Wall 84,4 +Wall 85,4 +Wall 86,4 +Wall 87,4 +Wall 88,4 +Wall 89,4 +Wall 90,4 +Wall 91,4 +Wall 92,4 +Wall 93,4 +Wall 94,4 +Wall 95,4 +Wall 96,4 +Wall 97,4 +Wall 98,4 +Wall 99,4 +Wall 106,4 +Wall 35,5 +Wall 36,5 +Wall 104,5 +Wall 106,5 +Wall 12,6 +Wall 13,6 +Wall 14,6 +Wall 15,6 +Wall 16,6 +Wall 17,6 +Wall 18,6 +Wall 19,6 +Wall 20,6 +Wall 21,6 +Wall 22,6 +Wall 23,6 +Wall 24,6 +Wall 25,6 +Wall 26,6 +Wall 27,6 +Wall 28,6 +Wall 29,6 +Wall 30,6 +Wall 31,6 +Wall 32,6 +Wall 33,6 +Wall 34,6 +Wall 35,6 +Wall 106,6 +Wall 4,7 +Wall 5,7 +Wall 6,7 +Wall 7,7 +Wall 8,7 +Wall 9,7 +Wall 10,7 +Wall 11,7 +Wall 12,7 +Wall 106,7 +Wall 3,8 +Wall 4,8 +Wall 102,8 +Wall 103,8 +Wall 106,8 +Wall 3,9 +Wall 106,9 +Wall 3,10 +Wall 99,10 +Wall 100,10 +Wall 106,10 +Wall 3,11 +Wall 106,11 +Wall 3,12 +Wall 104,12 +Wall 105,12 +Wall 106,12 +Wall 2,13 +Wall 3,13 +Wall 25,13 +Wall 26,13 +Wall 27,13 +Wall 28,13 +Wall 29,13 +Wall 30,13 +Wall 31,13 +Wall 36,13 +Wall 37,13 +Wall 42,13 +Wall 43,13 +Wall 47,13 +Wall 48,13 +Wall 49,13 +Wall 53,13 +Wall 54,13 +Wall 60,13 +Wall 61,13 +Wall 62,13 +Wall 63,13 +Wall 64,13 +Wall 65,13 +Wall 66,13 +Wall 67,13 +Wall 68,13 +Wall 69,13 +Wall 74,13 +Wall 75,13 +Wall 87,13 +Wall 88,13 +Wall 89,13 +Wall 95,13 +Wall 96,13 +Wall 97,13 +Wall 98,13 +Wall 99,13 +Wall 100,13 +Wall 101,13 +Wall 102,13 +Wall 103,13 +Wall 104,13 +Wall 0,14 +Wall 1,14 +Wall 2,14 +Wall 22,14 +Wall 23,14 +Wall 24,14 +Wall 25,14 +Wall 31,14 +Wall 60,14 +Wall 70,14 +Wall 95,14 +Wall 22,15 +Wall 31,15 +Spike 32,15 +Lava 33,15 +Spike 34,15 +Spike 35,15 +Spike 36,15 +Spike 37,15 +Spike 38,15 +Lava 39,15 +Spike 40,15 +Spike 41,15 +Spike 42,15 +Spike 43,15 +Spike 44,15 +Lava 45,15 +Spike 46,15 +Spike 47,15 +Spike 48,15 +Spike 49,15 +Spike 50,15 +Lava 51,15 +Spike 52,15 +Spike 53,15 +Spike 54,15 +Spike 55,15 +Spike 56,15 +Lava 57,15 +Spike 58,15 +Spike 59,15 +Wall 60,15 +Wall 70,15 +Spike 71,15 +Spike 72,15 +Lava 73,15 +Spike 74,15 +Spike 75,15 +Lava 76,15 +Spike 77,15 +Spike 78,15 +Spike 79,15 +Spike 80,15 +Spike 81,15 +Spike 82,15 +Spike 83,15 +Lava 84,15 +Spike 85,15 +Spike 86,15 +Lava 87,15 +Spike 88,15 +Lava 89,15 +Spike 90,15 +Spike 91,15 +Lava 92,15 +Wall 93,15 +Wall 94,15 +Wall 95,15 +Wall 20,16 +Wall 21,16 +Wall 22,16 +Wall 31,16 +Wall 32,16 +Wall 33,16 +Wall 34,16 +Wall 35,16 +Wall 36,16 +Wall 37,16 +Wall 38,16 +Wall 39,16 +Wall 40,16 +Wall 41,16 +Wall 42,16 +Wall 43,16 +Wall 44,16 +Wall 45,16 +Wall 46,16 +Wall 47,16 +Wall 48,16 +Wall 49,16 +Wall 50,16 +Wall 51,16 +Wall 52,16 +Wall 53,16 +Wall 54,16 +Wall 55,16 +Wall 56,16 +Wall 57,16 +Wall 58,16 +Wall 59,16 +Wall 60,16 +Wall 70,16 +Wall 71,16 +Wall 72,16 +Wall 73,16 +Wall 74,16 +Wall 75,16 +Wall 76,16 +Wall 77,16 +Wall 78,16 +Wall 79,16 +Wall 80,16 +Wall 81,16 +Wall 82,16 +Wall 83,16 +Wall 84,16 +Wall 85,16 +Wall 86,16 +Wall 87,16 +Wall 88,16 +Wall 89,16 +Wall 90,16 +Wall 91,16 +Wall 92,16 +Wall 93,16 +Player 4,17 +Wall 20,17 +Wall 21,17 +Wall 0,18 +Wall 1,18 +Wall 2,18 +Wall 3,18 +Wall 4,18 +Wall 5,18 +Wall 6,18 +Wall 7,18 +Wall 8,18 +Wall 9,18 +Wall 10,18 +Wall 11,18 +Wall 12,18 +Wall 13,18 +Wall 14,18 +Wall 20,18 +Wall 21,18 +Wall 14,19 +Wall 20,19 +Wall 21,19 +Wall 14,20 +Wall 20,20 +Wall 21,20 +Wall 14,21 +Lava 15,21 +Lava 16,21 +Lava 17,21 +Lava 18,21 +Lava 19,21 +Wall 20,21 +Wall 21,21 +Wall 14,22 +Wall 15,22 +Wall 16,22 +Wall 17,22 +Wall 18,22 +Wall 19,22 +Wall 20,22 +Wall 21,22 +LevelTrans 0,17 transition:1-4 +LevelTrans 0,17 transition:1-4 +LevelTrans 0,15 transition:1-4 +LevelTrans 104,1 transition:1-6 +LevelTrans 105,0 transition:1-6 +LevelTrans 106,1 transition:1-6 diff --git a/game/resource/levels/1-6.spm b/game/resource/levels/1-6.spm new file mode 100644 index 0000000..1c2eb9d --- /dev/null +++ b/game/resource/levels/1-6.spm @@ -0,0 +1,322 @@ +Platform +51 +51 +Wall 12,0 +Wall 13,0 +Wall 14,0 +Wall 15,0 +Wall 16,0 +Wall 17,0 +Wall 18,0 +Wall 19,0 +Wall 20,0 +Wall 21,0 +Wall 22,0 +Wall 23,0 +Wall 24,0 +Wall 25,0 +Wall 26,0 +Wall 27,0 +Wall 28,0 +Wall 29,0 +Wall 30,0 +Wall 31,0 +Wall 32,0 +Wall 33,0 +Wall 34,0 +Wall 35,0 +Wall 36,0 +Wall 37,0 +Wall 38,0 +Wall 39,0 +Wall 40,0 +Wall 41,0 +Wall 42,0 +Wall 43,0 +Wall 44,0 +Wall 45,0 +Wall 46,0 +Wall 47,0 +Wall 48,0 +Wall 49,0 +Wall 12,1 +Wall 49,1 +Wall 12,2 +Wall 49,2 +Wall 12,3 +Wall 49,3 +Wall 12,4 +Wall 49,4 +Wall 12,5 +Key 21,5 +Wall 49,5 +Wall 12,6 +Wall 20,6 +Wall 21,6 +Wall 22,6 +Wall 49,6 +Wall 12,7 +Wall 13,7 +Wall 14,7 +Wall 15,7 +Wall 16,7 +Wall 17,7 +Wall 18,7 +Wall 19,7 +Wall 20,7 +Wall 21,7 +Wall 22,7 +Wall 23,7 +Wall 24,7 +Wall 25,7 +Wall 26,7 +Wall 27,7 +Wall 28,7 +Wall 29,7 +Wall 30,7 +Wall 31,7 +Wall 32,7 +Wall 33,7 +Wall 34,7 +Wall 35,7 +Wall 49,7 +Wall 12,8 +Wall 35,8 +Wall 36,8 +Wall 37,8 +Wall 38,8 +Wall 39,8 +Wall 40,8 +Wall 49,8 +Wall 12,9 +Wall 49,9 +Wall 12,10 +Wall 49,10 +Wall 12,11 +Wall 49,11 +Wall 12,12 +Wall 40,12 +Wall 41,12 +Wall 42,12 +Wall 43,12 +Wall 44,12 +Wall 45,12 +Wall 46,12 +Wall 47,12 +Wall 48,12 +Wall 49,12 +Wall 12,13 +Wall 39,13 +Wall 12,14 +Wall 38,14 +Wall 12,15 +Wall 35,15 +Wall 36,15 +Wall 37,15 +Wall 12,16 +Wall 33,16 +Wall 34,16 +Wall 35,16 +Wall 12,17 +Wall 35,17 +Wall 12,18 +Wall 35,18 +Wall 5,19 +Wall 6,19 +Wall 7,19 +Wall 8,19 +Wall 9,19 +Wall 10,19 +Wall 11,19 +Wall 12,19 +Spike 23,19 +Wall 28,19 +Wall 29,19 +Wall 35,19 +Wall 5,20 +Wall 23,20 +Wall 24,20 +Wall 25,20 +Wall 35,20 +Wall 5,21 +Skeleton 12,21 +Wall 35,21 +Wall 36,21 +Wall 37,21 +Wall 38,21 +Wall 39,21 +Wall 40,21 +Wall 41,21 +Wall 42,21 +Wall 5,22 +Wall 12,22 +Wall 17,22 +Wall 18,22 +Wall 19,22 +Wall 42,22 +Wall 5,23 +Wall 6,23 +Wall 7,23 +Wall 8,23 +Wall 9,23 +Wall 10,23 +Wall 11,23 +Wall 12,23 +Skeleton 35,23 +Wall 42,23 +Wall 12,24 +Wall 35,24 +Wall 42,24 +Wall 12,25 +Spike 24,25 +Wall 35,25 +Wall 36,25 +Wall 37,25 +Wall 38,25 +Wall 39,25 +Wall 40,25 +Wall 41,25 +Wall 42,25 +Wall 4,26 +Wall 5,26 +Wall 6,26 +Wall 7,26 +Wall 8,26 +Wall 9,26 +Wall 10,26 +Wall 11,26 +Wall 12,26 +Spike 22,26 +Wall 24,26 +Wall 25,26 +Wall 26,26 +Wall 35,26 +Wall 4,27 +Wall 35,27 +Wall 4,28 +Skeleton 10,28 +Wall 35,28 +Wall 4,29 +Wall 10,29 +Wall 18,29 +Wall 19,29 +Wall 20,29 +Wall 35,29 +Wall 4,30 +Wall 5,30 +Wall 6,30 +Wall 7,30 +Wall 8,30 +Wall 9,30 +Wall 10,30 +Wall 35,30 +Wall 10,31 +Wall 35,31 +Wall 10,32 +Wall 35,32 +Wall 10,33 +Wall 25,33 +Wall 26,33 +Wall 27,33 +Wall 35,33 +Wall 10,34 +Wall 35,34 +Wall 10,35 +Wall 35,35 +Wall 36,35 +Wall 37,35 +Wall 38,35 +Wall 39,35 +Wall 40,35 +Wall 41,35 +Wall 42,35 +Wall 43,35 +Wall 10,36 +Wall 20,36 +Wall 21,36 +Wall 22,36 +Wall 43,36 +Wall 8,37 +Wall 9,37 +Wall 10,37 +Skeleton 36,37 +Wall 43,37 +Wall 7,38 +Wall 8,38 +Wall 36,38 +Wall 43,38 +Wall 5,39 +Wall 6,39 +Wall 7,39 +Wall 36,39 +Wall 37,39 +Wall 38,39 +Wall 39,39 +Wall 40,39 +Wall 41,39 +Wall 42,39 +Wall 43,39 +Wall 5,40 +Wall 28,40 +Wall 29,40 +Wall 30,40 +Wall 36,40 +Wall 4,41 +Wall 5,41 +Wall 36,41 +Wall 3,42 +Wall 4,42 +Wall 16,42 +Wall 17,42 +Wall 18,42 +Wall 36,42 +Wall 3,43 +Wall 36,43 +Wall 2,44 +Wall 3,44 +Wall 12,44 +Wall 13,44 +Wall 14,44 +Wall 15,44 +Wall 16,44 +Wall 17,44 +Wall 18,44 +Wall 19,44 +Wall 20,44 +Wall 21,44 +Wall 22,44 +Wall 23,44 +Wall 24,44 +Wall 25,44 +Wall 26,44 +Wall 27,44 +Wall 28,44 +Wall 29,44 +Wall 30,44 +Wall 31,44 +Wall 32,44 +Wall 33,44 +Wall 34,44 +Wall 35,44 +Wall 36,44 +Wall 1,45 +Wall 2,45 +Wall 12,45 +Wall 0,46 +Wall 1,46 +Wall 9,46 +Wall 10,46 +Wall 11,46 +Wall 12,46 +Wall 9,47 +Player 3,48 +Wall 6,48 +Wall 7,48 +Wall 8,48 +Wall 9,48 +Wall 3,49 +Wall 4,49 +Wall 5,49 +Wall 6,49 +LevelTrans 2,48 transition:1-5 +LevelTrans 2,47 transition:1-5 diff --git a/game/resource/levels/1-f.spm b/game/resource/levels/1-f.spm new file mode 100644 index 0000000..edf228e --- /dev/null +++ b/game/resource/levels/1-f.spm @@ -0,0 +1,128 @@ +Platform +51 +11 +Wall 0,0 +Wall 2,0 +Wall 3,0 +Wall 4,0 +Wall 5,0 +Wall 6,0 +Wall 7,0 +Wall 8,0 +Wall 9,0 +Wall 10,0 +Wall 11,0 +Wall 12,0 +Wall 13,0 +Wall 14,0 +Wall 15,0 +Wall 16,0 +Wall 17,0 +Wall 18,0 +Wall 19,0 +Wall 20,0 +Wall 21,0 +Wall 22,0 +Wall 23,0 +Wall 24,0 +Wall 25,0 +Wall 26,0 +Wall 27,0 +Wall 28,0 +Wall 29,0 +Wall 30,0 +Wall 31,0 +Wall 32,0 +Wall 33,0 +Wall 34,0 +Wall 35,0 +Wall 36,0 +Wall 37,0 +Wall 38,0 +Wall 39,0 +Wall 40,0 +Wall 41,0 +Wall 42,0 +Wall 43,0 +Wall 44,0 +Wall 45,0 +Wall 46,0 +Wall 47,0 +Wall 48,0 +Wall 49,0 +Wall 0,1 +Wall 2,1 +Wall 49,1 +Wall 0,2 +Wall 1,2 +Wall 2,2 +Wall 49,2 +Wall 0,3 +Wall 2,3 +Wall 49,3 +Wall 0,4 +Wall 1,4 +Wall 2,4 +Wall 49,4 +Wall 0,5 +Wall 2,5 +Wall 49,5 +Wall 0,6 +Wall 1,6 +Wall 2,6 +Wall 49,6 +Wall 0,7 +Wall 2,7 +Player 3,7 +Boss 45,7 +Wall 49,7 +Wall 0,8 +Wall 1,8 +Wall 2,8 +Wall 3,8 +Wall 4,8 +Wall 5,8 +Wall 6,8 +Wall 7,8 +Wall 8,8 +Wall 9,8 +Wall 10,8 +Wall 11,8 +Wall 12,8 +Wall 13,8 +Wall 14,8 +Wall 15,8 +Wall 16,8 +Wall 17,8 +Wall 18,8 +Wall 19,8 +Wall 20,8 +Wall 21,8 +Wall 22,8 +Wall 23,8 +Wall 24,8 +Wall 25,8 +Wall 26,8 +Wall 27,8 +Wall 28,8 +Wall 29,8 +Wall 30,8 +Wall 31,8 +Wall 32,8 +Wall 33,8 +Wall 34,8 +Wall 35,8 +Wall 36,8 +Wall 37,8 +Wall 38,8 +Wall 39,8 +Wall 40,8 +Wall 41,8 +Wall 42,8 +Wall 43,8 +Wall 44,8 +Wall 45,8 +Wall 46,8 +Wall 47,8 +Wall 48,8 +Wall 49,8 diff --git a/game/resource/levels/a-f.spm b/game/resource/levels/a-f.spm new file mode 100644 index 0000000..9515b11 --- /dev/null +++ b/game/resource/levels/a-f.spm @@ -0,0 +1,53 @@ +Roguelike +31 +11 +Wall 0,0 +Wall 2,0 +Wall 0,1 +Wall 2,1 +Wall 0,2 +Wall 1,2 +Wall 2,2 +Wall 0,3 +Wall 2,3 +Wall 0,4 +Wall 1,4 +Wall 2,4 +Wall 0,5 +Wall 2,5 +Wall 0,6 +Wall 1,6 +Wall 2,6 +Wall 0,7 +Wall 2,7 +Player 3,7 +Wall 0,8 +Wall 1,8 +Wall 2,8 +Wall 3,8 +Wall 4,8 +Wall 5,8 +Wall 6,8 +Wall 7,8 +Wall 8,8 +Wall 9,8 +Wall 10,8 +Wall 11,8 +Wall 12,8 +Wall 13,8 +Wall 14,8 +Wall 15,8 +Wall 16,8 +Wall 17,8 +Wall 18,8 +Wall 19,8 +Wall 20,8 +Wall 21,8 +Wall 22,8 +Wall 23,8 +Wall 24,8 +Wall 25,8 +Wall 26,8 +Wall 27,8 +Wall 28,8 +Wall 29,8 diff --git a/graphics/camera.cpp b/graphics/camera.cpp index f7fab28..1591ba5 100644 --- a/graphics/camera.cpp +++ b/graphics/camera.cpp @@ -1,21 +1,60 @@ #include "camera.h" #include "rendering.h" #include "screen.h" +#include "logger.h" +#include "event.h" + +extern SpylikeLogger LOGGER; Camera::Camera(TerminalScreen& screen, int width, int height, std::vector layers) : width{width}, height{height}, TextRenderManager(screen, layers) { origin = Coordinate(0,0); } void Camera::setOrigin(Coordinate pos) {origin = pos;} +void Camera::setOffset(int x, int y) {offsetX = x; offsetY = y;} Coordinate Camera::getOrigin() {return origin;} void Camera::draw(Coordinate coord, char c, std::string layerName) { - Coordinate cameraMapped(coord.x - origin.x, coord.y - origin.y); - if ((cameraMapped.x >= 0) && (cameraMapped.y >= 0)) { - if ((cameraMapped.x <= width) && (cameraMapped.y <= height)) { - TextRenderManager::draw(cameraMapped, c, layerName); + if (!absolute) { + Coordinate cameraMapped(coord.x - origin.x + offsetX, coord.y - origin.y + offsetY); + if ((cameraMapped.x >= 0) && (cameraMapped.y >= 0)) { + if ((cameraMapped.x <= width) && (cameraMapped.y <= height)) { + TextRenderManager::draw(cameraMapped, c, layerName); + } } } + else drawAbsolute(coord, c, layerName); +} + +void Camera::drawAbsolute(Coordinate coord, char c, std::string layerName) { + TextRenderManager::draw(coord, c, layerName); +} + +void Camera::toggleAbsolute() { + if (absolute) absolute = false; + else absolute = true; +} + +void Camera::on_event(Event& e) { + if (e.type == "CAMERA_MoveUp") { + SpylikeEvents::CameraEvent& ce = dynamic_cast(e); + if ((origin.y - ce.pos.y) < 5) { + //setOrigin(ce.pos); + } + } + if (e.type == "CAMERA_MoveDown") { + setOrigin(Coordinate(origin.x, origin.y+1)); + } + if (e.type == "CAMERA_MoveRight") { + setOrigin(Coordinate(origin.x+1, origin.y)); + } + if (e.type == "CAMERA_MoveLeft") { + setOrigin(Coordinate(origin.x-1, origin.y)); + } + if (e.type == "CAMERA_Move") { + SpylikeEvents::CameraEvent& ce = dynamic_cast(e); + setOrigin(ce.pos); + } } diff --git a/graphics/rendering.cpp b/graphics/rendering.cpp index d043121..cae45c0 100644 --- a/graphics/rendering.cpp +++ b/graphics/rendering.cpp @@ -7,6 +7,7 @@ #include #include #include +#include extern SpylikeLogger LOGGER; @@ -22,14 +23,20 @@ TextRenderManager::TextRenderManager(TerminalScreen& scr, std::vector manager.getScreenWidth()) { + if (c == '\n') { currentPos.y += 1; currentPos.x = pos.x; } manager.draw(currentPos, c, layerName); currentPos.x += 1; } - manager.renderToScreen(); } @@ -144,7 +156,6 @@ void GeometryRenderer::drawLine(Coordinate p1, Coordinate p2, char c, std::strin } currentPos.x += signX; } - manager.renderToScreen(); } void GeometryRenderer::drawBox(Coordinate p1, Coordinate p2, std::string layerName) { @@ -155,3 +166,11 @@ void GeometryRenderer::drawBox(Coordinate p1, Coordinate p2, std::string layerNa drawLine(p2, p3, '-', layerName); drawLine(p1, p4, '-', layerName); } + +int GeometryRenderer::getScreenWidth() { + return manager.getScreenWidth(); +} + +int GeometryRenderer::getScreenHeight() { + return manager.getScreenHeight(); +} diff --git a/graphics/screen.cpp b/graphics/screen.cpp index 4b6c8b6..60995c1 100644 --- a/graphics/screen.cpp +++ b/graphics/screen.cpp @@ -6,9 +6,8 @@ extern SpylikeLogger LOGGER; -NcursesTerminalScreen::NcursesTerminalScreen(int w, int h) { - width = w; - height = h; +NcursesTerminalScreen::NcursesTerminalScreen(int w, int h) : TerminalScreen(w, h) { + setlocale(LC_ALL, "en_US.UTF-8"); initscr(); cbreak(); noecho(); @@ -21,16 +20,18 @@ NcursesTerminalScreen::NcursesTerminalScreen(int w, int h) { void NcursesTerminalScreen::write(int x, int y, char c) { assert(x <= width); - assert(y <= height); - mvwaddch(win, y, x, c); - wrefresh(win); + assert(y <= height); + mvwaddch(win, y, x, c); } void NcursesTerminalScreen::write(int x, int y, std::string s) { assert(x <= width); assert(y <= height); const char* charStr = s.c_str(); - mvwprintw(win, y, x, charStr); + mvwaddstr(win, y, x, charStr); +} + +void NcursesTerminalScreen::update() { wrefresh(win); } @@ -102,7 +103,6 @@ char* NcursesTerminalScreen::read(int x, int y) { void NcursesTerminalScreen::clear() { werase(win); - wrefresh(win); } void NcursesTerminalScreen::end() { diff --git a/include/audio.h b/include/audio.h new file mode 100644 index 0000000..99c05c1 --- /dev/null +++ b/include/audio.h @@ -0,0 +1,36 @@ +#ifndef SPYLIKE_AUDIO_H +#define SPYLIKE_AUDIO_H + +#include "miniaudio.h" +#include "event.h" +#include + +class AudioManager : public EventHandler { + protected: + bool playing = false; + public: + virtual void playMusic(std::string track, float volume=50) = 0; + virtual void playSound(std::string sound, float volume=50) = 0; + virtual void stopMusic() = 0; + virtual void pauseMusic() = 0; + virtual void resumeMusic() = 0; + virtual void setMusicVolume(float volume) = 0; + void on_event(Event& e); + bool isPlaying() { return playing; } +}; + +class MiniaudioManager : public AudioManager { + std::string rootPath; + ma_engine* engine = nullptr; + ma_sound* cMusic = nullptr; + public: + MiniaudioManager(std::string rootPath); + void playMusic(std::string track, float volume=0.5) override; + void playSound(std::string sound, float volume=0.5) override; + void stopMusic() override; + void pauseMusic() override; + void resumeMusic() override; + void setMusicVolume(float volume) override; +}; + +#endif diff --git a/include/camera.h b/include/camera.h index 8e1ab48..f752353 100644 --- a/include/camera.h +++ b/include/camera.h @@ -4,18 +4,29 @@ #include "rendering.h" #include "levelmap.h" #include "screen.h" +#include "event.h" #include -class Camera : public TextRenderManager { +class Camera : public TextRenderManager, public EventHandler { Coordinate origin; int width; int height; + int offsetX=0; + int offsetY=0; + bool absolute=false; public: Camera(TerminalScreen& screen, int width, int height, std::vector layers); void setOrigin(Coordinate pos); + void setOffset(int x, int y); + int getXOffset() { return offsetX; } + int getYOffset() { return offsetY; } Coordinate getOrigin(); - virtual void draw(Coordinate coord, char c, std::string layerName); + // takes a world coordinate, converts to camera coordinates and draws. + void draw(Coordinate coord, char c, std::string layerName) override; + void drawAbsolute(Coordinate coord, char c, std::string layerName); + void toggleAbsolute(); + void on_event(Event& e) override; }; #endif diff --git a/include/entity.h b/include/entity.h index 739d58a..5d0dcd0 100644 --- a/include/entity.h +++ b/include/entity.h @@ -12,7 +12,7 @@ class ManagedEntity : public TileEntity { TileEntity& parent; void on_update(); void draw(GeometryRenderer& painter); - void on_event(Event e); + void on_event(Event& e); }; class ManagerEntity : public TileEntity { diff --git a/include/event.h b/include/event.h index 1a0967f..b06d895 100644 --- a/include/event.h +++ b/include/event.h @@ -5,6 +5,7 @@ #include #include #include +#include "rendering.h" struct Event { virtual ~Event() = default; @@ -25,11 +26,42 @@ namespace SpylikeEvents { char c; KeyInputEvent(std::string type, char c) : Event(type), c(c) {} }; + + struct CameraEvent : Event { + Coordinate pos; + CameraEvent(std::string type, Coordinate pos=Coordinate(0,0)) : Event(type), pos{pos} {} + }; struct MenuButtonEvent : Event { std::string buttonID; MenuButtonEvent(std::string type, std::string buttonID) : Event(type), buttonID(buttonID) {} }; + + struct MenuEvent : Event { + std::string menuID; + MenuEvent(std::string type, std::string menuID) : Event(type), menuID(menuID) {} + }; + + struct LevelChangeEvent : Event { + std::string levelPath; + LevelChangeEvent(std::string type, std::string levelPath) : Event(type), levelPath(levelPath) {} + }; + + struct PlayerHurtEvent : Event { + int health; + PlayerHurtEvent(std::string type, int health) : Event(type), health(health) {} + }; + + struct DoorResponseEvent : Event { + bool res; + DoorResponseEvent(std::string type, bool res) : Event(type), res(res) {} + }; + + struct AudioPlayEvent : Event { + std::string sound; + float volume; + AudioPlayEvent(std::string type, std::string sound, float volume=0.5) : Event(type), sound(sound), volume(volume) {} + }; } class EventHandler { @@ -41,7 +73,9 @@ class EventManager { std::map>> eventSubscribers; public: void subscribe(std::shared_ptr handler, std::string eventType); + void unsubscribe(std::shared_ptr handler); void emit(Event& event); + void clear() { eventSubscribers = {}; } }; #endif diff --git a/include/game.h b/include/game.h index 959bd0a..2bf49d2 100644 --- a/include/game.h +++ b/include/game.h @@ -1,3 +1,141 @@ -namespace Game { - void run(); +#ifndef SPYLIKE_THEGAME_H +#define SPYLIKE_THEGAME_H + +#include "scheduling.h" +#include "event.h" +#include "rendering.h" +#include "camera.h" +#include "levelmap.h" +#include "input.h" +#include "screen.h" +#include "menus.h" +#include "geometry.h" +#include "character.h" +#include "obstacle.h" +#include "logger.h" +#include "misc.h" +#include "audio.h" + +#include +#include +#include + +extern SpylikeLogger LOGGER; + +inline Level load_from_file(std::string path) { + LOGGER.log("Reading level from " + path, DEBUG); + std::ifstream input(path); + std::string levelTypeStr; + WorldType levelType; + getline(input, levelTypeStr); + if (levelTypeStr == "Roguelike") levelType = WorldType::Roguelike; + else levelType = WorldType::Platform; + std::string widthStr; + std::string heightStr; + int width; + int height; + getline(input, widthStr); + getline(input, heightStr); + width = std::stoi(widthStr); + height = std::stoi(heightStr); + std::map, Coordinate> entities = {}; + std::string entLine; + while (getline(input, entLine)) { + std::string name; + int idx = 0; + while(entLine[idx] != ' ') { + name += entLine[idx]; + idx++; + } + std::shared_ptr ent; + if (name == "Player") ent = std::make_shared(); + else if (name == "Goblin") ent = std::make_shared(); + else if (name == "Spike") ent = std::make_shared(); + else if (name == "Skeleton") ent = std::make_shared(); + else if (name == "Lava") ent = std::make_shared(); + else if (name == "Key") ent = std::make_shared(); + else if (name == "Door") ent = std::make_shared(); + else if (name == "Boss") ent = std::make_shared(); + else ent = std::make_shared(); + idx++; + std::string entXStr; + while (entLine[idx] != ',') { + entXStr += entLine[idx]; + idx++; + } + int entX = std::stoi(entXStr); + idx++; + std::string entYStr; + while (idx != entLine.length() && entLine[idx] != ' ') { + entYStr += entLine[idx]; + idx++; + } + int entY = std::stoi(entYStr); + if (name == "LevelTrans") { + while (entLine[idx] != ':') idx++; + idx++; + std::string levelPath; + while (idx < entLine.length() && entLine[idx] != ' ') { + levelPath += entLine[idx]; + idx++; + } + levelPath = "game/resource/levels/" + levelPath + ".spm"; + ent = std::make_shared(levelPath); + } + entities[ent] = Coordinate(entX, entY); + } + return Level(levelType, width, height, entities); } + +class GameManager : public EventHandler, public std::enable_shared_from_this { + bool paused; + FrameScheduler scheduler = FrameScheduler(20); + NcursesTerminalScreen screen = NcursesTerminalScreen(80, 30); + std::shared_ptr eventManager; + std::shared_ptr menuEventManager; + std::shared_ptr inputManager; + std::shared_ptr menuInputManager; + std::shared_ptr camera; + std::shared_ptr menuManager; + GeometryRenderer* gameRenderer; + GeometryRenderer* menuRenderer; + std::shared_ptr activeMenu; + std::shared_ptr map; + std::shared_ptr audioManager; + int playerHealth = 100; + bool keyCollected = false; + class RunLevelTask : public ScheduledTask { + GameManager& manager; + public: + RunLevelTask(GameManager& manager) : ScheduledTask("RunLevel"), manager{manager} {} + void update() override; + }; + class TickTask : public ScheduledTask { + GameManager& manager; + public: + TickTask(GameManager& manager) : ScheduledTask("RunTick"), manager{manager} {} + void update() override; + }; + class MenuTask : public ScheduledTask { + GameManager& manager; + public: + MenuTask(GameManager& manager) : ScheduledTask("MenuTask"), manager{manager} {} + void update() override; + }; + class StartupTask : public ScheduledTask { + GameManager& manager; + public: + StartupTask(GameManager& manager) : ScheduledTask("StartupTask"), manager{manager} {} + void update() override; + }; + public: + void loadLevel(Level level); + void pause(); + void quit(); + void run(); + void showMenu(std::shared_ptr menu, bool pause=true); + void closeMenu(); + void on_event(Event& e) override; +}; + +#endif diff --git a/include/levelmap.h b/include/levelmap.h index 6d499b5..fb68c27 100644 --- a/include/levelmap.h +++ b/include/levelmap.h @@ -3,9 +3,13 @@ #include "objects.h" #include "rendering.h" +#include "logger.h" #include #include #include +#include + +extern SpylikeLogger LOGGER; class Tile; class TileEntity; @@ -16,32 +20,45 @@ struct IDBlock { int endID; }; +typedef int EntityID; + +enum WorldType { Roguelike, Platform }; + class TileEntity : public SpritedObject, public std::enable_shared_from_this { std::shared_ptr parent = nullptr; std::vector> children; + bool alive = true; protected: std::shared_ptr world; public: + // getter/setter + const bool isCollidable; const Tile* tile = nullptr; void setTile(Tile* tileObj); void setParent(std::shared_ptr ent); void removeParent(); std::shared_ptr getParent(); + // check if removing tile removes children void addChild(std::shared_ptr ent); - void removeChild(int entityID); + void removeChild(EntityID entityID); std::vector> getChildren(); - bool isAlive = true; void registerWorld(std::shared_ptr levelMap); + void kill(); + bool isAlive(); + Coordinate getPos(); + virtual void on_collide(std::shared_ptr collider) {} + TileEntity(bool collidable) : isCollidable{collidable} {} }; class Tile { std::vector> entities; public: Tile(Coordinate pos) : pos(pos) {} - Coordinate pos; + // why can we change pos + const Coordinate pos; std::vector> getEntities(); void addEntity(std::shared_ptr ent); - void removeEntity(int entityID); + void removeEntity(EntityID entityID); }; @@ -57,21 +74,40 @@ class LevelMap : public std::enable_shared_from_this { public: const int width; const int height; - LevelMap(int width, int height, std::shared_ptr eventManager, IDBlock idRange); + const WorldType worldType; + LevelMap(int width, int height, std::shared_ptr eventManager, IDBlock idRange, WorldType wt=Platform); std::shared_ptr getTile(Coordinate coord); - void putEntity(std::shared_ptr ent, Coordinate coord); + void updateTile(Coordinate coord); + void drawTile(Coordinate coord, GeometryRenderer& painter); void destroyTile(Coordinate coord); std::shared_ptr findEntity(int entityID); - std::shared_ptr removeEntity(int entityID); //returns pointer to entity that was removed if it exists + // Searches for entities of type Ent within range tiles of origin and returns them. If range=0, searches everything + template std::vector> findEntities(Coordinate origin=Coordinate(0,0), int range=0) { + std::vector> res; + for (auto entPair : trackedEntities) { + if ((range <= 0) || (sqrt(pow(origin.x - entPair.second.x, 2) + pow(origin.y - entPair.second.y, 2)) <= range)) { + std::shared_ptr ent = findEntity(entPair.first); + std::shared_ptr specialized = std::dynamic_pointer_cast(ent); + if (specialized) res.push_back(std::dynamic_pointer_cast(ent)); + } + } + return res; + } + void putEntity(std::shared_ptr ent, Coordinate coord); void removeEntity(std::shared_ptr ent); - void moveEntity(int entityID, Coordinate pos); - void moveEntity(std::shared_ptr ent, Coordinate pos); - void updateTile(Coordinate coord); - void drawTile(Coordinate coord, GeometryRenderer& camera); + // Returns true if entity successfully moved, false otherwise (e.g. collision) + bool moveEntity(std::shared_ptr ent, Coordinate pos); + bool moveEntity(EntityID entityID, Coordinate pos); bool isInMap(Coordinate coord); //convenience function void registerEntity(std::shared_ptr ent, Coordinate pos); - Coordinate getEntityPos(int entityID); }; +struct Level { + WorldType worldType; + int width; + int height; + std::map, Coordinate> entities; + Level(WorldType wt, int w, int h, std::map, Coordinate> entities) : worldType{wt}, width{w}, height{h}, entities{entities} {} +}; #endif diff --git a/include/logger.h b/include/logger.h index 72fa59b..7426f7d 100644 --- a/include/logger.h +++ b/include/logger.h @@ -1,6 +1,9 @@ #ifndef SPYLIKE_LOGGING_H #define SPYLIKE_LOGGING_H +#include +#include +#include #include enum LogLevel {DEBUG=0, ERROR=1, CRITICAL=2}; @@ -8,7 +11,25 @@ enum LogLevel {DEBUG=0, ERROR=1, CRITICAL=2}; class SpylikeLogger { public: SpylikeLogger(std::string fname, LogLevel level); - void log(std::string text, LogLevel level); + template void log(T text, LogLevel level) { + if (loglevel >= level) { + std::string levelname; + switch(level) { + case DEBUG: + levelname = "DEBUG"; + break; + case ERROR: + levelname = "ERROR"; + break; + case CRITICAL: + levelname = "CRITICAL"; + break; + } + std::ofstream logfile {filename, std::ios_base::app}; + logfile << '[' << levelname << "] " << text << std::endl; + logfile.close(); + } + } private: std::string filename; LogLevel loglevel; diff --git a/include/menus.h b/include/menus.h deleted file mode 100644 index c0c1679..0000000 --- a/include/menus.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef SPYLIKE_MENUS_H -#define SPYLIKE_MENUS_H - -#include "levelmap.h" -#include "event.h" -#include -#include -#include -#include - -class MenuButton : public TileEntity { - void on_update(); - void on_event(Event& e); - int width; - int height; - std::string text; - std::string buttonID; - public: - MenuButton(int width, int height, std::string buttonText, std::string buttonID); - void draw(GeometryRenderer& painter); - void click(); - std::string getButtonID(); -}; - -class Menu : public TileEntity { - int width; - int height; - std::map> buttons; - std::queue selectionList; - std::string currentSelection = ""; - void on_update(); - void on_event(Event& e); - public: - Menu(int width, int height): width{width}, height{height} {} - void draw(GeometryRenderer& painter); - void addButton(std::shared_ptr button, Coordinate pos); - void setSelection(std::string buttonID); - void selectNext(); - void click(); -}; - - -#endif diff --git a/include/objects.h b/include/objects.h index dcd21b8..12e3dd2 100644 --- a/include/objects.h +++ b/include/objects.h @@ -12,7 +12,9 @@ class Object : public EventHandler { std::shared_ptr eventManager; virtual void on_event(Event& e) = 0; virtual void on_update() = 0; + virtual void on_init() {} int ID = -1; + void registerEventManager(std::shared_ptr manager); public: void update() { assert(eventManager); @@ -23,9 +25,12 @@ class Object : public EventHandler { assert(eventManager); on_event(e); }; + void init(std::shared_ptr manager) { + registerEventManager(manager); + on_init(); + } void setID(int objID); int getID(); - void registerEventManager(std::shared_ptr manager); }; class SpritedObject : public Object { diff --git a/include/rendering.h b/include/rendering.h index 8950418..538dda8 100644 --- a/include/rendering.h +++ b/include/rendering.h @@ -2,10 +2,11 @@ #define SPYLIKE_RENDER_MANAGER_H #include "screen.h" -#include +#include #include #include #include +#include struct Coordinate { int x; @@ -26,48 +27,60 @@ struct Coordinate { int ny = y * c.y; return Coordinate(nx, ny); } + bool operator==(const Coordinate& c) const { + return ((x == c.x) && (y == c.y)); + } + bool operator!=(const Coordinate& c) const { + return ((x != c.x) || (y != c.y)); + } }; struct RenderLayer { + //const? std::string name; int priority; RenderLayer(std::string name, int priority): name(name), priority(priority) {} }; -struct coordCompare : public std::binary_function { - //TODO: literally stolen LMAO - bool operator()(const Coordinate& c1, const Coordinate& c2) const { - return (c1.x < c2.x) || ((c1.x == c2.x) && (c1.y < c2.y)); +struct coordHash { + std::size_t operator()(const Coordinate& c) const { + return (std::hash{}(c.x) ^ std::hash{}(c.y)); } }; - class TextRenderManager { TerminalScreen& screen; - typedef std::map TextLayer; - std::map layersCache; + typedef std::unordered_map TextLayer; + std::unordered_map layersCache; + //std::unordered_map toUpdate; std::vector orderedLayers; + bool locked = false; public: TextRenderManager(TerminalScreen& screen, std::vector layers); virtual void draw(Coordinate coord, char c, std::string layerName); void renderToScreen(); - void clearLayer(std::string layerName); + void clearLayer(std::string layerName); void clearCache(); void clearScreen(); - int getScreenWidth(); - int getScreenHeight(); - std::string getSnapshot(); - + void lock() { locked = true; } // prevents any new draws from taking effect - pauses the camera + void unlock() { locked = false; } + int getScreenWidth(); + int getScreenHeight(); + std::string getSnapshot(); }; class GeometryRenderer { - TextRenderManager& manager; + protected: + TextRenderManager& manager; public: GeometryRenderer(TextRenderManager& renderManager); + void draw(Coordinate coord, char c, std::string layerName); void drawString(Coordinate pos, std::string str, std::string layerName); void drawLine(Coordinate p1, Coordinate p2, char c, std::string layerName); void drawBox(Coordinate p1, Coordinate p2, std::string layerName); + int getScreenWidth(); + int getScreenHeight(); }; #endif diff --git a/include/scheduling.h b/include/scheduling.h index e738787..859e835 100644 --- a/include/scheduling.h +++ b/include/scheduling.h @@ -8,22 +8,34 @@ class ScheduledTask { public: ScheduledTask(std::string id); + // should be getter/setter std::string id; + // ditto bool running; virtual void update() = 0; }; class FrameScheduler { int maxFPS; - std::vector> tasks; + std::vector> tasks; bool runningSignal; + int usElapsed=0; public: - FrameScheduler(std::vector> tasks, int maxFPS); + FrameScheduler(int maxFPS=60) : maxFPS{maxFPS} { + runningSignal = false; + } + void addTask(std::unique_ptr t) { + tasks.push_back(std::move(t)); + } void run(); void pause(); void resume(); + // no more IDs void pauseTask(std::string taskID); + // should be starttask void resumeTask(std::string taskID); + bool isRunning(std::string taskID); + int timeElapsed(); // in seconds }; #endif diff --git a/include/screen.h b/include/screen.h index a14b94e..d76df90 100644 --- a/include/screen.h +++ b/include/screen.h @@ -12,10 +12,12 @@ struct MouseEvent { class TerminalScreen { public: - int width; - int height; + const int width; + const int height; + TerminalScreen(int w, int h) : width{w}, height{h} {} virtual void write(int x, int y, char c) = 0; virtual void write(int x, int y, std::string s) = 0; + virtual void update() = 0; //virtual char* read(int x, int y) = 0; virtual void clear() = 0; virtual void end() = 0; @@ -25,10 +27,12 @@ class TerminalScreen { class NcursesTerminalScreen : public TerminalScreen { WINDOW* win; + WINDOW* inputWin; public: NcursesTerminalScreen(int w, int h); void write(int x, int y, char c); void write(int x, int y, std::string s); + void update(); //char* read(int x, int y); char getInput(); //MouseEvent getMouse(); diff --git a/include/sprites.h b/include/sprites.h index b6f561d..6244532 100644 --- a/include/sprites.h +++ b/include/sprites.h @@ -7,6 +7,7 @@ struct SpriteDelta { int startPos; std::string data; + SpriteDelta(int startPos, std::string data) : startPos{startPos}, data{data} {} }; typedef std::vector SpriteFrame; diff --git a/include/timer.h b/include/timer.h new file mode 100644 index 0000000..1f702b6 --- /dev/null +++ b/include/timer.h @@ -0,0 +1,15 @@ +#ifndef SPYLIKE_TIMER_H +#define SPYLIKE_TIMER_H + +class Timer { + int elapsed; // in ticks + bool paused=false; + public: + void tick(); + void reset(); + void pause(); + void unpause(); + int getElapsed(); +}; + +#endif diff --git a/level/entity.cpp b/level/entity.cpp index 4873d08..a825562 100644 --- a/level/entity.cpp +++ b/level/entity.cpp @@ -6,7 +6,7 @@ void ManagedEntity::on_update() { parent.update(); } void ManagedEntity::draw(GeometryRenderer& painter) { parent.draw(painter); } -void ManagedEntity::on_event(Event e) { parent.event(e); } +void ManagedEntity::on_event(Event& e) { parent.event(e); } void ManagerEntity::createChild(Coordinate pos) { } diff --git a/level/levelmap.cpp b/level/levelmap.cpp index 4b32639..27900ae 100644 --- a/level/levelmap.cpp +++ b/level/levelmap.cpp @@ -6,6 +6,7 @@ #include #include #include +#include extern SpylikeLogger LOGGER; @@ -42,7 +43,7 @@ void TileEntity::addChild(std::shared_ptr ent) { } } -void TileEntity::removeChild(int entityID) { +void TileEntity::removeChild(EntityID entityID) { int idxToRemove = -1; for (int i=0; igetID() == entityID) { @@ -64,13 +65,26 @@ std::vector> TileEntity::getChildren() { std::vector> Tile::getEntities() { return entities; } + +void TileEntity::kill() { + world->removeEntity(shared_from_this()); + alive = false; +} + +bool TileEntity::isAlive() { + return alive; +} + +Coordinate TileEntity::getPos() { + return tile->pos; +} void Tile::addEntity(std::shared_ptr ent) { ent->setTile(this); entities.push_back(ent); } -void Tile::removeEntity(int entityID) { +void Tile::removeEntity(EntityID entityID) { int idxToRemove = -1; for (int i=0; igetID() == entityID) { @@ -83,7 +97,7 @@ void Tile::removeEntity(int entityID) { } } -LevelMap::LevelMap(int width, int height, std::shared_ptr eventManager, IDBlock idRange) : width{width}, height{height}, manager{eventManager}, idRange{idRange} { +LevelMap::LevelMap(int width, int height, std::shared_ptr eventManager, IDBlock idRange, WorldType wt) : width{width}, height{height}, manager{eventManager}, idRange{idRange}, worldType{wt} { tileMap = std::vector>(width*height); std::fill(tileMap.begin(), tileMap.end(), std::shared_ptr(nullptr)); currentID = idRange.startID; @@ -94,7 +108,7 @@ LevelMap::LevelMap(int width, int height, std::shared_ptr eventMan int LevelMap::getTileIndex(Coordinate coord) { int tileIndex = coord.x + coord.y*width; if (tileIndex > tileMap.size() - 1) { - throw std::invalid_argument("Coordinate outside of map"); + throw std::invalid_argument("Coordinate (" + std::to_string(coord.x) + "," + std::to_string(coord.y) + ") outside of map."); } return tileIndex; } @@ -104,6 +118,7 @@ std::shared_ptr LevelMap::getTile(Coordinate coord) { } void LevelMap::putEntity(std::shared_ptr ent, Coordinate coord) { + LOGGER.log("Adding entity " + std::to_string(ent->getID()) + " to coordinate (" + std::to_string(coord.x) + "," + std::to_string(coord.y) + ")", DEBUG); if (tileMap[getTileIndex(coord)] != nullptr) { tileMap[getTileIndex(coord)]->addEntity(ent); } @@ -118,6 +133,20 @@ void LevelMap::destroyTile(Coordinate coord) { tileMap[getTileIndex(coord)] = std::shared_ptr(nullptr); } +void LevelMap::removeEntity(std::shared_ptr ent) { + auto currentTile = getTile(ent->tile->pos); // entity's tile is const + if (currentTile) { + currentTile->removeEntity(ent->getID()); + if (currentTile->getEntities().size() == 0) { + destroyTile(currentTile->pos); + } + for (auto child : ent->getChildren()) { + removeEntity(child); + } + trackedEntities.erase(ent->getID()); + } +} + std::shared_ptr LevelMap::findEntity(int entityID) { if (trackedEntities.find(entityID) == trackedEntities.end()) { return nullptr; @@ -136,52 +165,57 @@ std::shared_ptr LevelMap::findEntity(int entityID) { return targetEntity; } -std::shared_ptr LevelMap::removeEntity(int entityID) { - std::shared_ptr targetEntity = findEntity(entityID); - if (targetEntity) { - removeEntity(targetEntity); - return targetEntity; - } - return nullptr; -} - -void LevelMap::removeEntity(std::shared_ptr ent) { - auto currentTile = getTile(ent->tile->pos); // entity's tile is const - if (currentTile) { - currentTile->removeEntity(ent->getID()); - if (currentTile->getEntities().size() == 0) { - destroyTile(currentTile->pos); - } - for (auto child : ent->getChildren()) { - removeEntity(child); - } - trackedEntities.erase(ent->getID()); - } -} - -void LevelMap::moveEntity(int entityID, Coordinate pos) { - std::shared_ptr ent = findEntity(entityID); - moveEntity(ent, pos); -} - -void LevelMap::moveEntity(std::shared_ptr ent, Coordinate pos) { +bool LevelMap::moveEntity(std::shared_ptr ent, Coordinate pos) { + LOGGER.log("Moving entity " + std::to_string(ent->getID()) + " to coordinate (" + std::to_string(pos.x) + "," + std::to_string(pos.y) + ")", DEBUG); if (ent) { + std::vector> movedChildren; + bool result = false; for (auto child : ent->getChildren()) { if (child->tile) { - moveEntity(child, pos + (child->tile->pos - ent->tile->pos)); + result = moveEntity(child, pos + (child->tile->pos - ent->tile->pos)); + if (result) break; + else movedChildren.push_back(child); } } - auto currentTile = getTile(ent->tile->pos); - if (currentTile) { - currentTile->removeEntity(ent->getID()); - if (currentTile->getEntities().size() == 0) { - destroyTile(currentTile->pos); + if (!result) { + auto targetTile = getTile(pos); + bool foundCollidable = false; + if (targetTile && targetTile->getEntities().size() != 0) { + for (auto occupier : targetTile->getEntities()) { + if (occupier->isCollidable) { + occupier->on_collide(ent); + if (occupier->isAlive()) ent->on_collide(occupier); + foundCollidable = true; + + } + } + } + if (!foundCollidable) { + auto currentTile = getTile(ent->tile->pos); + if (currentTile) { + currentTile->removeEntity(ent->getID()); + if (currentTile->getEntities().size() == 0) { + destroyTile(currentTile->pos); + } + trackedEntities.erase(ent->getID()); + } + putEntity(ent, pos); + trackedEntities[ent->getID()] = pos; + return true; } - trackedEntities.erase(ent->getID()); } - putEntity(ent, pos); + // We didn't succeed in moving the parent + for (auto movedChild : movedChildren) { + moveEntity(movedChild, movedChild->tile->pos - ent->tile->pos); + } + } - trackedEntities[ent->getID()] = pos; + return false; +} + +bool LevelMap::moveEntity(EntityID entityID, Coordinate pos) { + bool result = moveEntity(findEntity(entityID), pos); + return result; } void LevelMap::updateTile(Coordinate coord) { @@ -193,11 +227,11 @@ void LevelMap::updateTile(Coordinate coord) { } } -void LevelMap::drawTile(Coordinate coord, GeometryRenderer& camera) { +void LevelMap::drawTile(Coordinate coord, GeometryRenderer& painter) { std::shared_ptr tile = getTile(coord); if (tile != nullptr) { for (auto ent : tile->getEntities()) { - ent->draw(camera); + ent->draw(painter); } } } @@ -218,6 +252,7 @@ int LevelMap::getNextID() { return chosenID; } else { + LOGGER.log("Ran out of IDs!", CRITICAL); throw std::runtime_error("World has run out of its entityID allocation"); } } @@ -225,9 +260,9 @@ int LevelMap::getNextID() { void LevelMap::registerEntity(std::shared_ptr ent, Coordinate pos) { ent->registerWorld(shared_from_this()); - ent->registerEventManager(manager); ent->setID(getNextID()); trackedEntities.insert({ent->getID(), pos}); putEntity(ent, pos); + ent->init(manager); } diff --git a/lib/miniaudio b/lib/miniaudio new file mode 160000 index 0000000..4a5b74b --- /dev/null +++ b/lib/miniaudio @@ -0,0 +1 @@ +Subproject commit 4a5b74bef029b3592c54b6048650ee5f972c1a48 diff --git a/log2.txt b/log2.txt new file mode 100644 index 0000000..a1de2f2 --- /dev/null +++ b/log2.txt @@ -0,0 +1 @@ +[?1049h(B[?7h[?1006;1000hHealth: 100~~~~~~~~~~~~~Time elapsed: 0:0~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~&~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~!~~~~$~~~~~~~~~~~~~~~~~~~~~~ ~~~~ ~~~~~~@~~~#########~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~###### -------------------------------------------------------------------------------|WELCOME TO SPYLIKE! |CONTROLS: |W,A,S,D MOVEMENT |V,B,N,G ATTACK |ESC PAUSE |||| |v |------------------------------ ||||| || Start game || Quit | ||||| |------------------------------ ||||||||||||||-------------------------------------------------------------------------------|v | | | v|v | | | v|v | | | v|v | | | v|v | | | v[?1006;1000l[?1049l [?1l> \ No newline at end of file diff --git a/logging/logger.cpp b/logging/logger.cpp index 2fe0600..e72bfce 100644 --- a/logging/logger.cpp +++ b/logging/logger.cpp @@ -2,35 +2,11 @@ #include #include #include -using namespace std; #include -SpylikeLogger::SpylikeLogger(string fname, LogLevel level) : filename(fname), loglevel(level) { - ofstream logfile; +SpylikeLogger::SpylikeLogger(std::string fname, LogLevel level) : filename(fname), loglevel(level) { + std::ofstream logfile; logfile.open(filename); logfile.close(); } - -void SpylikeLogger::log(string text, LogLevel level) { - if (loglevel >= level) { - string levelname; - switch(level) { - case DEBUG: - levelname = "DEBUG"; - break; - case ERROR: - levelname = "ERROR"; - break; - case CRITICAL: - levelname = "CRITICAL"; - break; - } - char mbuffer[levelname.length() + text.length() + 3]; - sprintf(mbuffer, "[%s] %s", levelname.c_str(), text.c_str()); - string message = mbuffer; - ofstream logfile {filename, ios_base::app}; - logfile << message + '\n'; - logfile.close(); - } -} diff --git a/main.cpp b/main.cpp index 487b1a4..03b7349 100644 --- a/main.cpp +++ b/main.cpp @@ -1,11 +1,12 @@ #include "game.h" //#include "navier.h" +#include #include "logger.h" extern SpylikeLogger LOGGER("log.txt", DEBUG); int main() { - Game::run(); - //run(); + std::shared_ptr manager = std::make_shared(); + manager->run(); return 0; } diff --git a/models/event.cpp b/models/event.cpp index ee6702c..af15992 100644 --- a/models/event.cpp +++ b/models/event.cpp @@ -1,7 +1,11 @@ #include "event.h" +#include "logger.h" #include #include +#include + +extern SpylikeLogger LOGGER; void EventManager::subscribe(std::shared_ptr handler, std::string eventType) { if (eventSubscribers.find(eventType) == eventSubscribers.end()) { @@ -13,9 +17,21 @@ void EventManager::subscribe(std::shared_ptr handler, std::string } void EventManager::emit(Event& event) { + std::vector> toClean; if (eventSubscribers.find(event.type) != eventSubscribers.end()) { for (auto& handler : eventSubscribers[event.type]) { - handler->on_event(event); + if(handler) handler->on_event(event); + else toClean.push_back(handler); } } + for (auto& handler : toClean) { + unsubscribe(handler); + } +} + +void EventManager::unsubscribe(std::shared_ptr handler) { + for (auto& subscriberPair : eventSubscribers) { + auto& subscribers = subscriberPair.second; + subscribers.erase(std::remove(subscribers.begin(), subscribers.end(), handler), subscribers.end()); + } } diff --git a/models/scheduling.cpp b/models/scheduling.cpp index d0c4d59..e8173c8 100644 --- a/models/scheduling.cpp +++ b/models/scheduling.cpp @@ -10,22 +10,18 @@ extern SpylikeLogger LOGGER; ScheduledTask::ScheduledTask(std::string id) : id(id), running(true) {} -FrameScheduler::FrameScheduler(std::vector> tasks, int maxFPS=60) : tasks(tasks), maxFPS(maxFPS) { - assert(maxFPS > 0); - runningSignal = false; -} - void FrameScheduler::run() { int frameDelay = 1000000/maxFPS; if (!runningSignal) { runningSignal = true; - while (true) { + while (runningSignal) { for (auto& task : tasks) { if (task->running) { task->update(); } } usleep(frameDelay); + usElapsed += frameDelay; } } else { @@ -60,3 +56,16 @@ void FrameScheduler::resumeTask(std::string taskID) { } } } + +bool FrameScheduler::isRunning(std::string taskID) { + for (auto& task : tasks) { + if (task->id == taskID) { + return task->running; + } + } + return false; +} + +int FrameScheduler::timeElapsed() { + return usElapsed/1000000; +} diff --git a/util/input.cpp b/util/input.cpp new file mode 100644 index 0000000..4aa2355 --- /dev/null +++ b/util/input.cpp @@ -0,0 +1,43 @@ +#include "input.h" +#include "screen.h" +#include "rendering.h" +#include "logger.h" + +extern SpylikeLogger LOGGER; + +void InputManager::update() { + char ch = screen.getInput(); + if (ch != '\0') { + SpylikeEvents::KeyInputEvent ev {"INPUT_KeyPress", ch}; + manager->emit(ev); + } + /* + MouseEvent mouse = screen.getMouse(); + if (mouse.mouseInput != MouseEvent::MOUSENONE) { + std::string eventString; + switch (mouse.mouseInput) { + case MouseEvent::MOUSELEFT: + eventString = "MouseLeftEvent"; + break; + case MouseEvent::MOUSELEFT_RELEASED: + eventString = "MouseLeftReleasedEvent"; + break; + case MouseEvent::MOUSERIGHT: + eventString = "MouseRightEvent"; + break; + case MouseEvent::MOUSERIGHT_RELEASED: + eventString = "MouseRightReleasedEvent"; + break; + case MouseEvent::MOUSEMIDDLE: + eventString = "MouseMiddleEvent"; + break; + case MouseEvent::MOUSEMIDDLE_RELEASED: + eventString = "MouseMiddleReleasedEvent"; + break; + } + SpylikeEvents::MouseInputEvent ev {eventString, mouse.x, mouse.y}; + LOGGER.log("Mouse input detected!", DEBUG); + manager->emit(ev); + } + */ +} diff --git a/util/timer.cpp b/util/timer.cpp new file mode 100644 index 0000000..879e70a --- /dev/null +++ b/util/timer.cpp @@ -0,0 +1,7 @@ +#include "timer.h" + +void Timer::tick() { if (!paused) elapsed++; } +void Timer::reset() { elapsed=0; } +void Timer::pause() { paused=true; } +void Timer::unpause() { paused=false; } +int Timer::getElapsed() { return elapsed; }