The open source repository for the action RPG game in development by Sig Productions titled 'Adventures in Lestoria'!
https://forums.lestoria.net
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4442 lines
178 KiB
4442 lines
178 KiB
#pragma region License
|
|
/*
|
|
License (OLC-3)
|
|
~~~~~~~~~~~~~~~
|
|
|
|
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
|
|
|
|
Redistribution and use in source and binary forms, with or without modification,
|
|
are permitted provided that the following conditions are met:
|
|
|
|
1. Redistributions or derivations of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
|
|
2. Redistributions or derivative works in binary form must reproduce the above
|
|
copyright notice. This list of conditions and the following disclaimer must be
|
|
reproduced in the documentation and/or other materials provided with the distribution.
|
|
|
|
3. Neither the name of the copyright holder nor the names of its contributors may
|
|
be used to endorse or promote products derived from this software without specific
|
|
prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
|
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
SUCH DAMAGE.
|
|
|
|
Portions of this software are copyright © 2024 The FreeType
|
|
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
|
|
All rights reserved.
|
|
*/
|
|
#pragma endregion
|
|
#include "olcUTIL_Camera2D.h"
|
|
#include "olcPGEX_TransformedView.h"
|
|
#include "AdventuresInLestoria.h"
|
|
#include "DamageNumber.h"
|
|
#include "Bullet.h"
|
|
#include "Ability.h"
|
|
#include "Class.h"
|
|
#include "Version.h"
|
|
#include "TMXParser.h"
|
|
#include "TSXParser.h"
|
|
#include "Map.h"
|
|
#include "DEFINES.h"
|
|
#include "util.h"
|
|
#include <set>
|
|
#include <queue>
|
|
#include "Emitter.h"
|
|
#include "config.h"
|
|
#include "safemap.h"
|
|
#include "Key.h"
|
|
#include "Menu.h"
|
|
#include "GameState.h"
|
|
#include "Item.h"
|
|
#include "Toggleable.h"
|
|
#include "Unlock.h"
|
|
#include "State_OverworldMap.h"
|
|
#include "Test.h"
|
|
#include "ItemDrop.h"
|
|
#include "VisualNovel.h"
|
|
#include "util.h"
|
|
#include "olcPGEX_TTF.h"
|
|
#include "MenuItemItemButton.h"
|
|
#include "Merchant.h"
|
|
#include "SaveFile.h"
|
|
#include "TitleScreen.h"
|
|
#include "SoundEffect.h"
|
|
#include "olcPGEX_Gamepad.h"
|
|
#include "InventoryScrollableWindowComponent.h"
|
|
#ifndef __EMSCRIPTEN__
|
|
#include "discord.h"
|
|
#include "steam/steam_api.h"
|
|
#endif
|
|
#include "GameSettings.h"
|
|
#include "LoadingScreen.h"
|
|
#include "Tutorial.h"
|
|
#include "SteamKeyboardCallbackHandler.h"
|
|
#include "SteamStatsReceivedHandler.h"
|
|
#include "StageMaskPolygon.h"
|
|
|
|
INCLUDE_EMITTER_LIST
|
|
INCLUDE_ITEM_CATEGORIES
|
|
INCLUDE_BACKDROP_DATA
|
|
INCLUDE_FOREDROP_DATA
|
|
INCLUDE_MONSTER_DATA
|
|
INCLUDE_PACK_KEY
|
|
|
|
bool _DEBUG_MAP_LOAD_INFO = false;
|
|
//360x240
|
|
vi2d WINDOW_SIZE={24*15,24*10};
|
|
safemap<std::string,Animate2D::FrameSequence>ANIMATION_DATA;
|
|
std::vector<std::shared_ptr<Monster>>MONSTER_LIST;
|
|
std::unordered_map<MonsterSpawnerID,MonsterSpawner>SPAWNER_LIST;
|
|
std::vector<std::shared_ptr<DamageNumber>>DAMAGENUMBER_LIST;
|
|
std::vector<std::unique_ptr<IBullet>>BULLET_LIST;
|
|
std::optional<std::queue<MonsterSpawnerID>>SPAWNER_CONTROLLER;
|
|
safemap<std::string,Renderable>GFX;
|
|
utils::datafile DATA;
|
|
AiL*game;
|
|
#undef KEY_LEFT //Stupid Linux
|
|
#undef KEY_RIGHT //Stupid Linux
|
|
#undef KEY_UP //Stupid Linux
|
|
#undef KEY_DOWN //Stupid Linux
|
|
#undef KEY_MENU //Stupid Linux
|
|
#undef KEY_SCROLLUP //Stupid Linux
|
|
#undef KEY_SCROLLDOWN //Stupid Linux
|
|
#undef KEY_BACK //Stupid Linux
|
|
#undef KEY_SELECT //Stupid Linux
|
|
#undef KEY_ENTER //Stupid Linux
|
|
InputGroup AiL::KEY_LEFT;
|
|
InputGroup AiL::KEY_RIGHT;
|
|
InputGroup AiL::KEY_UP;
|
|
InputGroup AiL::KEY_DOWN;
|
|
InputGroup AiL::KEY_ATTACK;
|
|
InputGroup AiL::KEY_CONFIRM;
|
|
InputGroup AiL::KEY_MENU;
|
|
InputGroup AiL::KEY_BACK;
|
|
InputGroup AiL::KEY_START;
|
|
InputGroup AiL::KEY_CONTROLLER_START;
|
|
InputGroup AiL::KEY_SELECT;
|
|
InputGroup AiL::KEY_UNEQUIP;
|
|
|
|
InputGroup AiL::KEY_FACEUP;
|
|
InputGroup AiL::KEY_FACERIGHT;
|
|
InputGroup AiL::KEY_FACELEFT;
|
|
InputGroup AiL::KEY_FACEDOWN;
|
|
InputGroup AiL::KEY_ENTER;
|
|
|
|
InputGroup AiL::KEY_SCROLLDOWN;
|
|
InputGroup AiL::KEY_SCROLLUP;
|
|
InputGroup AiL::KEY_FASTSCROLLDOWN;
|
|
InputGroup AiL::KEY_FASTSCROLLUP;
|
|
InputGroup AiL::KEY_SCROLLLEFT;
|
|
InputGroup AiL::KEY_SCROLLRIGHT;
|
|
InputGroup AiL::KEY_SCROLLHORZ;
|
|
InputGroup AiL::KEY_SCROLLHORZ_L;
|
|
InputGroup AiL::KEY_SCROLLHORZ_R;
|
|
InputGroup AiL::KEY_SCROLLVERT;
|
|
InputGroup AiL::KEY_SCROLLVERT_R;
|
|
InputGroup AiL::KEY_SCROLLVERT_L;
|
|
InputGroup AiL::KEY_SCROLL;
|
|
InputGroup AiL::KEY_SHOULDER;
|
|
InputGroup AiL::KEY_SHOULDER2;
|
|
InputGroup AiL::KEY_CHANGE_LOADOUT;
|
|
InputGroup AiL::KEY_MOUSE_RIGHT;
|
|
|
|
InputGroup AiL::KEY_TOGGLE_MAP;
|
|
|
|
#ifndef __EMSCRIPTEN__
|
|
::discord::Core*Discord{};
|
|
#endif
|
|
|
|
float AiL::SIZE_CHANGE_SPEED=1;
|
|
|
|
AiL::AiL(){
|
|
GFX.Reset();
|
|
DATA.Reset();
|
|
InitializeGameConfigurations();
|
|
sAppName="GAME_NAME"_S;
|
|
game=this;
|
|
gameStarted=time(NULL);
|
|
}
|
|
|
|
void AiL::InitializeGameConfigurations(){
|
|
utils::datafile::Read(DATA,"assets/config/configuration.txt");
|
|
std::filesystem::create_directories("save_file_path"_S);
|
|
|
|
_DEBUG_MAP_LOAD_INFO=bool("debug_map_load_info"_I);
|
|
|
|
MONSTER_LIST.reserve(500); //This is going to make it so the monster limit is soft-capped. We have hitlists in this game that may store pointers to monsters, this hopefully protects them from invalidated pointers. Worst-case-scenario we get double hits when there are more than 500 monsters on-screen at once which I really don't think will ever occur.
|
|
|
|
std::string CONFIG_PATH = "config_path"_S;
|
|
|
|
std::string GFX_CONFIG = CONFIG_PATH + "gfx_config"_S;
|
|
utils::datafile::Read(DATA,GFX_CONFIG);
|
|
WINDOW_SIZE={"WINDOW_SIZE"_i[0],"WINDOW_SIZE"_i[1]};
|
|
|
|
std::string MAP_CONFIG = CONFIG_PATH + "map_config"_S;
|
|
utils::datafile::Read(DATA,MAP_CONFIG);
|
|
|
|
std::string PLAYER_CONFIG = CONFIG_PATH + "player_config"_S;
|
|
utils::datafile::Read(DATA,PLAYER_CONFIG);
|
|
|
|
std::string MONSTERS_CONFIG = CONFIG_PATH + "monsters_config"_S;
|
|
utils::datafile::Read(DATA,MONSTERS_CONFIG);
|
|
|
|
std::string NPC_CONFIG = CONFIG_PATH + "npc_config"_S;
|
|
utils::datafile::Read(DATA,NPC_CONFIG);
|
|
|
|
std::string MONSTERSTRATEGIES_CONFIG = CONFIG_PATH + "monsterstrategies_config"_S;
|
|
utils::datafile::Read(DATA,MONSTERSTRATEGIES_CONFIG);
|
|
|
|
std::string THEMES_CONFIG = CONFIG_PATH + "themes_config"_S;
|
|
utils::datafile::Read(DATA,THEMES_CONFIG);
|
|
|
|
std::string ITEM_CONFIG = CONFIG_PATH + "item_config"_S;
|
|
utils::datafile::Read(DATA,ITEM_CONFIG);
|
|
|
|
std::string ITEM_SET_CONFIG = CONFIG_PATH + "item_set_config"_S;
|
|
utils::datafile::Read(DATA,ITEM_SET_CONFIG);
|
|
|
|
std::string ITEM_STATS_CONFIG = CONFIG_PATH + "item_stats_config"_S;
|
|
utils::datafile::Read(DATA,ITEM_STATS_CONFIG);
|
|
|
|
std::string BACKDROP_CONFIG = CONFIG_PATH + "backdrop_config"_S;
|
|
utils::datafile::Read(DATA,BACKDROP_CONFIG);
|
|
|
|
std::string INPUT_CONFIG = CONFIG_PATH + "input_config"_S;
|
|
utils::datafile::Read(DATA,INPUT_CONFIG);
|
|
|
|
auto keys=DATA.GetProperty("ItemConfiguration");
|
|
for(auto&[key,value]:keys){
|
|
std::string config=DATA["ItemConfiguration"][key].GetString();
|
|
utils::datafile::Read(DATA,CONFIG_PATH + "item_directory"_S + config);
|
|
}
|
|
|
|
DEBUG_PATHFINDING="debug_pathfinding"_I;
|
|
|
|
std::vector<std::string>values=DATA.GetProperty("class_list").GetValues();
|
|
for(const std::string&cl:values){
|
|
LOG(cl);
|
|
utils::datafile::Read(DATA,CONFIG_PATH + "class_directory"_S + cl + ".txt");
|
|
}
|
|
|
|
std::string BGM_CONFIG = CONFIG_PATH + "bgm_config"_S;
|
|
utils::datafile::Read(DATA,BGM_CONFIG);
|
|
|
|
std::string BGM_EVENTS_CONFIG = CONFIG_PATH + "event_config"_S;
|
|
utils::datafile::Read(DATA,BGM_EVENTS_CONFIG);
|
|
|
|
std::string AUDIO_CONFIG = CONFIG_PATH + "audio_config"_S;
|
|
utils::datafile::Read(DATA,AUDIO_CONFIG);
|
|
|
|
std::string INTERFACE_CONFIG = CONFIG_PATH + "interface_config"_S;
|
|
utils::datafile::Read(DATA,INTERFACE_CONFIG);
|
|
|
|
std::string ENVIRONMENTAL_AUDIO_CONFIG = CONFIG_PATH + "environmental_audio_config"_S;
|
|
utils::datafile::Read(DATA,ENVIRONMENTAL_AUDIO_CONFIG);
|
|
|
|
std::string CREDITS_CONFIG = CONFIG_PATH + "credits_config"_S;
|
|
utils::datafile::Read(DATA,CREDITS_CONFIG);
|
|
|
|
std::string ACHIEVEMENT_CONFIG = CONFIG_PATH + "achievement_config"_S;
|
|
utils::datafile::Read(DATA,ACHIEVEMENT_CONFIG);
|
|
|
|
std::string MINIMAP_CONFIG = CONFIG_PATH + "minimap_config"_S;
|
|
utils::datafile::Read(DATA,MINIMAP_CONFIG);
|
|
|
|
utils::datafile::DEBUG_ACCESS_OPTIONS="debug_access_options"_I;
|
|
}
|
|
|
|
bool AiL::OnUserCreate(){
|
|
if(PACK_KEY=="INSERT_PACK_KEY_HERE")ERR("ERROR! Starting the game with the default pack ID is not allowed!")
|
|
gamepack.LoadPack("assets/"+"gamepack_file"_S,PACK_KEY);
|
|
|
|
GamePad::init();
|
|
Input::Initialize();
|
|
|
|
Font::init();
|
|
|
|
InitializeDefaultKeybinds();
|
|
|
|
VisualNovel::Initialize();
|
|
|
|
InitializeGraphics();
|
|
InitializeClasses();
|
|
|
|
sig::Animation::InitializeAnimations();
|
|
|
|
InitializePlayer();
|
|
|
|
InitializePlayerLevelCap();
|
|
|
|
healthCounter.Initialize(&player->hp,"Interface.HUD Health Tick Rate"_F,"Interface.HUD Health Display Color"_Pixel,"Interface.HUD Heal Damage Color"_Pixel,"Interface.HUD Take Damage Color"_Pixel,"Interface.HUD Health Change Time"_F);
|
|
manaCounter.Initialize(&player->mana,"Interface.HUD Mana Tick Rate"_F,"Interface.HUD Mana Display Color"_Pixel,"Interface.HUD Restore Mana Color"_Pixel,"Interface.HUD Reduce Mana Color"_Pixel,"Interface.HUD Mana Change Time"_F);
|
|
|
|
Monster::InitializeStrategies();
|
|
MonsterData::InitializeMonsterData();
|
|
MonsterData::InitializeNPCData();
|
|
|
|
InitializeLevels();
|
|
|
|
//Initialize Camera.
|
|
camera=Camera2D{WINDOW_SIZE};
|
|
camera.SetMode(olc::utils::Camera2D::Mode::LazyFollow);
|
|
camera.SetTarget(player->GetPos());
|
|
camera.SetWorldBoundary({0,0},GetCurrentMap().MapData.MapSize*GetCurrentMap().MapData.TileSize);
|
|
camera.EnableWorldBoundary(false);
|
|
|
|
sig::Animation::SetupPlayerAnimations();
|
|
view=TileTransformedView{GetScreenSize(),{1,1}};
|
|
|
|
Audio::Initialize();
|
|
EnvironmentalAudio::Initialize();
|
|
SoundEffect::Initialize();
|
|
|
|
Menu::InitializeMenus();
|
|
|
|
ChangePlayerClass(WARRIOR);
|
|
|
|
GameState::Initialize();
|
|
GameState::ChangeState(States::MAIN_MENU);
|
|
|
|
Unlock::Initialize();
|
|
ItemDrop::Initialize();
|
|
|
|
Merchant::Initialize();
|
|
|
|
TitleScreen::Initialize();
|
|
|
|
Tutorial::Initialize();
|
|
|
|
Stats::InitializeDamageReductionTable();
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
emscripten_idb_async_load("/assets",("save_file_path"_S+"system.conf").c_str(),0,[](void*arg,void*data,int length){
|
|
LOG("Loaded System Save File successfully!");
|
|
|
|
std::string rawMetadata=(char*)data;
|
|
std::ofstream file("save_file_path"_S+"system.conf");
|
|
for(int i=0;i<length;i++){
|
|
file<<rawMetadata[i];
|
|
}
|
|
file.close();
|
|
GameSettings::Initialize();
|
|
},[](void*arg){
|
|
LOG("Failed to load System Save File!");
|
|
});
|
|
#else
|
|
GameSettings::Initialize();
|
|
#endif
|
|
|
|
State_OverworldMap::SetStageMarker("starting_map_name"_S);
|
|
STEAMINPUT(
|
|
SteamInput()->Init(false);
|
|
Input::LoadSteamButtonIcons();
|
|
Input::ingameControlsHandle=SteamInput()->GetActionSetHandle("InGameControls");
|
|
)
|
|
|
|
#ifndef __EMSCRIPTEN__
|
|
if(steamKeyboardCallbackListener==nullptr){
|
|
steamKeyboardCallbackListener=new SteamKeyboardCallbackHandler();
|
|
}
|
|
if(steamStatsReceivedHandlerListener==nullptr){
|
|
steamStatsReceivedHandlerListener=new SteamStatsReceivedHandler();
|
|
}
|
|
#endif
|
|
|
|
utils::datafile::INITIAL_SETUP_COMPLETE=true;
|
|
|
|
ValidateGameStatus(); //Checks to make sure everything has been initialized properly.
|
|
#ifndef __EMSCRIPTEN__
|
|
SetupDiscord();
|
|
#endif
|
|
|
|
player->InitializeMinimapImage();
|
|
minimap.Initialize();
|
|
|
|
gameInitialized=true;
|
|
|
|
if(!gamepack.Loaded()&&"GENERATE_GAMEPACK"_B){
|
|
gamepack.SavePack("assets/"+"gamepack_file"_S,PACK_KEY);
|
|
LOG("Game Pack has been generated!"<<std::endl<<"========================"<<std::endl);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AiL::OnUserUpdate(float fElapsedTime){
|
|
GlobalGameUpdates();
|
|
LoadingScreen::Update();
|
|
InputListener::Update();
|
|
Tutorial::Update();
|
|
Audio::Update();
|
|
if(!game->TestingModeEnabled()){
|
|
GameState::STATE->Draw(this);
|
|
RenderMenu();
|
|
GameState::STATE->DrawOverlay(this);
|
|
RenderFadeout();
|
|
LoadingScreen::Draw();
|
|
RenderVersionInfo();
|
|
#ifndef __EMSCRIPTEN__
|
|
if(Discord){
|
|
auto result=Discord->RunCallbacks();
|
|
if(result!=::discord::Result::Ok){
|
|
LOG("Discord Error Code "<<int(result));
|
|
delete Discord;
|
|
Discord=nullptr;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
return !gameEnd;
|
|
}
|
|
|
|
bool AiL::LeftHeld(){
|
|
return KEY_LEFT.Held()||KEY_SCROLLHORZ_L.Analog()<=-0.2f;
|
|
}
|
|
bool AiL::RightHeld(){
|
|
return KEY_RIGHT.Held()||KEY_SCROLLHORZ_L.Analog()>=0.2f;
|
|
}
|
|
bool AiL::UpHeld(){
|
|
return KEY_UP.Held()||KEY_SCROLLVERT_L.Analog()<=-0.2f;
|
|
}
|
|
bool AiL::DownHeld(){
|
|
return KEY_DOWN.Held()||KEY_SCROLLVERT_L.Analog()>=0.2f;
|
|
}
|
|
bool AiL::LeftReleased(){
|
|
return KEY_LEFT.Released()||
|
|
Input::UsingGamepad()&&!KEY_LEFT.Held()&&player->movementVelocity.x<0&&KEY_SCROLLHORZ_L.Analog()>-0.2f; //Since controller players have the option of using the Left Analog stick or the dpad for movement, we have to make sure both are not being utilized while moving before we can consider an input released.
|
|
}
|
|
bool AiL::RightReleased(){
|
|
return KEY_RIGHT.Released()||Input::UsingGamepad()&&!KEY_RIGHT.Held()&&player->movementVelocity.x>0&&KEY_SCROLLHORZ_L.Analog()<0.2f; //Since controller players have the option of using the Left Analog stick or the dpad for movement, we have to make sure both are not being utilized while moving before we can consider an input released.
|
|
}
|
|
bool AiL::UpReleased(){
|
|
return KEY_UP.Released()||Input::UsingGamepad()&&!KEY_UP.Held()&&player->movementVelocity.y<0&&KEY_SCROLLVERT_L.Analog()>-0.2f; //Since controller players have the option of using the Left Analog stick or the dpad for movement, we have to make sure both are not being utilized while moving before we can consider an input released.
|
|
}
|
|
bool AiL::DownReleased(){
|
|
return KEY_DOWN.Released()||Input::UsingGamepad()&&!KEY_DOWN.Held()&&player->movementVelocity.y>0&&KEY_SCROLLVERT_L.Analog()<0.2f; //Since controller players have the option of using the Left Analog stick or the dpad for movement, we have to make sure both are not being utilized while moving before we can consider an input released.
|
|
}
|
|
bool AiL::LeftPressed(){
|
|
return KEY_LEFT.Pressed();
|
|
}
|
|
bool AiL::RightPressed(){
|
|
return KEY_RIGHT.Pressed();
|
|
}
|
|
bool AiL::UpPressed(){
|
|
return KEY_UP.Pressed();
|
|
}
|
|
bool AiL::DownPressed(){
|
|
return KEY_DOWN.Pressed();
|
|
}
|
|
|
|
void AiL::HandleUserInput(float fElapsedTime){
|
|
if(!Menu::stack.empty())return; //A window being opened means there's no user input allowed.
|
|
if(GetKey(SCROLL).bPressed)displayHud=!displayHud;
|
|
|
|
bool setIdleAnimation=true;
|
|
bool heldDownMovementKey=false; //Is true when a movement key has been held down.
|
|
|
|
if(KEY_MENU.Released()){
|
|
Menu::OpenMenu(MenuType::PAUSE);
|
|
}
|
|
float animationSpd=0.f;
|
|
player->movementVelocity={};
|
|
if((player->GetVelocity().mag()<"Player.Move Allowed Velocity Lower Limit"_F&&player->CanMove())||(player->GetState()==State::ROLL&&"Thief.Right Click Ability.Roll Time"_F-player->rolling_timer>=0.2f)){
|
|
auto GetPlayerStaircaseDirection=[&](){
|
|
for(LayerTag&layer:MAP_DATA[GetCurrentLevel()].LayerData){
|
|
int truncatedPlayerX=int(player->GetX())/game->GetCurrentMapData().tilewidth;
|
|
int truncatedPlayerY=int(player->GetY())/game->GetCurrentMapData().tilewidth;
|
|
int tileID=layer.tiles[truncatedPlayerY][truncatedPlayerX];
|
|
TilesheetData dat=GetTileSheet(GetCurrentLevel(),tileID);
|
|
int realTileID{tileID-dat.firstgid-1};
|
|
if(MAP_TILESETS.at(dat.tilesetName).staircaseTiles.find(realTileID)!=MAP_TILESETS.at(dat.tilesetName).staircaseTiles.end()){
|
|
return MAP_TILESETS.at(dat.tilesetName).staircaseTiles.at(realTileID).data.at("value");
|
|
}
|
|
}
|
|
return std::string("NONE");
|
|
};
|
|
std::string staircaseDirection=GetPlayerStaircaseDirection();
|
|
vf2d newAimingAngle{};
|
|
if(RightHeld()){
|
|
float moveAmt="Player.MoveSpd"_F;
|
|
if(Input::UsingGamepad()&&KEY_SCROLLHORZ_L.Analog()>=0.2f){
|
|
float controllerAmt=abs(KEY_SCROLLHORZ_L.Analog());
|
|
if(controllerAmt>=0.6f)controllerAmt=1.f; //Edge zone.
|
|
if(controllerAmt>animationSpd){
|
|
animationSpd=controllerAmt;
|
|
}
|
|
moveAmt*=controllerAmt;
|
|
}else animationSpd=1.f;
|
|
player->SetX(player->GetX()+fElapsedTime*moveAmt*player->GetMoveSpdMult());
|
|
player->movementVelocity.x+=moveAmt*fElapsedTime*player->GetMoveSpdMult();
|
|
if(staircaseDirection=="RIGHT"){
|
|
player->SetY(player->GetY()-"Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult());
|
|
player->movementVelocity.y+=-"Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult();
|
|
} else
|
|
if(staircaseDirection=="LEFT"){
|
|
player->SetY(player->GetY()+"Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult());
|
|
player->movementVelocity.y+="Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult();
|
|
}
|
|
|
|
newAimingAngle+=vf2d{1,0};
|
|
|
|
setIdleAnimation=false;
|
|
heldDownMovementKey=true;
|
|
}
|
|
if(LeftHeld()){
|
|
float moveAmt="Player.MoveSpd"_F;
|
|
if(Input::UsingGamepad()&&KEY_SCROLLHORZ_L.Analog()<=-0.2f){
|
|
float controllerAmt=abs(KEY_SCROLLHORZ_L.Analog());
|
|
controllerAmt+=0.2f;
|
|
if(controllerAmt>=0.6f)controllerAmt=1.f; //Edge zone.
|
|
if(controllerAmt>animationSpd){
|
|
animationSpd=controllerAmt;
|
|
}
|
|
moveAmt*=controllerAmt;
|
|
}else animationSpd=1.f;
|
|
player->SetX(player->GetX()-fElapsedTime*moveAmt*player->GetMoveSpdMult());
|
|
player->movementVelocity.x+=-moveAmt*fElapsedTime*player->GetMoveSpdMult();
|
|
if(staircaseDirection=="RIGHT"){
|
|
player->SetY(player->GetY()+"Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult());
|
|
player->movementVelocity.y+="Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult();
|
|
} else
|
|
if(staircaseDirection=="LEFT"){
|
|
player->SetY(player->GetY()-"Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult());
|
|
player->movementVelocity.y+=-"Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult();
|
|
}
|
|
|
|
newAimingAngle-=vf2d{1,0};
|
|
|
|
setIdleAnimation=false;
|
|
heldDownMovementKey=true;
|
|
}
|
|
if(UpHeld()){
|
|
float moveAmt="Player.MoveSpd"_F;
|
|
if(Input::UsingGamepad()&&KEY_SCROLLVERT_L.Analog()<=-0.2f){
|
|
float controllerAmt=abs(KEY_SCROLLVERT_L.Analog());
|
|
controllerAmt+=0.2f;
|
|
if(controllerAmt>=0.6f)controllerAmt=1.f; //Edge zone.
|
|
if(controllerAmt>animationSpd){
|
|
animationSpd=controllerAmt;
|
|
}
|
|
moveAmt*=controllerAmt;
|
|
}else animationSpd=1.f;
|
|
player->SetY(player->GetY()-fElapsedTime*moveAmt*player->GetMoveSpdMult());
|
|
player->movementVelocity.y+=-moveAmt*fElapsedTime*player->GetMoveSpdMult();
|
|
|
|
newAimingAngle-=vf2d{0,1};
|
|
|
|
setIdleAnimation=false;
|
|
heldDownMovementKey=true;
|
|
}
|
|
if(DownHeld()){
|
|
float moveAmt="Player.MoveSpd"_F;
|
|
if(Input::UsingGamepad()&&KEY_SCROLLVERT_L.Analog()>=0.2f){
|
|
float controllerAmt=abs(KEY_SCROLLVERT_L.Analog());
|
|
controllerAmt+=0.2f;
|
|
if(controllerAmt>=0.6f)controllerAmt=1.f; //Edge zone.
|
|
if(controllerAmt>animationSpd){
|
|
animationSpd=controllerAmt;
|
|
}
|
|
moveAmt*=controllerAmt;
|
|
}else animationSpd=1.f;
|
|
player->SetY(player->GetY()+fElapsedTime*moveAmt*player->GetMoveSpdMult());
|
|
player->movementVelocity.y+=moveAmt*fElapsedTime*player->GetMoveSpdMult();
|
|
|
|
newAimingAngle+=vf2d{0,1};
|
|
|
|
setIdleAnimation=false;
|
|
heldDownMovementKey=true;
|
|
}
|
|
if(newAimingAngle!=vf2d{}){
|
|
player->aimingAngle=newAimingAngle.norm().polar();
|
|
}
|
|
}
|
|
if(heldDownMovementKey){
|
|
if(player->GetState()==State::ROLL&&"Thief.Right Click Ability.Roll Time"_F-player->rolling_timer>=0.2f){
|
|
player->SetState(State::NORMAL);
|
|
player->ResetVelocity();
|
|
}
|
|
|
|
if(player->movementVelocity.x!=0.f||player->movementVelocity.y!=0.f){
|
|
if(abs(player->movementVelocity.x)>abs(player->movementVelocity.y)){ //Greater Horizontal movement.
|
|
if(player->movementVelocity.x!=0.f){
|
|
player->SetFacingDirection(player->movementVelocity.x>0?RIGHT:LEFT);
|
|
if(player->GetState()==State::NORMAL||player->GetState()==State::PREP_CAST){
|
|
player->UpdateWalkingAnimation(player->GetFacingDirection(),animationSpd);
|
|
}
|
|
}
|
|
}else{ //Greater Vertical movement.
|
|
if(player->movementVelocity.y!=0.f){
|
|
player->SetFacingDirection(player->movementVelocity.y>0?DOWN:UP);
|
|
if(player->GetState()==State::NORMAL||player->GetState()==State::PREP_CAST){
|
|
player->UpdateWalkingAnimation(player->GetFacingDirection(),animationSpd);
|
|
}
|
|
}
|
|
}
|
|
#pragma region Footstep code
|
|
player->footstepTimer+=GetElapsedTime()*animationSpd;
|
|
if(player->footstepTimer>"Player.Footstep Timer"_F){
|
|
player->footstepTimer-="Player.Footstep Timer"_F;
|
|
PlayFootstepSound();
|
|
}
|
|
#pragma endregion
|
|
}else{ //This means we are holding down movement keys but we aren't actually moving anywhere, so don't.
|
|
setIdleAnimation=true;
|
|
}
|
|
}
|
|
if(UpReleased()){
|
|
if(player->GetState()==State::NORMAL||player->GetState()==State::PREP_CAST){
|
|
if(RightHeld()){
|
|
player->UpdateWalkingAnimation(RIGHT);
|
|
} else
|
|
if(DownHeld()){
|
|
player->UpdateWalkingAnimation(DOWN);
|
|
} else
|
|
if(LeftHeld()){
|
|
player->UpdateWalkingAnimation(LEFT);
|
|
}
|
|
}
|
|
}
|
|
if(RightReleased()){
|
|
if(player->GetState()==State::NORMAL||player->GetState()==State::PREP_CAST){
|
|
if(UpHeld()){
|
|
player->UpdateWalkingAnimation(UP);
|
|
} else
|
|
if(DownHeld()){
|
|
player->UpdateWalkingAnimation(DOWN);
|
|
} else
|
|
if(LeftHeld()){
|
|
player->UpdateWalkingAnimation(LEFT);
|
|
}
|
|
}
|
|
}
|
|
if(LeftReleased()){
|
|
if(player->GetState()==State::NORMAL||player->GetState()==State::PREP_CAST){
|
|
if(RightHeld()){
|
|
player->UpdateWalkingAnimation(RIGHT);
|
|
} else
|
|
if(DownHeld()){
|
|
player->UpdateWalkingAnimation(DOWN);
|
|
} else
|
|
if(UpHeld()){
|
|
player->UpdateWalkingAnimation(UP);
|
|
}
|
|
}
|
|
}
|
|
if(DownReleased()){
|
|
if(player->GetState()==State::NORMAL||player->GetState()==State::PREP_CAST){
|
|
if(RightHeld()){
|
|
player->UpdateWalkingAnimation(RIGHT);
|
|
} else
|
|
if(UpHeld()){
|
|
player->UpdateWalkingAnimation(UP);
|
|
} else
|
|
if(LeftHeld()){
|
|
player->UpdateWalkingAnimation(LEFT);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(player->GetState()!=State::NORMAL&&player->GetState()!=State::PREP_CAST){
|
|
setIdleAnimation=false;
|
|
}
|
|
|
|
if(setIdleAnimation){
|
|
switch(player->GetFacingDirection()){
|
|
case UP:{
|
|
player->UpdateIdleAnimation(UP);
|
|
}break;
|
|
case DOWN:{
|
|
player->UpdateIdleAnimation(DOWN);
|
|
}break;
|
|
case LEFT:{
|
|
player->UpdateIdleAnimation(LEFT);
|
|
}break;
|
|
case RIGHT:{
|
|
player->UpdateIdleAnimation(RIGHT);
|
|
}break;
|
|
default:{
|
|
player->UpdateIdleAnimation(DOWN);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void AiL::UpdateCamera(float fElapsedTime){
|
|
lastWorldShakeAdjust=std::max(0.f,lastWorldShakeAdjust-fElapsedTime);
|
|
if(worldShakeTime>0){
|
|
worldShakeTime-=fElapsedTime;
|
|
if(worldShakeTime<=0){
|
|
camera.SetTarget(player->GetPos());
|
|
Input::StopVibration();
|
|
}
|
|
if(lastWorldShakeAdjust==0){
|
|
lastWorldShakeAdjust=0.02f;
|
|
worldShakeVel.x*=-1;
|
|
worldShakeVel.y*=-1;
|
|
}
|
|
worldShake=player->GetPos()+worldShakeVel;
|
|
}
|
|
camera.Update(fElapsedTime);
|
|
if(view.GetZoom().x!=targetZoom){
|
|
if(view.GetZoom().x<targetZoom)view.SetZoom(std::min(view.GetZoom().x+zoomAdjustSpeed*fElapsedTime,targetZoom),camera.GetTarget());
|
|
else view.SetZoom(std::max(view.GetZoom().x-zoomAdjustSpeed*fElapsedTime,targetZoom),camera.GetTarget());
|
|
}
|
|
view.SetWorldOffset(camera.GetViewPosition());
|
|
}
|
|
|
|
void AiL::UpdateEffects(float fElapsedTime){
|
|
for(auto it=EMITTER_LIST.begin();it!=EMITTER_LIST.end();++it){
|
|
auto ptr=(*it).get();
|
|
ptr->Update(fElapsedTime);
|
|
}
|
|
for(std::vector<std::unique_ptr<Effect>>::iterator it=backgroundEffects.begin();it!=backgroundEffects.end();++it){
|
|
Effect*e=(*it).get();
|
|
e->Update(fElapsedTime);
|
|
}
|
|
for(std::vector<std::unique_ptr<Effect>>::iterator it=foregroundEffects.begin();it!=foregroundEffects.end();++it){
|
|
Effect*e=(*it).get();
|
|
e->Update(fElapsedTime);
|
|
}
|
|
|
|
std::erase_if(EMITTER_LIST,[](std::unique_ptr<Emitter>&e){return e->dead;});
|
|
std::erase_if(backgroundEffects,[](std::unique_ptr<Effect>&e){return e->dead;});
|
|
std::erase_if(foregroundEffects,[](std::unique_ptr<Effect>&e){return e->dead;});
|
|
std::erase_if(DAMAGENUMBER_LIST,[](std::shared_ptr<DamageNumber>&n){return n->lifeTime>1;});
|
|
|
|
for(auto it=foregroundEffectsToBeInserted.begin();it!=foregroundEffectsToBeInserted.end();++it){
|
|
foregroundEffects.push_back(std::move(*it));
|
|
}
|
|
for(auto it=backgroundEffectsToBeInserted.begin();it!=backgroundEffectsToBeInserted.end();++it){
|
|
backgroundEffects.push_back(std::move(*it));
|
|
}
|
|
foregroundEffectsToBeInserted.clear();
|
|
backgroundEffectsToBeInserted.clear();
|
|
}
|
|
void AiL::UpdateBullets(float fElapsedTime){
|
|
for(auto it=BULLET_LIST.begin();it!=BULLET_LIST.end();++it){
|
|
IBullet*b=(*it).get();
|
|
b->_Update(fElapsedTime);
|
|
}
|
|
std::erase_if(BULLET_LIST,[](std::unique_ptr<IBullet>&b){return b->IsDead();});
|
|
}
|
|
|
|
const HurtList AiL::Hurt(vf2d pos,float radius,int damage,bool upperLevel,float z,const HurtType hurtTargets,HurtFlag::HurtFlag hurtFlags)const{
|
|
HurtList hitList;
|
|
const bool CheckForMonsterCollisions=hurtTargets&HurtType::MONSTER;
|
|
const bool CheckForPlayerCollisions=hurtTargets&HurtType::PLAYER;
|
|
|
|
if(CheckForMonsterCollisions){
|
|
for(std::shared_ptr<Monster>&m:MONSTER_LIST){
|
|
const bool InRange=geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()));
|
|
if(InRange){
|
|
HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags);
|
|
hitList.push_back({&*m,returnVal});
|
|
}
|
|
}
|
|
}
|
|
if(CheckForPlayerCollisions){
|
|
const bool InRange=geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(GetPlayer()->GetPos(),12*GetPlayer()->GetSizeMult()));
|
|
if(InRange){
|
|
HurtReturnValue returnVal=GetPlayer()->Hurt(damage,upperLevel,z,hurtFlags);
|
|
hitList.push_back({GetPlayer(),returnVal});
|
|
}
|
|
}
|
|
return hitList;
|
|
}
|
|
|
|
const HurtList AiL::HurtMonsterType(vf2d pos,float radius,int damage,bool upperLevel,float z,const std::string_view monsterName,HurtFlag::HurtFlag hurtFlags)const{
|
|
if(!MONSTER_DATA.count(std::string(monsterName)))ERR(std::format("WARNING! Cannot check for monster type name {}! Does not exist!",monsterName));
|
|
HurtList hitList;
|
|
|
|
for(std::shared_ptr<Monster>&m:MONSTER_LIST){
|
|
const bool InRange=geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),m->GetCollisionRadius()));
|
|
if(m->GetName()==monsterName&&InRange){
|
|
HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags);
|
|
hitList.push_back({&*m,returnVal});
|
|
}
|
|
}
|
|
|
|
return hitList;
|
|
}
|
|
|
|
const HurtList AiL::HurtConeNotHit(vf2d pos,float radius,float angle,float sweepAngle,int damage,HitList&hitList,bool upperLevel,float z,const HurtType hurtTargets,HurtFlag::HurtFlag hurtFlags)const{
|
|
HurtList affectedList;
|
|
const bool CheckForMonsterCollisions=hurtTargets&HurtType::MONSTER;
|
|
const bool CheckForPlayerCollisions=hurtTargets&HurtType::PLAYER;
|
|
|
|
if(CheckForMonsterCollisions){
|
|
for(std::shared_ptr<Monster>&m:MONSTER_LIST){
|
|
if(!hitList.count(&*m)&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){
|
|
float angleToMonster=geom2d::line<float>{pos,m->GetPos()}.vector().polar().y;
|
|
float angleDiff=util::angle_difference(angleToMonster,angle);
|
|
if(abs(angleDiff)<=sweepAngle){
|
|
HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags);
|
|
affectedList.push_back({&*m,returnVal});
|
|
hitList.insert(&*m);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(CheckForPlayerCollisions){
|
|
if(!hitList.count(GetPlayer())&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(GetPlayer()->GetPos(),12*GetPlayer()->GetSizeMult()))){
|
|
float angleToPlayer=geom2d::line<float>{pos,GetPlayer()->GetPos()}.vector().polar().y;
|
|
float angleDiff=util::angle_difference(angleToPlayer,angle);
|
|
if(abs(angleDiff)<=sweepAngle){
|
|
HurtReturnValue returnVal=GetPlayer()->Hurt(damage,upperLevel,z,hurtFlags);
|
|
affectedList.push_back({GetPlayer(),returnVal});
|
|
hitList.insert(GetPlayer());
|
|
}
|
|
}
|
|
}
|
|
return affectedList;
|
|
}
|
|
|
|
const HurtList AiL::HurtNotHit(vf2d pos,float radius,int damage,HitList&hitList,bool upperLevel,float z,const HurtType hurtTargets,HurtFlag::HurtFlag hurtFlags)const{
|
|
HurtList affectedList;
|
|
const bool CheckForMonsterCollisions=hurtTargets&HurtType::MONSTER;
|
|
const bool CheckForPlayerCollisions=hurtTargets&HurtType::PLAYER;
|
|
|
|
if(CheckForMonsterCollisions){
|
|
for(std::shared_ptr<Monster>&m:MONSTER_LIST){
|
|
if(!hitList.count(&*m)&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){
|
|
HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags);
|
|
affectedList.push_back({&*m,returnVal});
|
|
hitList.insert(&*m);
|
|
}
|
|
}
|
|
}
|
|
if(CheckForPlayerCollisions){
|
|
if(!hitList.count(GetPlayer())&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(GetPlayer()->GetPos(),12*GetPlayer()->GetSizeMult()))){
|
|
HurtReturnValue returnVal=GetPlayer()->Hurt(damage,upperLevel,z,hurtFlags);
|
|
affectedList.push_back({GetPlayer(),returnVal});
|
|
hitList.insert(GetPlayer());
|
|
}
|
|
}
|
|
return affectedList;
|
|
}
|
|
|
|
void AiL::ProximityKnockback(const vf2d pos,const float radius,const float knockbackAmt,const HurtType knockbackTargets)const{
|
|
const bool CheckForMonsterCollisions=knockbackTargets&HurtType::MONSTER;
|
|
const bool CheckForPlayerCollisions=knockbackTargets&HurtType::PLAYER;
|
|
if(CheckForMonsterCollisions){
|
|
for(std::shared_ptr<Monster>&m:MONSTER_LIST){
|
|
if(geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){
|
|
m->ProximityKnockback(pos,knockbackAmt);
|
|
}
|
|
}
|
|
}
|
|
if(CheckForPlayerCollisions){
|
|
if(geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(GetPlayer()->GetPos(),12*GetPlayer()->GetSizeMult()))){
|
|
GetPlayer()->ProximityKnockback(pos,knockbackAmt);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AiL::ProximityKnockback(const vf2d pos,const float radius,const float knockbackAmt,const HurtList&knockbackTargets,const KnockbackCondition condition)const{
|
|
using enum KnockbackCondition;
|
|
for(auto&[target,wasHurt]:knockbackTargets){
|
|
if(condition==KNOCKBACK_HURT_TARGETS&&!wasHurt||
|
|
condition==KNOCKBACK_UNHURT_TARGETS&&wasHurt)continue;
|
|
if(std::holds_alternative<Player*>(target)){
|
|
Player*player{std::get<Player*>(target)};
|
|
player->ProximityKnockback(pos,knockbackAmt);
|
|
}else
|
|
if(std::holds_alternative<Monster*>(target)){
|
|
Monster*monster{std::get<Monster*>(target)};
|
|
monster->ProximityKnockback(pos,knockbackAmt);
|
|
}else ERR("WARNING! Target list was holding an unknown type??? THIS SHOULD NOT BE HAPPENING!")
|
|
}
|
|
}
|
|
|
|
void AiL::PopulateRenderLists(){
|
|
monstersBeforeLower.clear();
|
|
monstersAfterLower.clear();
|
|
monstersBeforeUpper.clear();
|
|
monstersAfterUpper.clear();
|
|
bulletsLower.clear();
|
|
bulletsUpper.clear();
|
|
backgroundEffectsLower.clear();
|
|
backgroundEffectsUpper.clear();
|
|
foregroundEffectsLower.clear();
|
|
foregroundEffectsUpper.clear();
|
|
endZones.clear();
|
|
upperEndZones.clear();
|
|
dropsBeforeLower.clear();
|
|
dropsAfterLower.clear();
|
|
dropsBeforeUpper.clear();
|
|
dropsAfterUpper.clear();
|
|
|
|
tilesWithCollision.clear();
|
|
tilesWithoutCollision.clear();
|
|
|
|
Player*pl=GetPlayer();
|
|
pl->rendered=false;
|
|
|
|
std::sort(MONSTER_LIST.begin(),MONSTER_LIST.end(),[](std::shared_ptr<Monster>&m1,std::shared_ptr<Monster>&m2){return m1->GetPos().y<m2->GetPos().y;});
|
|
std::sort(ItemDrop::drops.begin(),ItemDrop::drops.end(),[](ItemDrop&id1,ItemDrop&id2){return id1.GetPos().y<id2.GetPos().y;});
|
|
std::sort(BULLET_LIST.begin(),BULLET_LIST.end(),[](std::unique_ptr<IBullet>&b1,std::unique_ptr<IBullet>&b2){return b1->pos.y<b2->pos.y;});
|
|
std::sort(foregroundEffects.begin(),foregroundEffects.end(),[](std::unique_ptr<Effect>&e1,std::unique_ptr<Effect>&e2){return e1->pos.y<e2->pos.y;});
|
|
std::sort(backgroundEffects.begin(),backgroundEffects.end(),[](std::unique_ptr<Effect>&e1,std::unique_ptr<Effect>&e2){return e1->pos.y<e2->pos.y;});
|
|
|
|
for(auto it=MONSTER_LIST.begin();it!=MONSTER_LIST.end();++it){
|
|
Monster&m=**it;
|
|
if(m.GetPos().y<pl->GetPos().y){//This monster renders before the player does (behind the player)
|
|
if(m.OnUpperLevel()){
|
|
monstersBeforeUpper.push_back(&m);
|
|
}else{
|
|
monstersBeforeLower.push_back(&m);
|
|
}
|
|
} else {//This monster renders after the player does (in front of the player)
|
|
if(m.OnUpperLevel()){
|
|
monstersAfterUpper.push_back(&m);
|
|
}else{
|
|
monstersAfterLower.push_back(&m);
|
|
}
|
|
}
|
|
}
|
|
for(int i=0;i<ItemDrop::drops.size();i++){
|
|
ItemDrop&drop=ItemDrop::drops[i];
|
|
if(drop.GetPos().y<pl->GetPos().y){//This item drop renders before the player does (behind the player)
|
|
if(drop.OnUpperLevel()){
|
|
dropsBeforeUpper.push_back(i);
|
|
}else{
|
|
dropsBeforeLower.push_back(i);
|
|
}
|
|
} else {//This item drop renders after the player does (in front of the player)
|
|
if(drop.OnUpperLevel()){
|
|
dropsAfterUpper.push_back(i);
|
|
}else{
|
|
dropsAfterLower.push_back(i);
|
|
}
|
|
}
|
|
}
|
|
for(auto it=BULLET_LIST.begin();it!=BULLET_LIST.end();++it){
|
|
IBullet*b=(*it).get();
|
|
if(b->OnUpperLevel()){
|
|
bulletsUpper.push_back(b);
|
|
}else{
|
|
bulletsLower.push_back(b);
|
|
}
|
|
}
|
|
for(auto it=foregroundEffects.begin();it!=foregroundEffects.end();++it){
|
|
Effect*e=(*it).get();
|
|
if(e->OnUpperLevel()){
|
|
foregroundEffectsUpper.push_back(e);
|
|
}else{
|
|
foregroundEffectsLower.push_back(e);
|
|
}
|
|
}
|
|
for(auto it=backgroundEffects.begin();it!=backgroundEffects.end();++it){
|
|
Effect*e=(*it).get();
|
|
if(e->OnUpperLevel()){
|
|
backgroundEffectsUpper.push_back(e);
|
|
}else{
|
|
backgroundEffectsLower.push_back(e);
|
|
}
|
|
}
|
|
|
|
for(const ZoneData&zone:GetZones().at("EndZone")){
|
|
if(zone.isUpper){
|
|
upperEndZones.push_back(zone);
|
|
}else{
|
|
endZones.push_back(zone);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AiL::RenderTile(vi2d pos,TilesheetData tileSheet,int tileSheetIndex,vi2d tileSheetPos){
|
|
Pixel tempCol=worldColorFunc(pos*game->GetCurrentMapData().tilewidth);
|
|
|
|
if(MAP_TILESETS.at(tileSheet.tilesetName).animationData.count(tileSheetIndex)){
|
|
int animationDuration_ms=int(MAP_TILESETS.at(tileSheet.tilesetName).animationData.at(tileSheetIndex).size()*"animation_tile_precision"_I);
|
|
int animatedIndex=MAP_TILESETS.at(tileSheet.tilesetName).animationData.at(tileSheetIndex)[size_t(fmod(levelTime*1000.f,animationDuration_ms)/"animation_tile_precision"_I)];
|
|
int tileSheetWidth=MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite()->width/MAP_TILESETS.at(tileSheet.tilesetName).tilewidth;
|
|
int tileSheetX=animatedIndex%tileSheetWidth;
|
|
int tileSheetY=animatedIndex/tileSheetWidth;
|
|
if(MAP_TILESETS.at(tileSheet.tilesetName).tileRepeatData.count(tileSheetIndex)){
|
|
for(int y=0;y<MAP_TILESETS.at(tileSheet.tilesetName).tileRepeatData.at(tileSheetIndex).y;y++){
|
|
for(int x=0;x<MAP_TILESETS.at(tileSheet.tilesetName).tileRepeatData.at(tileSheetIndex).x;x++){
|
|
view.DrawPartialDecal(pos*game->GetCurrentMapData().tilewidth+vf2d{float(x),float(y)}*game->GetCurrentMapData().tilewidth/vf2d{MAP_TILESETS.at(tileSheet.tilesetName).tileRepeatData.at(tileSheetIndex)},vf2d{float(MAP_TILESETS.at(tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tilesetName).tileheight)}/vf2d{MAP_TILESETS.at(tileSheet.tilesetName).tileRepeatData.at(tileSheetIndex)},MAP_TILESETS.at(tileSheet.tilesetName).tileset->Decal(),vi2d{tileSheetX,tileSheetY}*MAP_TILESETS.at(tileSheet.tilesetName).tilewidth,{float(MAP_TILESETS.at(tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tilesetName).tileheight)},tempCol);
|
|
}
|
|
}
|
|
}else view.DrawPartialDecal(pos*game->GetCurrentMapData().tilewidth,{float(MAP_TILESETS.at(tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tilesetName).tileheight)},MAP_TILESETS.at(tileSheet.tilesetName).tileset->Decal(),vi2d{tileSheetX,tileSheetY}*MAP_TILESETS.at(tileSheet.tilesetName).tilewidth,{float(MAP_TILESETS.at(tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tilesetName).tileheight)},tempCol);
|
|
}else{
|
|
if(MAP_TILESETS.at(tileSheet.tilesetName).tileRepeatData.count(tileSheetIndex)){
|
|
for(int y=0;y<MAP_TILESETS.at(tileSheet.tilesetName).tileRepeatData.at(tileSheetIndex).y;y++){
|
|
for(int x=0;x<MAP_TILESETS.at(tileSheet.tilesetName).tileRepeatData.at(tileSheetIndex).x;x++){
|
|
view.DrawPartialDecal(pos*game->GetCurrentMapData().tilewidth+vi2d{x,y}*game->GetCurrentMapData().tilewidth/MAP_TILESETS.at(tileSheet.tilesetName).tileRepeatData.at(tileSheetIndex),{float(MAP_TILESETS.at(tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tilesetName).tileheight)},MAP_TILESETS.at(tileSheet.tilesetName).tileset->Decal(),tileSheetPos*MAP_TILESETS.at(tileSheet.tilesetName).tilewidth,vf2d{float(MAP_TILESETS.at(tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tilesetName).tileheight)}/MAP_TILESETS.at(tileSheet.tilesetName).tileRepeatData.at(tileSheetIndex),tempCol);
|
|
}
|
|
}
|
|
}else{
|
|
view.DrawPartialDecal(pos*game->GetCurrentMapData().tilewidth,{float(MAP_TILESETS.at(tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tilesetName).tileheight)},MAP_TILESETS.at(tileSheet.tilesetName).tileset->Decal(),tileSheetPos*MAP_TILESETS.at(tileSheet.tilesetName).tilewidth,{float(MAP_TILESETS.at(tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tilesetName).tileheight)},tempCol);
|
|
if("debug_tileIDs"_I)view.DrawStringDecal(pos*game->GetCurrentMapData().tilewidth,std::to_string(tileSheetIndex));
|
|
}
|
|
}
|
|
}
|
|
|
|
void AiL::RenderTile(TileRenderData&tileSheet,Pixel col){
|
|
if(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).animationData.count(tileSheet.tileID%1000000)){
|
|
int animationDuration_ms=int(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).animationData.at(tileSheet.tileID%1000000).size()*"animation_tile_precision"_I);
|
|
int animatedIndex=MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).animationData.at(tileSheet.tileID%1000000)[size_t(fmod(levelTime*1000.f,animationDuration_ms)/"animation_tile_precision"_I)];
|
|
int tileSheetWidth=MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileset->Sprite()->width/MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tilewidth;
|
|
int tileSheetX=animatedIndex%tileSheetWidth;
|
|
int tileSheetY=animatedIndex/tileSheetWidth;
|
|
if(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileRepeatData.count(animatedIndex)){
|
|
for(int y=0;y<MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileRepeatData.at(animatedIndex).y;y++){
|
|
for(int x=0;x<MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileRepeatData.at(animatedIndex).x;x++){
|
|
view.DrawPartialDecal(tileSheet.pos+vi2d{x,y}*game->GetCurrentMapData().tilewidth/MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileRepeatData.at(animatedIndex),{float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileheight)},MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileset->Decal(),vi2d{tileSheetX,tileSheetY}*vf2d{float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileheight)},vf2d{float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileheight)}/MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileRepeatData.at(animatedIndex),col);
|
|
}
|
|
}
|
|
}else view.DrawPartialDecal(tileSheet.pos,{float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileheight)},MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileset->Decal(),vi2d{tileSheetX,tileSheetY}*vf2d{float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileheight)},{float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileheight)},col);
|
|
}else{
|
|
if(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileRepeatData.count(tileSheet.tileID)){
|
|
for(int y=0;y<MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileRepeatData.at(tileSheet.tileID).y;y++){
|
|
for(int x=0;x<MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileRepeatData.at(tileSheet.tileID).x;x++){
|
|
view.DrawPartialDecal(tileSheet.pos+vi2d{x,y}*game->GetCurrentMapData().tilewidth/MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileRepeatData.at(tileSheet.tileID),{float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileheight)},MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileset->Decal(),tileSheet.tileSheetPos,vf2d{float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileheight)}/MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileRepeatData.at(tileSheet.tileID),col);
|
|
}
|
|
}
|
|
}else view.DrawPartialDecal(tileSheet.pos,{float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileheight)},MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileset->Decal(),tileSheet.tileSheetPos,{float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tileSheet.tilesetName).tileheight)},col);
|
|
}
|
|
}
|
|
|
|
void AiL::RenderWorld(float fElapsedTime){
|
|
LayerTag*bridgeLayer=nullptr;
|
|
bool bridgeLayerFade=false;
|
|
Player*pl=GetPlayer();
|
|
|
|
PopulateRenderLists();
|
|
|
|
auto RenderPlayer=[&](vf2d pos,vf2d scale){
|
|
if(player->IsInvisible())return;
|
|
vf2d playerScale=vf2d(player->GetSizeMult(),player->GetSizeMult());
|
|
int count=0;
|
|
for(vf2d&pos:player->ghostPositions){
|
|
view.DrawPartialRotatedDecal(pos,player->GetFrame().GetSourceImage()->Decal(),player->GetSpinAngle(),{12,12},player->GetFrame().GetSourceRect().pos,player->GetFrame().GetSourceRect().size,playerScale,{0,0,0,uint8_t(float(count)/player->RETREAT_GHOST_FRAMES*255)});
|
|
count++;
|
|
}
|
|
if(player->teleportAnimationTimer>0){
|
|
playerScale.x=120*float(abs(pow(player->teleportAnimationTimer-"Wizard.Right Click Ability.AnimationTime"_F/2,3)));
|
|
pos=player->teleportStartPosition.lerp(player->teleportTarget,("Wizard.Right Click Ability.AnimationTime"_F-player->teleportAnimationTimer)/"Wizard.Right Click Ability.AnimationTime"_F);
|
|
}
|
|
const std::vector<Buff>attackBuffs{player->GetStatBuffs({"Attack","Attack %"})};
|
|
const std::vector<Buff>movespeedBuffs{player->GetBuffs(BuffType::SPEEDBOOST)};
|
|
const std::vector<Buff>adrenalineRushBuffs{player->GetBuffs(BuffType::ADRENALINE_RUSH)};
|
|
|
|
Pixel playerCol{WHITE};
|
|
if(attackBuffs.size()>0)playerCol={255,uint8_t(255*abs(sin(1.4f*attackBuffs[0].duration))),uint8_t(255*abs(sin(1.4f*attackBuffs[0].duration)))};
|
|
else if(adrenalineRushBuffs.size()>0)playerCol={uint8_t(255*abs(sin(6.f*adrenalineRushBuffs[0].duration))),255,uint8_t(255*abs(sin(6.f*adrenalineRushBuffs[0].duration)))};
|
|
else if(movespeedBuffs.size()>0)playerCol={uint8_t(255*abs(sin(2.f*movespeedBuffs[0].duration))),255,uint8_t(255*abs(sin(2.f*movespeedBuffs[0].duration)))};
|
|
view.DrawPartialSquishedRotatedDecal(pos+vf2d{0,-player->GetZ()*(std::signbit(scale.y)?-1:1)},player->GetFrame().GetSourceImage()->Decal(),player->GetSpinAngle(),{12,12},player->GetFrame().GetSourceRect().pos,player->GetFrame().GetSourceRect().size,playerScale*scale,{1.f,player->ySquishFactor},playerCol);
|
|
DrawAfterImage:view.DrawRotatedDecal(player->afterImagePos,player->afterImage.Decal(),0.f,player->afterImage.Sprite()->Size()/2,{player->GetSizeMult(),player->GetSizeMult()},{0xFFDCDA});
|
|
SetDecalMode(DecalMode::NORMAL);
|
|
if(player->GetState()==State::BLOCK){
|
|
view.DrawDecal(player->GetPos()+vf2d{0,-player->GetZ()*(std::signbit(scale.y)?-1:1)}-vf2d{12,12},GFX["block.png"].Decal());
|
|
}
|
|
};
|
|
|
|
auto RenderZone=[&](geom2d::rect<int>&zone){
|
|
game->SetDecalMode(DecalMode::ADDITIVE);
|
|
Pixel ringColor={128,128,128,uint8_t(abs(sin(game->levelTime))*255)};
|
|
Decal*ringDecal=GFX["finishring_green.png"].Decal();
|
|
if(!player->IsOutOfCombat()){
|
|
game->SetDecalMode(DecalMode::NORMAL);
|
|
ringColor.r=ringColor.g=ringColor.b=64;
|
|
ringDecal=GFX["finishring.png"].Decal();
|
|
}
|
|
view.DrawDecal(zone.pos,ringDecal,vf2d(zone.size)/vf2d(GFX["finishring.png"].Sprite()->Size()),ringColor);
|
|
game->SetDecalMode(DecalMode::NORMAL);
|
|
};
|
|
|
|
enum class RenderMode{
|
|
REFLECTIVE_TILES,
|
|
NORMAL_TILES,
|
|
EMPTY_TILES,
|
|
};
|
|
|
|
reflectionUpdateTimer-=fElapsedTime;
|
|
if(reflectionUpdateTimer<=0){
|
|
reflectionStepTime+="water_reflection_time_step"_F;
|
|
reflectionUpdateTimer="water_reflection_update_time"_F;
|
|
}
|
|
|
|
#pragma region Basic Tile Layer Rendering
|
|
const MapTag¤tMap=GetCurrentMapData();
|
|
vf2d backgroundToMapRatio;
|
|
vf2d backgroundExcessAmount;
|
|
vf2d backgroundSize;
|
|
vf2d foregroundExcessAmount;
|
|
vf2d foregroundSize;
|
|
vf2d foregroundToMapRatio;
|
|
const vf2d mapPixelSize=currentMap.MapSize*currentMap.TileSize;
|
|
if(GetCurrentMap().backdrop.length()>0){
|
|
backgroundSize=BACKDROP_DATA[GetCurrentMap().backdrop].Sprite()->Size();
|
|
backgroundExcessAmount=backgroundSize-vf2d(WINDOW_SIZE); //Extends outside the boundaries of the screen. Essentially the background size minus the size of the screen and take half to get the amount that extends in any one direction.
|
|
backgroundToMapRatio=1-(backgroundExcessAmount/mapPixelSize); //Expected range is from -half map size to +half map size.
|
|
|
|
DrawPartialDecal({},BACKDROP_DATA[GetCurrentMap().backdrop].Decal(),backgroundToMapRatio*-camera.GetPosition()+view.GetWorldOffset()+WINDOW_SIZE/2,WINDOW_SIZE);
|
|
if(FOREDROP_DATA.count(GetCurrentMap().backdrop)){
|
|
foregroundSize=FOREDROP_DATA[GetCurrentMap().backdrop].Sprite()->Size();
|
|
foregroundExcessAmount=foregroundSize-vf2d(WINDOW_SIZE); //Extends outside the boundaries of the screen. Essentially the background size minus the size of the screen and take half to get the amount that extends in any one direction.
|
|
foregroundToMapRatio=1-(foregroundExcessAmount/mapPixelSize);
|
|
DrawPartialDecal({},FOREDROP_DATA[GetCurrentMap().backdrop].Decal(),foregroundToMapRatio*-camera.GetPosition()+view.GetWorldOffset()+WINDOW_SIZE/2,WINDOW_SIZE);
|
|
}
|
|
}else{
|
|
FillRectDecal({0,0},GetScreenSize(),{100,180,100});
|
|
}
|
|
for(RenderMode mode=RenderMode::REFLECTIVE_TILES;mode<=RenderMode::EMPTY_TILES;mode=RenderMode(int(mode)+1)){
|
|
if(mode==RenderMode::NORMAL_TILES){
|
|
SetDecalMode(DecalMode::ADDITIVE);
|
|
float reflectionHeight=(float(player->GetFrame().GetSourceRect().size.y)-8)*player->GetSizeMult();
|
|
float reflectionBottom=player->GetPos().y+reflectionHeight;
|
|
float cutOff=reflectionBottom-GetCurrentMapData().height*GetCurrentMapData().tileheight;
|
|
float multiplierX=0.9f;
|
|
multiplierX*=(1-abs(sin(reflectionStepTime))*"water_reflection_scale_factor"_F);
|
|
multiplierX*=(1-abs(cos(1.5f*reflectionStepTime))*"water_reflection_scale_factor"_F);
|
|
float reflectionRatioX=abs(sin(reflectionStepTime))*"water_reflection_scale_factor"_F;
|
|
RenderPlayer(player->GetPos()+vf2d{reflectionRatioX*player->GetFrame().GetSourceRect().size.x,float(player->GetFrame().GetSourceRect().size.y)-8}*player->GetSizeMult(),{multiplierX,-1});
|
|
for(std::shared_ptr<Monster>&m:MONSTER_LIST){
|
|
m->DrawReflection(reflectionRatioX,multiplierX);
|
|
}
|
|
SetDecalMode(DecalMode::NORMAL);
|
|
}
|
|
if(GetCurrentMapData().optimized){
|
|
view.FillRectDecal(-WINDOW_SIZE,vf2d{float(GetCurrentMapData().width),float(GetCurrentMapData().height)}*float(GetCurrentMapData().tilewidth)+vf2d{WINDOW_SIZE}*2,{100,180,100});
|
|
view.DrawDecal({0,0},MAP_DATA[GetCurrentLevel()].optimizedTile->Decal());
|
|
}else{
|
|
for (int x = view.GetTopLeftTile().x/GetCurrentMapData().tilewidth-1; x <= view.GetBottomRightTile().x/GetCurrentMapData().tilewidth; x++){
|
|
for (int y = view.GetTopLeftTile().y/GetCurrentMapData().tilewidth-1; y <= view.GetBottomRightTile().y/GetCurrentMapData().tilewidth; y++){
|
|
if(x>=0&&x<GetCurrentMapData().width&&y>=0&&y<GetCurrentMapData().height){
|
|
switch(mode){
|
|
case RenderMode::NORMAL_TILES:{
|
|
for(LayerTag&layer:MAP_DATA[currentLevel].LayerData){
|
|
if(Unlock::IsUnlocked(layer.unlockCondition)){
|
|
if(IsBridgeLayer(layer)){
|
|
bridgeLayer=&layer;
|
|
if(!bridgeLayerFade&&!player->upperLevel){
|
|
int tileID=layer.tiles[y][x]-1;
|
|
if(tileID!=-1){
|
|
int playerXTruncated=int(player->GetPos().x)/GetCurrentMapData().tilewidth;
|
|
int playerYTruncated=int(player->GetPos().y)/GetCurrentMapData().tilewidth;
|
|
if(playerXTruncated==x&&playerYTruncated==y){
|
|
bridgeLayerFade=true;
|
|
}
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
int tileID=layer.tiles[y][x]-1;
|
|
if(tileID!=-1){
|
|
TilesheetData tileSheet=GetTileSheet(currentLevel,tileID);
|
|
int tileSheetWidth=MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite()->width/MAP_TILESETS.at(tileSheet.tilesetName).tilewidth;
|
|
int tileSheetHeight=MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite()->height/MAP_TILESETS.at(tileSheet.tilesetName).tileheight;
|
|
int tileSheetIndex=tileID-(tileSheet.firstgid-1);
|
|
int tileSheetX=tileSheetIndex%tileSheetWidth;
|
|
int tileSheetY=tileSheetIndex/tileSheetWidth;
|
|
if(!IsForegroundTile(tileSheet,tileSheetIndex)&&!IsUpperForegroundTile(tileSheetIndex)&&!IsReflectiveTile(tileSheet,tileSheetIndex)){
|
|
if(layer.tag.data["class"]!="CollisionOnly"){visibleTiles.erase({x,y});}
|
|
RenderTile({x,y},tileSheet,tileSheetIndex,{tileSheetX,tileSheetY});
|
|
#pragma region Debug Collision boxes
|
|
#ifdef _DEBUG
|
|
if("debug_collision_boxes"_I){
|
|
if(MAP_TILESETS.at(tileSheet.tilesetName).collision.find(tileSheetIndex)!=MAP_TILESETS.at(tileSheet.tilesetName).collision.end()){
|
|
const geom2d::rect<float>collision=const_cast<TilesetData&>(MAP_TILESETS.at(tileSheet.tilesetName)).collision.at(tileSheetIndex).collision;
|
|
view.FillRectDecal(vf2d{float(x),float(y)}*float(GetCurrentMapData().tilewidth)+collision.pos,collision.size,{0,0,0,128});
|
|
view.DrawRectDecal(vf2d{float(x),float(y)}*float(GetCurrentMapData().tilewidth)+collision.pos,collision.size,GREY);
|
|
}
|
|
}
|
|
#endif
|
|
#pragma endregion
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}break;
|
|
case RenderMode::REFLECTIVE_TILES:{
|
|
visibleTiles.insert({x,y});
|
|
for(LayerTag&layer:MAP_DATA[currentLevel].LayerData){
|
|
if(Unlock::IsUnlocked(layer.unlockCondition)){
|
|
int tileID=layer.tiles[y][x]-1;
|
|
if(tileID!=-1){
|
|
TilesheetData tileSheet=GetTileSheet(currentLevel,tileID);
|
|
int tileSheetWidth=MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite()->width/MAP_TILESETS.at(tileSheet.tilesetName).tilewidth;
|
|
int tileSheetHeight=MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite()->height/MAP_TILESETS.at(tileSheet.tilesetName).tileheight;
|
|
int tileSheetIndex=tileID-(tileSheet.firstgid-1);
|
|
int tileSheetX=tileSheetIndex%tileSheetWidth;
|
|
int tileSheetY=tileSheetIndex/tileSheetWidth;
|
|
if(IsReflectiveTile(tileSheet,tileSheetIndex)){
|
|
if(layer.tag.data["class"]!="CollisionOnly"){visibleTiles.erase({x,y});}
|
|
RenderTile({x,y},tileSheet,tileSheetIndex,{tileSheetX,tileSheetY});
|
|
}
|
|
#ifdef _DEBUG
|
|
if("debug_collision_boxes"_I){
|
|
if(MAP_TILESETS.at(tileSheet.tilesetName).collision.find(tileSheetIndex)!=MAP_TILESETS.at(tileSheet.tilesetName).collision.end()){
|
|
const geom2d::rect<float>collision=const_cast<TilesetData&>(MAP_TILESETS.at(tileSheet.tilesetName)).collision.at(tileSheetIndex).collision;
|
|
view.FillRectDecal(vf2d{float(x),float(y)}*float(GetCurrentMapData().tilewidth)+collision.pos,collision.size,{0,0,0,128});
|
|
view.DrawRectDecal(vf2d{float(x),float(y)}*float(GetCurrentMapData().tilewidth)+collision.pos,collision.size,GREY);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}break;
|
|
case RenderMode::EMPTY_TILES:{
|
|
if(visibleTiles.count({x,y})){
|
|
#pragma region Render Backdrop
|
|
if(GetCurrentMap().backdrop.length()>0){
|
|
vf2d tileWorldPos=vi2d{x,y}*GetCurrentMapData().tilewidth;
|
|
view.DrawPartialDecal(tileWorldPos,{float(GetCurrentMapData().tilewidth),float(GetCurrentMapData().tilewidth)},BACKDROP_DATA[GetCurrentMap().backdrop].Decal(),backgroundToMapRatio*-camera.GetPosition()+tileWorldPos+WINDOW_SIZE/2,{float(GetCurrentMapData().tilewidth),float(GetCurrentMapData().tilewidth)});
|
|
if(FOREDROP_DATA.count(GetCurrentMap().backdrop))view.DrawPartialDecal(tileWorldPos,{float(GetCurrentMapData().tilewidth),float(GetCurrentMapData().tilewidth)},FOREDROP_DATA[GetCurrentMap().backdrop].Decal(),foregroundToMapRatio*-camera.GetPosition()+tileWorldPos+WINDOW_SIZE/2,{float(GetCurrentMapData().tilewidth),float(GetCurrentMapData().tilewidth)});
|
|
}else{
|
|
view.FillRectDecal(vi2d{x,y}*GetCurrentMapData().tilewidth,{float(GetCurrentMapData().tilewidth),float(GetCurrentMapData().tilewidth)},{100,180,100});
|
|
}
|
|
#pragma endregion
|
|
}
|
|
}break;
|
|
}
|
|
#pragma region Debug A* Tiles
|
|
#ifdef _DEBUG
|
|
if(DEBUG_PATHFINDING){
|
|
for(int y2=0;y2<2;y2++){
|
|
for(int x2=0;x2<2;x2++){
|
|
vf2d tilePos=vf2d{float(x),float(y)}*24;
|
|
vf2d gridPos=tilePos+pathfinder.gridSpacing*vf2d{float(x2),float(y2)};
|
|
if(pathfinder.nodes.count(gridPos)){
|
|
Pixel col=RED;
|
|
if(!pathfinder.nodes[gridPos].bObstacle&&!pathfinder.nodes[gridPos].bObstacleUpper){
|
|
col=GREEN;
|
|
}
|
|
view.FillRectDecal(gridPos,pathfinder.gridSpacing,{col.r,col.g,col.b,128});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#pragma endregion
|
|
}else{
|
|
if(GetCurrentMap().backdrop.length()>0){
|
|
vf2d tileWorldPos=vi2d{x,y}*GetCurrentMapData().tilewidth;
|
|
view.DrawPartialDecal(tileWorldPos,{float(GetCurrentMapData().tilewidth),float(GetCurrentMapData().tilewidth)},BACKDROP_DATA[GetCurrentMap().backdrop].Decal(),backgroundToMapRatio*-camera.GetPosition()+tileWorldPos+WINDOW_SIZE/2,{float(GetCurrentMapData().tilewidth),float(GetCurrentMapData().tilewidth)});
|
|
if(FOREDROP_DATA.count(GetCurrentMap().backdrop))view.DrawPartialDecal(tileWorldPos,{float(GetCurrentMapData().tilewidth),float(GetCurrentMapData().tilewidth)},FOREDROP_DATA[GetCurrentMap().backdrop].Decal(),foregroundToMapRatio*-camera.GetPosition()+tileWorldPos+WINDOW_SIZE/2,{float(GetCurrentMapData().tilewidth),float(GetCurrentMapData().tilewidth)});
|
|
}else{
|
|
view.FillRectDecal(vi2d{x,y}*GetCurrentMapData().tilewidth,{float(GetCurrentMapData().tilewidth),float(GetCurrentMapData().tilewidth)},{100,180,100});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(bridgeLayerFade){
|
|
bridgeFadeFactor=std::min(bridgeFadeFactor+fElapsedTime,TileGroup::FADE_TIME);
|
|
}else{
|
|
bridgeFadeFactor=std::max(bridgeFadeFactor-fElapsedTime,0.f);
|
|
}
|
|
}
|
|
#pragma endregion
|
|
|
|
for(ZoneData&zone:endZones){
|
|
RenderZone(zone.zone);
|
|
}
|
|
auto RenderPrecastTargetingIndicator=[&](){
|
|
if(player->GetState()==State::PREP_CAST){
|
|
float precastSize=GetPlayer()->castPrepAbility->precastInfo.size;
|
|
float precastRange=GetPlayer()->castPrepAbility->precastInfo.range;
|
|
vf2d scale=vf2d{precastSize,precastSize}*2/3.f;
|
|
vf2d centerPoint=GetPlayer()->GetWorldAimingLocation()-vf2d{GFX["circle.png"].Sprite()->width*scale.x/2,GFX["circle.png"].Sprite()->height*scale.y/2};
|
|
float distance=float(sqrt(pow(player->GetX()-GetPlayer()->GetWorldAimingLocation().x,2)+pow(player->GetY()-GetPlayer()->GetWorldAimingLocation().y,2)));
|
|
if(distance>precastRange){//Clamp the distance.
|
|
vf2d pointToCursor = {GetPlayer()->GetWorldAimingLocation().x-player->GetX(),GetPlayer()->GetWorldAimingLocation().y-player->GetY()};
|
|
pointToCursor=pointToCursor.norm()*precastRange;
|
|
vf2d centerPoint=player->GetPos()+pointToCursor-vf2d{GFX["circle.png"].Sprite()->width*scale.x/2,GFX["circle.png"].Sprite()->height*scale.y/2};
|
|
view.DrawDecal(centerPoint,GFX["circle.png"].Decal(),scale,{255,0,0,96});
|
|
} else {
|
|
view.DrawDecal(centerPoint,GFX["circle.png"].Decal(),scale,{255,0,0,96});
|
|
}
|
|
}
|
|
};
|
|
if(!player->OnUpperLevel()){
|
|
RenderPrecastTargetingIndicator();
|
|
}
|
|
|
|
#pragma region Foreground Rendering Preparation
|
|
for(TileGroup&group:foregroundTileGroups){
|
|
if(view.IsRectVisible(group.GetRange().pos,group.GetRange().size)){
|
|
if(geom2d::overlaps(group.GetFadeRange(),player->pos)){
|
|
if(GameState::STATE!=GameState::states[States::MAIN_MENU]){ //Don't fade out tile groups while we are on the main menu.
|
|
group.fadeFactor=std::min(group.fadeFactor+fElapsedTime,TileGroup::FADE_TIME);
|
|
}
|
|
} else {
|
|
group.fadeFactor=std::max(group.fadeFactor-fElapsedTime,0.f);
|
|
}
|
|
|
|
for(TileRenderData&tile:group.GetTiles()){
|
|
tile.tileOpacity=group.fadeFactor;
|
|
tile.group=&group;
|
|
|
|
#pragma region Unhiding tile detection
|
|
auto it=MAP_TILESETS.at(tile.tileSheet.tilesetName).foregroundTiles.find(tile.tileID); //We're looking for tiles that are marked as should not be faded away by a tile group.
|
|
if(it!=MAP_TILESETS.at(tile.tileSheet.tilesetName).foregroundTiles.end()){
|
|
if(!(*it).second.hide)tile.tileOpacity=0.f;
|
|
}
|
|
#pragma endregion
|
|
if(MAP_TILESETS.at(tile.tileSheet.tilesetName).collision.find(tile.tileID)!=MAP_TILESETS.at(tile.tileSheet.tilesetName).collision.end()){
|
|
tilesWithCollision.push_back(&tile);
|
|
}else{
|
|
tilesWithoutCollision.push_back(&tile); //Tiles without collision are assumed to always be in the foreground. They don't have any depth rules.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#pragma endregion
|
|
|
|
std::sort(tilesWithCollision.begin(),tilesWithCollision.end(),[](TileRenderData*tile1,TileRenderData*tile2){
|
|
if(tile1->group->GetCollisionRange().middle().y!=tile2->group->GetCollisionRange().middle().y){
|
|
return tile1->group->GetCollisionRange().middle().y<tile2->group->GetCollisionRange().middle().y;
|
|
}else
|
|
if(tile1->pos.y!=tile2->pos.y){
|
|
return tile1->pos.y<tile2->pos.y;
|
|
}else{
|
|
return tile1->layerID<tile2->layerID;
|
|
}
|
|
});
|
|
std::sort(tilesWithoutCollision.begin(),tilesWithoutCollision.end(),[](TileRenderData*tile1,TileRenderData*tile2){return tile1->layerID<tile2->layerID;});
|
|
|
|
for(auto it=monstersBeforeLower.begin();it!=monstersBeforeLower.end();++it){
|
|
Monster&m=**it;
|
|
m.strategyDraw(this,m,MONSTER_DATA[m.GetName()].GetAIStrategy());
|
|
}
|
|
for(auto it=monstersAfterLower.begin();it!=monstersAfterLower.end();++it){
|
|
Monster&m=**it;
|
|
m.strategyDraw(this,m,MONSTER_DATA[m.GetName()].GetAIStrategy());
|
|
}
|
|
|
|
#pragma region Render Background Effects
|
|
{
|
|
auto it=backgroundEffectsLower.begin();
|
|
while(it!=backgroundEffectsLower.end()){
|
|
const Effect&e=**it;
|
|
e.Draw();
|
|
++it;
|
|
}
|
|
}
|
|
#pragma endregion
|
|
|
|
auto monstersBeforeLowerIt=monstersBeforeLower.begin();
|
|
auto monstersAfterLowerIt=monstersAfterLower.begin();
|
|
auto dropsBeforeLowerIt=dropsBeforeLower.begin();
|
|
auto dropsAfterLowerIt=dropsAfterLower.begin();
|
|
#pragma region Rendering before Tile Depth Rendering
|
|
if(tilesWithCollision.size()>0){
|
|
const float topTileY=(*tilesWithCollision.begin())->group->GetCollisionRange().middle().y;
|
|
#pragma region Depth Ordered Rendering
|
|
while(monstersBeforeLowerIt!=monstersBeforeLower.end()){
|
|
Monster&m=**monstersBeforeLowerIt;
|
|
if(m.pos.y<topTileY){
|
|
m.Draw();
|
|
++monstersBeforeLowerIt;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
while(dropsBeforeLowerIt!=dropsBeforeLower.end()){
|
|
const ItemDrop&drop=ItemDrop::drops[*dropsBeforeLowerIt];
|
|
if(drop.pos.y<topTileY){
|
|
drop.Draw();
|
|
++dropsBeforeLowerIt;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if(!player->rendered&&!player->upperLevel&&player->GetPos().y<topTileY){
|
|
//If we are supposed to render the player, everything else before must be rendered at this point.
|
|
while(monstersBeforeLowerIt!=monstersBeforeLower.end()){
|
|
Monster&m=**monstersBeforeLowerIt;
|
|
m.Draw();
|
|
++monstersBeforeLowerIt;
|
|
}
|
|
while(dropsBeforeLowerIt!=dropsBeforeLower.end()){
|
|
const ItemDrop&drop=ItemDrop::drops[*dropsBeforeLowerIt];
|
|
drop.Draw();
|
|
++dropsBeforeLowerIt;
|
|
}
|
|
player->rendered=true;
|
|
if(player->GetZ()>0){
|
|
vf2d shadowScale=vf2d{8*player->GetSizeMult()/3.f,1}/std::max(1.f,player->GetZ()/24);
|
|
view.DrawDecal(player->GetPos()-vf2d{3,3}*shadowScale/2+vf2d{0,6*player->GetSizeMult()},GFX["circle.png"].Decal(),shadowScale,BLACK);
|
|
}
|
|
if(player->IsUsingAdditiveBlending())SetDecalMode(DecalMode::ADDITIVE);
|
|
else SetDecalMode(DecalMode::NORMAL);
|
|
RenderPlayer(player->GetPos(),{1,1});
|
|
}
|
|
while(monstersAfterLowerIt!=monstersAfterLower.end()){
|
|
Monster&m=**monstersAfterLowerIt;
|
|
if(m.pos.y<topTileY){
|
|
m.Draw();
|
|
++monstersAfterLowerIt;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
while(dropsAfterLowerIt!=dropsAfterLower.end()){
|
|
const ItemDrop&drop=ItemDrop::drops[*dropsAfterLowerIt];
|
|
if(drop.pos.y<topTileY){
|
|
drop.Draw();
|
|
++dropsAfterLowerIt;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
#pragma endregion
|
|
}
|
|
#pragma endregion
|
|
#pragma region Foreground Rendering w/Depth
|
|
for(TileRenderData*tile:tilesWithCollision){
|
|
#pragma region Depth Ordered Rendering
|
|
if(!player->rendered&&!player->upperLevel&&player->GetPos().y<tile->group->GetCollisionRange().middle().y){
|
|
//If we are supposed to render the player, everything else before must be rendered at this point.
|
|
while(monstersBeforeLowerIt!=monstersBeforeLower.end()){
|
|
Monster&m=**monstersBeforeLowerIt;
|
|
m.Draw();
|
|
++monstersBeforeLowerIt;
|
|
}
|
|
while(dropsBeforeLowerIt!=dropsBeforeLower.end()){
|
|
const ItemDrop&drop=ItemDrop::drops[*dropsBeforeLowerIt];
|
|
drop.Draw();
|
|
++dropsBeforeLowerIt;
|
|
}
|
|
player->rendered=true;
|
|
if(player->GetZ()>0){
|
|
vf2d shadowScale=vf2d{8*player->GetSizeMult()/3.f,1}/std::max(1.f,player->GetZ()/24);
|
|
view.DrawDecal(player->GetPos()-vf2d{3,3}*shadowScale/2+vf2d{0,6*player->GetSizeMult()},GFX["circle.png"].Decal(),shadowScale,BLACK);
|
|
}
|
|
RenderPlayer(player->GetPos(),{1,1});
|
|
}
|
|
while(monstersAfterLowerIt!=monstersAfterLower.end()){
|
|
Monster&m=**monstersAfterLowerIt;
|
|
if(m.pos.y<tile->group->GetCollisionRange().middle().y){
|
|
m.Draw();
|
|
++monstersAfterLowerIt;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
while(dropsAfterLowerIt!=dropsAfterLower.end()){
|
|
const ItemDrop&drop=ItemDrop::drops[*dropsAfterLowerIt];
|
|
if(drop.pos.y<tile->group->GetCollisionRange().middle().y){
|
|
drop.Draw();
|
|
++dropsAfterLowerIt;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
#pragma endregion
|
|
RenderTile(*tile,{255,255,255,uint8_t(255-tile->tileOpacity/TileGroup::FADE_TIME*TileGroup::FADE_AMT)});
|
|
float distToPlayer=geom2d::line<float>(player->GetPos(),tile->pos+vf2d{12,12}).length();
|
|
if(GameSettings::TerrainCollisionBoxesEnabled()&&distToPlayer<24*3&&tile->tileOpacity>0&&MAP_TILESETS.at(tile->tileSheet.tilesetName).collision.find(tile->tileID)!=MAP_TILESETS.at(tile->tileSheet.tilesetName).collision.end()){
|
|
const geom2d::rect<float>collision=MAP_TILESETS.at(tile->tileSheet.tilesetName).collision.at(tile->tileID).collision;
|
|
distToPlayer/=4;
|
|
if(distToPlayer<1){distToPlayer=1;}
|
|
view.FillRectDecal(tile->pos+collision.pos,collision.size,{255,0,0,uint8_t(128*tile->tileOpacity/sqrt(distToPlayer))});
|
|
view.DrawRectDecal(tile->pos+collision.pos,collision.size,{128,0,0,uint8_t(255/sqrt(distToPlayer))});
|
|
#ifdef _DEBUG
|
|
if("debug_collision_boxes_snapping"_I){
|
|
if(fmod(tile->pos.x+collision.pos.x,1.f)!=0.f||fmod(tile->pos.y+collision.pos.y,1.f)!=0.f){
|
|
view.DrawShadowStringPropDecal(tile->pos+collision.pos-vf2d{0,24},(tile->pos+collision.pos).str(),RED,BLACK,{0.5f,1.f},24.f);
|
|
view.DrawShadowStringPropDecal(tile->pos+collision.pos+vf2d{0,24},((tile->pos+collision.pos)+(collision.size)).str(),GREEN,BLACK,{0.5f,1.f},24.f);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
#pragma endregion
|
|
#pragma region Remaining Rendering
|
|
while(monstersBeforeLowerIt!=monstersBeforeLower.end()){
|
|
Monster*const m=*monstersBeforeLowerIt;
|
|
m->Draw();
|
|
++monstersBeforeLowerIt;
|
|
}
|
|
for(const Effect*const e:backgroundEffectsLower){
|
|
e->Draw();
|
|
}
|
|
while(dropsBeforeLowerIt!=dropsBeforeLower.end()){
|
|
const int dropInd=*dropsBeforeLowerIt;
|
|
ItemDrop::drops[dropInd].Draw();
|
|
++dropsBeforeLowerIt;
|
|
}
|
|
if(!player->rendered&&!player->upperLevel){
|
|
player->rendered=true;
|
|
if(player->GetZ()>0){
|
|
vf2d shadowScale=vf2d{8*player->GetSizeMult()/3.f,1}/std::max(1.f,player->GetZ()/24);
|
|
view.DrawDecal(player->GetPos()-vf2d{3,3}*shadowScale/2+vf2d{0,6*player->GetSizeMult()},GFX["circle.png"].Decal(),shadowScale,BLACK);
|
|
}
|
|
RenderPlayer(player->GetPos(),{1,1});
|
|
}
|
|
while(monstersAfterLowerIt!=monstersAfterLower.end()){
|
|
Monster*const m=*monstersAfterLowerIt;
|
|
m->Draw();
|
|
++monstersAfterLowerIt;
|
|
}
|
|
while(dropsAfterLowerIt!=dropsAfterLower.end()){
|
|
const int dropInd=*dropsAfterLowerIt;
|
|
ItemDrop::drops[dropInd].Draw();
|
|
++dropsAfterLowerIt;
|
|
}
|
|
for(const IBullet*const b:bulletsLower){
|
|
b->_Draw();
|
|
}
|
|
for(const Effect*const e:foregroundEffectsLower){
|
|
e->Draw();
|
|
}
|
|
#pragma endregion
|
|
#pragma region Permanent Foreground Rendering
|
|
for(TileRenderData*tile:tilesWithoutCollision){
|
|
RenderTile(*tile,{255,255,255,uint8_t(255-tile->tileOpacity/TileGroup::FADE_TIME*TileGroup::FADE_AMT)});
|
|
float distToPlayer=geom2d::line<float>(player->GetPos(),tile->pos+vf2d{12,12}).length();
|
|
if(distToPlayer<24*3&&tile->tileOpacity>0&&MAP_TILESETS.at(tile->tileSheet.tilesetName).collision.find(tile->tileID)!=MAP_TILESETS.at(tile->tileSheet.tilesetName).collision.end()){
|
|
const geom2d::rect<float>collision=MAP_TILESETS.at(tile->tileSheet.tilesetName).collision.at(tile->tileID).collision;
|
|
distToPlayer/=4;
|
|
if(distToPlayer<1){distToPlayer=1;}
|
|
view.FillRectDecal(tile->pos+collision.pos,collision.size,{255,0,0,uint8_t(128*tile->tileOpacity/sqrt(distToPlayer))});
|
|
view.DrawRectDecal(tile->pos+collision.pos,collision.size,{128,0,0,uint8_t(255/sqrt(distToPlayer))});
|
|
#ifdef _DEBUG
|
|
if("debug_collision_boxes_snapping"_I){
|
|
if(fmod(tile->pos.x+collision.pos.x,1.f)!=0.f||fmod(tile->pos.y+collision.pos.y,1.f)!=0.f){
|
|
view.DrawShadowStringPropDecal(tile->pos+collision.pos-vf2d{0,24},(tile->pos+collision.pos).str(),RED,BLACK,{0.5f,1.f},24.f);
|
|
view.DrawShadowStringPropDecal(tile->pos+collision.pos+vf2d{0,24},((tile->pos+collision.pos)+(collision.size)).str(),GREEN,BLACK,{0.5f,1.f},24.f);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
#pragma endregion
|
|
|
|
tilesWithCollision.clear();
|
|
tilesWithoutCollision.clear();
|
|
|
|
#pragma region Bridge Layer Rendering
|
|
if(bridgeLayer!=nullptr){
|
|
for (int x = view.GetTopLeftTile().x/game->GetCurrentMapData().tilewidth-1; x <= view.GetBottomRightTile().x/game->GetCurrentMapData().tilewidth; x++){
|
|
for (int y = view.GetTopLeftTile().y/game->GetCurrentMapData().tilewidth-1; y <= view.GetBottomRightTile().y/game->GetCurrentMapData().tilewidth; y++){
|
|
if(x>=0&&x<GetCurrentMapData().width&&y>=0&&y<GetCurrentMapData().height){
|
|
int tileID=bridgeLayer->tiles[y][x]-1;
|
|
if(tileID!=-1){
|
|
TilesheetData tileSheet=GetTileSheet(currentLevel,tileID);
|
|
int tileSheetWidth=MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite()->width/MAP_TILESETS.at(tileSheet.tilesetName).tilewidth;
|
|
int tileSheetHeight=MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite()->height/MAP_TILESETS.at(tileSheet.tilesetName).tileheight;
|
|
int tileSheetIndex=tileID-(tileSheet.firstgid-1);
|
|
int tileSheetX=tileSheetIndex%tileSheetWidth;
|
|
int tileSheetY=tileSheetIndex/tileSheetWidth;
|
|
view.DrawPartialDecal(vi2d{x,y}*game->GetCurrentMapData().tilewidth,{float(MAP_TILESETS.at(tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tilesetName).tileheight)},MAP_TILESETS.at(tileSheet.tilesetName).tileset->Decal(),vi2d{tileSheetX,tileSheetY}*game->GetCurrentMapData().tilewidth,{float(MAP_TILESETS.at(tileSheet.tilesetName).tilewidth),float(MAP_TILESETS.at(tileSheet.tilesetName).tileheight)},{255,255,255,uint8_t(255-bridgeFadeFactor/TileGroup::FADE_TIME*TileGroup::FADE_AMT)});
|
|
#ifdef _DEBUG
|
|
if("debug_collision_boxes"_I){
|
|
if(MAP_TILESETS.at(tileSheet.tilesetName).collision.find(tileSheetIndex)!=MAP_TILESETS.at(tileSheet.tilesetName).collision.end()){
|
|
const geom2d::rect<float>collision=const_cast<TilesetData&>(MAP_TILESETS.at(tileSheet.tilesetName)).collision.at(tileSheetIndex).collision;
|
|
view.FillRectDecal(vf2d{float(x),float(y)}*float(game->GetCurrentMapData().tilewidth)+collision.pos,collision.size,{0,0,0,128});
|
|
view.DrawRectDecal(vf2d{float(x),float(y)}*float(game->GetCurrentMapData().tilewidth)+collision.pos,collision.size,GREY);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#pragma endregion
|
|
|
|
for(ZoneData&zone:upperEndZones){
|
|
RenderZone(zone.zone);
|
|
}
|
|
|
|
if(player->OnUpperLevel()){
|
|
RenderPrecastTargetingIndicator();
|
|
}
|
|
|
|
#pragma region Upper Foreground Rendering Preparation
|
|
for(TileGroup&group:upperForegroundTileGroups){
|
|
if(view.IsRectVisible(group.GetRange().pos,group.GetRange().size)){
|
|
if(geom2d::overlaps(group.GetFadeRange(),player->pos)){
|
|
if(GameState::STATE!=GameState::states[States::MAIN_MENU]){ //Don't fade out tile groups while we are on the main menu.
|
|
group.fadeFactor=std::min(group.fadeFactor+fElapsedTime,TileGroup::FADE_TIME);
|
|
}
|
|
}else{
|
|
group.fadeFactor=std::max(group.fadeFactor-fElapsedTime,0.f);
|
|
}
|
|
for(TileRenderData&tile:group.GetTiles()){
|
|
tile.tileOpacity=group.fadeFactor;
|
|
tile.group=&group;
|
|
|
|
#pragma region Unhiding tile detection
|
|
auto it=MAP_TILESETS.at(tile.tileSheet.tilesetName).upperForegroundTiles.find(tile.tileID); //We're looking for tiles that are marked as should not be faded away by a tile group.
|
|
if(it!=MAP_TILESETS.at(tile.tileSheet.tilesetName).upperForegroundTiles.end()){
|
|
if(!(*it).second.hide)tile.tileOpacity=0.f;
|
|
}
|
|
#pragma endregion
|
|
|
|
if(MAP_TILESETS.at(tile.tileSheet.tilesetName).collision.find(tile.tileID)!=MAP_TILESETS.at(tile.tileSheet.tilesetName).collision.end()){
|
|
tilesWithCollision.push_back(&tile);
|
|
}else{
|
|
tilesWithoutCollision.push_back(&tile);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#pragma endregion
|
|
|
|
std::sort(tilesWithCollision.begin(),tilesWithCollision.end(),[](TileRenderData*tile1,TileRenderData*tile2){
|
|
if(tile1->group->GetCollisionRange().middle().y!=tile2->group->GetCollisionRange().middle().y){
|
|
return tile1->group->GetCollisionRange().middle().y<tile2->group->GetCollisionRange().middle().y;
|
|
}else
|
|
if(tile1->pos.y!=tile2->pos.y){
|
|
return tile1->pos.y<tile2->pos.y;
|
|
}else{
|
|
return tile1->layerID<tile2->layerID;
|
|
}
|
|
});
|
|
std::sort(tilesWithoutCollision.begin(),tilesWithoutCollision.end(),[](TileRenderData*tile1,TileRenderData*tile2){return tile1->layerID<tile2->layerID;});
|
|
|
|
for(auto it=monstersBeforeUpper.begin();it!=monstersBeforeUpper.end();++it){
|
|
Monster&m=**it;
|
|
m.strategyDraw(this,m,MONSTER_DATA[m.GetName()].GetAIStrategy());
|
|
}
|
|
for(auto it=monstersAfterUpper.begin();it!=monstersAfterUpper.end();++it){
|
|
Monster&m=**it;
|
|
m.strategyDraw(this,m,MONSTER_DATA[m.GetName()].GetAIStrategy());
|
|
}
|
|
|
|
#pragma region Render Background Effects
|
|
{
|
|
auto it=backgroundEffectsUpper.begin();
|
|
while(it!=backgroundEffectsUpper.end()){
|
|
const Effect&e=**it;
|
|
e.Draw();
|
|
++it;
|
|
}
|
|
}
|
|
#pragma endregion
|
|
|
|
auto monstersBeforeUpperIt=monstersBeforeUpper.begin();
|
|
auto monstersAfterUpperIt=monstersAfterUpper.begin();
|
|
auto dropsBeforeUpperIt=dropsBeforeUpper.begin();
|
|
auto dropsAfterUpperIt=dropsAfterUpper.begin();
|
|
#pragma region Rendering before Tile Depth Rendering
|
|
if(tilesWithCollision.size()>0){
|
|
const float topTileY=(*tilesWithCollision.begin())->group->GetCollisionRange().middle().y;
|
|
#pragma region Depth Ordered Rendering
|
|
while(monstersBeforeUpperIt!=monstersBeforeUpper.end()){
|
|
Monster&m=**monstersBeforeUpperIt;
|
|
if(m.pos.y<topTileY){
|
|
m.Draw();
|
|
++monstersBeforeUpperIt;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
while(dropsBeforeUpperIt!=dropsBeforeUpper.end()){
|
|
const ItemDrop&drop=ItemDrop::drops[*dropsBeforeUpperIt];
|
|
if(drop.pos.y<topTileY){
|
|
drop.Draw();
|
|
++dropsBeforeUpperIt;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if(!player->rendered&&!player->upperLevel&&player->GetPos().y<topTileY){
|
|
//If we are supposed to render the player, everything else before must be rendered at this point.
|
|
while(monstersBeforeUpperIt!=monstersBeforeUpper.end()){
|
|
Monster&m=**monstersBeforeUpperIt;
|
|
m.Draw();
|
|
++monstersBeforeUpperIt;
|
|
}
|
|
while(dropsBeforeUpperIt!=dropsBeforeUpper.end()){
|
|
const ItemDrop&drop=ItemDrop::drops[*dropsBeforeUpperIt];
|
|
drop.Draw();
|
|
++dropsBeforeUpperIt;
|
|
}
|
|
player->rendered=true;
|
|
if(player->GetZ()>0){
|
|
vf2d shadowScale=vf2d{8*player->GetSizeMult()/3.f,1}/std::max(1.f,player->GetZ()/24);
|
|
view.DrawDecal(player->GetPos()-vf2d{3,3}*shadowScale/2+vf2d{0,6*player->GetSizeMult()},GFX["circle.png"].Decal(),shadowScale,BLACK);
|
|
}
|
|
RenderPlayer(player->GetPos(),{1,1});
|
|
}
|
|
while(monstersAfterUpperIt!=monstersAfterUpper.end()){
|
|
Monster&m=**monstersAfterUpperIt;
|
|
if(m.pos.y<topTileY){
|
|
m.Draw();
|
|
++monstersAfterUpperIt;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
while(dropsAfterUpperIt!=dropsAfterUpper.end()){
|
|
const ItemDrop&drop=ItemDrop::drops[*dropsAfterUpperIt];
|
|
if(drop.pos.y<topTileY){
|
|
drop.Draw();
|
|
++dropsAfterUpperIt;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
#pragma endregion
|
|
}
|
|
#pragma endregion
|
|
#pragma region Upper Foreground Rendering w/Depth
|
|
for(TileRenderData*tile:tilesWithCollision){
|
|
#pragma region Depth Ordered Upper Rendering
|
|
while(monstersBeforeUpperIt!=monstersBeforeUpper.end()){
|
|
Monster&m=**monstersBeforeUpperIt;
|
|
if(m.pos.y<tile->group->GetCollisionRange().middle().y){
|
|
m.Draw();
|
|
++monstersBeforeUpperIt;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
while(dropsBeforeUpperIt!=dropsBeforeUpper.end()){
|
|
const ItemDrop&drop=ItemDrop::drops[*dropsBeforeUpperIt];
|
|
if(drop.pos.y<tile->group->GetCollisionRange().middle().y){
|
|
drop.Draw();
|
|
++dropsBeforeUpperIt;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if(!player->rendered&&player->upperLevel&&player->GetPos().y<tile->group->GetCollisionRange().middle().y){
|
|
//If we are supposed to render the player, everything else before must be rendered at this point.
|
|
while(monstersBeforeUpperIt!=monstersBeforeUpper.end()){
|
|
Monster&m=**monstersBeforeUpperIt;
|
|
m.Draw();
|
|
++monstersBeforeUpperIt;
|
|
}
|
|
while(dropsBeforeUpperIt!=dropsBeforeUpper.end()){
|
|
const ItemDrop&drop=ItemDrop::drops[*dropsBeforeUpperIt];
|
|
drop.Draw();
|
|
++dropsBeforeUpperIt;
|
|
}
|
|
player->rendered=true;
|
|
if(player->GetZ()>0){
|
|
vf2d shadowScale=vf2d{8*player->GetSizeMult()/3.f,1}/std::max(1.f,player->GetZ()/24);
|
|
view.DrawDecal(player->GetPos()-vf2d{3,3}*shadowScale/2+vf2d{0,6*player->GetSizeMult()},GFX["circle.png"].Decal(),shadowScale,BLACK);
|
|
}
|
|
RenderPlayer(player->GetPos(),{1,1});
|
|
}
|
|
while(monstersAfterUpperIt!=monstersAfterUpper.end()){
|
|
Monster&m=**monstersAfterUpperIt;
|
|
if(m.pos.y<tile->group->GetCollisionRange().middle().y){
|
|
m.Draw();
|
|
++monstersAfterUpperIt;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
while(dropsAfterUpperIt!=dropsAfterUpper.end()){
|
|
const ItemDrop&drop=ItemDrop::drops[*dropsAfterUpperIt];
|
|
if(drop.pos.y<tile->group->GetCollisionRange().middle().y){
|
|
drop.Draw();
|
|
++dropsAfterUpperIt;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
#pragma endregion
|
|
RenderTile(*tile,{255,255,255,uint8_t(255-tile->tileOpacity/TileGroup::FADE_TIME*TileGroup::FADE_AMT)});
|
|
float distToPlayer=geom2d::line<float>(player->GetPos(),tile->pos+vf2d{12,12}).length();
|
|
if(GameSettings::TerrainCollisionBoxesEnabled()&&distToPlayer<24*3&&tile->tileOpacity>0&&MAP_TILESETS.at(tile->tileSheet.tilesetName).collision.find(tile->tileID)!=MAP_TILESETS.at(tile->tileSheet.tilesetName).collision.end()){
|
|
const geom2d::rect<float>collision=MAP_TILESETS.at(tile->tileSheet.tilesetName).collision.at(tile->tileID).collision;
|
|
distToPlayer/=4;
|
|
if(distToPlayer<1){distToPlayer=1;}
|
|
view.FillRectDecal(tile->pos+collision.pos,collision.size,{255,0,0,uint8_t(128*tile->tileOpacity/sqrt(distToPlayer))});
|
|
view.DrawRectDecal(tile->pos+collision.pos,collision.size,{128,0,0,uint8_t(255/sqrt(distToPlayer))});
|
|
}
|
|
}
|
|
#pragma endregion
|
|
#pragma region Remaining Upper Rendering
|
|
while(monstersBeforeUpperIt!=monstersBeforeUpper.end()){
|
|
Monster*const m=*monstersBeforeUpperIt;
|
|
m->Draw();
|
|
++monstersBeforeUpperIt;
|
|
}
|
|
for(const Effect*const e:backgroundEffectsUpper){
|
|
e->Draw();
|
|
}
|
|
while(dropsBeforeUpperIt!=dropsBeforeUpper.end()){
|
|
const int dropInd=*dropsBeforeUpperIt;
|
|
ItemDrop::drops[dropInd].Draw();
|
|
++dropsBeforeUpperIt;
|
|
}
|
|
if(!player->rendered&&player->upperLevel){
|
|
player->rendered=true;
|
|
if(player->GetZ()>0){
|
|
vf2d shadowScale=vf2d{8*player->GetSizeMult()/3.f,1}/std::max(1.f,player->GetZ()/24);
|
|
view.DrawDecal(player->GetPos()-vf2d{3,3}*shadowScale/2+vf2d{0,6*player->GetSizeMult()},GFX["circle.png"].Decal(),shadowScale,BLACK);
|
|
}
|
|
RenderPlayer(player->GetPos(),{1,1});
|
|
}
|
|
while(monstersAfterUpperIt!=monstersAfterUpper.end()){
|
|
Monster*const m=*monstersAfterUpperIt;
|
|
m->Draw();
|
|
++monstersAfterUpperIt;
|
|
}
|
|
while(dropsAfterUpperIt!=dropsAfterUpper.end()){
|
|
const int dropInd=*dropsAfterUpperIt;
|
|
ItemDrop::drops[dropInd].Draw();
|
|
++dropsAfterUpperIt;
|
|
}
|
|
for(const IBullet*const b:bulletsUpper){
|
|
b->_Draw();
|
|
}
|
|
for(const Effect*const e:foregroundEffectsUpper){
|
|
e->Draw();
|
|
}
|
|
#pragma endregion
|
|
#pragma region Permanent Upper Foreground Rendering
|
|
for(TileRenderData*tile:tilesWithoutCollision){
|
|
RenderTile(*tile,{255,255,255,uint8_t(255-tile->tileOpacity/TileGroup::FADE_TIME*TileGroup::FADE_AMT)});
|
|
float distToPlayer=geom2d::line<float>(player->GetPos(),tile->pos+vf2d{12,12}).length();
|
|
if(distToPlayer<24*3&&tile->tileOpacity>0&&MAP_TILESETS.at(tile->tileSheet.tilesetName).collision.find(tile->tileID)!=MAP_TILESETS.at(tile->tileSheet.tilesetName).collision.end()){
|
|
const geom2d::rect<float>collision=MAP_TILESETS.at(tile->tileSheet.tilesetName).collision.at(tile->tileID).collision;
|
|
distToPlayer/=4;
|
|
if(distToPlayer<1){distToPlayer=1;}
|
|
view.FillRectDecal(tile->pos+collision.pos,collision.size,{255,0,0,uint8_t(128*tile->tileOpacity/sqrt(distToPlayer))});
|
|
view.DrawRectDecal(tile->pos+collision.pos,collision.size,{128,0,0,uint8_t(255/sqrt(distToPlayer))});
|
|
}
|
|
}
|
|
#pragma endregion
|
|
|
|
for(std::shared_ptr<Monster>&m:MONSTER_LIST){
|
|
m->strategyDrawOverlay(this,*m,MONSTER_DATA[m->GetName()].GetAIStrategy());
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
if(DEBUG_PATHFINDING){
|
|
std::vector<vf2d>pathing=game->pathfinder.Solve_AStar(player.get()->GetPos(),GetWorldMousePos(),8,player.get()->OnUpperLevel());
|
|
for(vf2d&square:pathing){
|
|
view.FillRectDecal(square*float(game->GetCurrentMapData().tilewidth),{float(game->GetCurrentMapData().tilewidth),float(game->GetCurrentMapData().tilewidth)},DARK_GREEN);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
Player*const AiL::GetPlayer()const{
|
|
return player.get();
|
|
}
|
|
|
|
void AiL::RenderHud(){
|
|
if(!displayHud)return;
|
|
healthCounter.Update();
|
|
manaCounter.Update();
|
|
|
|
auto RenderAimingCursor=[&](){
|
|
if(Input::UsingGamepad()&&Input::AxesActive()){
|
|
vf2d aimingLocation=player->GetAimingLocation();
|
|
float analogStickDistance=geom2d::line<float>(GetScreenSize()/2,aimingLocation).length();
|
|
if(analogStickDistance>12.f){
|
|
float aimingDist=geom2d::line<float>(view.WorldToScreen(player->GetPos()),aimingLocation).length();
|
|
if(aimingDist>"Player.Aiming Cursor Max Distance"_F/100*24.f){
|
|
//Clamp the line to the max possible distance.
|
|
aimingLocation=geom2d::line<float>(view.WorldToScreen(player->GetPos()),aimingLocation).rpoint("Player.Aiming Cursor Max Distance"_F/100*24.f);
|
|
aimingDist=geom2d::line<float>(view.WorldToScreen(player->GetPos()),aimingLocation).length();
|
|
}
|
|
|
|
geom2d::line<float>aimingLine=geom2d::line<float>(view.WorldToScreen(player->GetPos()),aimingLocation);
|
|
|
|
DrawRotatedDecal(aimingLocation,GFX["aiming_target.png"].Decal(),0.f,GFX["aiming_target.png"].Sprite()->Size()/2,{1.0f,1.0f},{255,255,255,128});
|
|
|
|
vf2d lineScale=vf2d(GFX["aiming_line.png"].Sprite()->Size())*vf2d{aimingDist/GFX["aiming_line.png"].Sprite()->width,1};
|
|
|
|
DrawPartialRotatedDecal(view.WorldToScreen(player->GetPos()),GFX["aiming_line.png"].Decal(),aimingLine.vector().polar().y,{0,0},{0,0},lineScale,vf2d{aimingDist,1.f}/lineScale,{255,255,255,192});
|
|
}
|
|
}
|
|
};
|
|
|
|
#pragma region Boss Indicators
|
|
if(bossIndicatorPos.has_value()){
|
|
const vf2d&bossIndicator=bossIndicatorPos.value();
|
|
const bool BossIsOutsideView=!geom2d::overlaps(geom2d::rect<float>{view.GetWorldTL(),view.GetWorldVisibleArea()},bossIndicator);
|
|
if(BossIsOutsideView){
|
|
|
|
const bool flicker=sinf(GetRunTime())>0.5f&&sinf(GetRunTime())<0.55f;
|
|
|
|
#pragma region Side Indicators
|
|
const float yPos{std::clamp(view.WorldToScreen(bossIndicator).y,0.f,view.GetWorldVisibleArea().y)};
|
|
|
|
if(bossIndicator.x<view.GetWorldTL().x){ //Left-side indicator
|
|
DrawRotatedDecal({0,yPos},GFX["bossIndicator.png"].Decal(),0.f,{0,16},{8,1},flicker?RED:DARK_RED);
|
|
}else
|
|
if(bossIndicator.x>view.GetWorldBR().x){
|
|
DrawRotatedDecal({float(ScreenWidth()-8),yPos},GFX["bossIndicator.png"].Decal(),0.f,{0,16},{8,1},flicker?RED:DARK_RED);
|
|
}
|
|
#pragma endregion
|
|
#pragma region Top+Bottom Indicators
|
|
const float xPos{std::clamp(view.WorldToScreen(bossIndicator).x,0.f,view.GetWorldVisibleArea().x)};
|
|
|
|
if(bossIndicator.y<view.GetWorldTL().y){ //Left-side indicator
|
|
DrawRotatedDecal({xPos,4},GFX["bossIndicator.png"].Decal(),PI/2,{0.5f,16.f},{8,1},flicker?RED:DARK_RED);
|
|
}else
|
|
if(bossIndicator.y>view.GetWorldBR().y){
|
|
DrawRotatedDecal({xPos,float(ScreenHeight()-4)},GFX["bossIndicator.png"].Decal(),PI/2,{0.5f,16.f},{8,1},flicker?RED:DARK_RED);
|
|
}
|
|
#pragma endregion
|
|
}
|
|
}
|
|
#pragma endregion
|
|
|
|
minimap.Update();
|
|
minimap.Draw();
|
|
|
|
RenderAimingCursor();
|
|
|
|
ItemOverlay::Draw();
|
|
|
|
RenderCooldowns();
|
|
|
|
auto RenderCastbar=[&](const CastInfo&cast){
|
|
FillRectDecal(vf2d{ScreenWidth()/2-92.f,ScreenHeight()-90.f},{184,20},BLACK);
|
|
FillRectDecal(vf2d{ScreenWidth()/2-90.f,ScreenHeight()-88.f},{180,16},DARK_GREY);
|
|
float timer=cast.castTimer;
|
|
float totalTime=cast.castTotalTime;
|
|
std::string castText=cast.name;
|
|
GradientFillRectDecal(vf2d{ScreenWidth()/2-90.f,ScreenHeight()-88.f},{(timer/totalTime)*180,16},{247,125,37},{247,125,37},{247,184,37},{247,184,37});
|
|
std::stringstream castTimeDisplay;
|
|
castTimeDisplay<<std::fixed<<std::setprecision(1)<<timer;
|
|
DrawShadowStringPropDecal(vf2d{ScreenWidth()/2+90.f,ScreenHeight()-80.f}-vf2d{float(GetTextSizeProp(castTimeDisplay.str()).x),0},castTimeDisplay.str(),WHITE,BLACK);
|
|
DrawShadowStringPropDecal(vf2d{ScreenWidth()/2.f-GetTextSizeProp(castText).x*2/2,ScreenHeight()-64.f},castText,WHITE,BLACK,{2,3},std::numeric_limits<float>::max(),2.f);
|
|
};
|
|
|
|
if(GetPlayer()->GetCastInfo().castTimer>0){
|
|
RenderCastbar(GetPlayer()->GetCastInfo());
|
|
}else
|
|
if(GetPlayer()->GetEndZoneStandTime()>0){
|
|
RenderCastbar(CastInfo{"Exiting Level...",GetPlayer()->GetEndZoneStandTime(),"Player.End Zone Wait Time"_F});
|
|
}
|
|
|
|
Pixel healthOutlineCol=BLACK;
|
|
if(player->GetHealthRatio()<="Player.Health Warning Pct"_F/100.f){
|
|
float runTimeAmt=fmod(GetRunTime(),"Player.Health Warning Flicker Time"_F*2);
|
|
if(runTimeAmt<"Player.Health Warning Flicker Time"_F){
|
|
healthOutlineCol="Player.Health Warning Outline Color"_Pixel;
|
|
}
|
|
|
|
Input::SetLightbar(healthOutlineCol);
|
|
}
|
|
|
|
DrawDecal({2,2},GFX["heart_outline.png"].Decal(),{1.f,1.f},healthOutlineCol);
|
|
DrawDecal({2,2},GFX["heart.png"].Decal());
|
|
DrawDecal({2,20},GFX["mana.png"].Decal());
|
|
std::string text=player->GetHealth()>0?std::to_string(healthCounter.GetDisplayValue()):"X";
|
|
std::string text_mana=std::to_string(manaCounter.GetDisplayValue());
|
|
|
|
DrawShadowStringPropDecal({20,3},text,healthCounter.GetDisplayColor(),healthOutlineCol,{2,2},INFINITE);
|
|
DrawShadowStringPropDecal({24,23},text_mana,manaCounter.GetDisplayColor(),BLACK,{1.5f,1.5f},INFINITE);
|
|
|
|
#pragma region Show Max Health/Max Mana
|
|
if(GameSettings::ShowMaxHealth()){
|
|
vf2d healthTextSize=GetTextSizeProp(text)*vf2d{2.f,2.f};
|
|
|
|
std::string maxHealthText="/"+std::to_string(int(player->GetMaxHealth()));
|
|
float maxHealthTextHeight=GetTextSizeProp(maxHealthText).y;
|
|
DrawShadowStringPropDecal(vf2d{20,3}+healthTextSize+vf2d{1.f,-maxHealthTextHeight},maxHealthText,{200,200,200,255},healthOutlineCol,{1.f,1.f},INFINITE);
|
|
}
|
|
if(GameSettings::ShowMaxMana()){
|
|
vf2d manaTextSize=GetTextSizeProp(text_mana)*vf2d{1.5f,1.5f};
|
|
|
|
std::string maxManaText="/"+std::to_string(player->GetMaxMana());
|
|
float maxManaTextHeight=GetTextSizeProp(maxManaText).y;
|
|
DrawShadowStringPropDecal(vf2d{24,23}+manaTextSize+vf2d{1.f,-maxManaTextHeight},maxManaText,{200,200,255,255},BLACK,{1.f,1.f},INFINITE);
|
|
}
|
|
#pragma endregion
|
|
|
|
if(player->notEnoughManaDisplay.second>0){
|
|
std::string displayText="Not enough mana for "+player->notEnoughManaDisplay.first+"!";
|
|
DrawShadowStringPropDecal(vf2d{float(ScreenWidth()/2),float(ScreenHeight()/4)}-GetTextSizeProp(displayText)/2,displayText,DARK_RED,VERY_DARK_RED);
|
|
}
|
|
if(player->notificationDisplay.second>0){
|
|
std::string displayText=player->notificationDisplay.first;
|
|
DrawShadowStringPropDecal(vf2d{float(ScreenWidth()/2),float(ScreenHeight()/4)-24}-GetTextSizeProp(displayText)/2,displayText,BLUE,VERY_DARK_BLUE);
|
|
}
|
|
|
|
DisplayBossEncounterInfo();
|
|
|
|
for(std::vector<std::shared_ptr<DamageNumber>>::iterator it=DAMAGENUMBER_LIST.begin();it!=DAMAGENUMBER_LIST.end();++it){
|
|
DamageNumber*dn=(*it).get();
|
|
dn->Draw();
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
if("debug_player_info"_I){
|
|
DrawShadowStringDecal({0,128},player->GetPos().str());
|
|
DrawShadowStringDecal({0,136},"Spd: "+std::to_string(player->GetMoveSpdMult()));
|
|
DrawShadowStringDecal({0,144},"HP Timer: "+std::to_string(player->hpRecoveryTimer));
|
|
DrawShadowStringDecal({0,152},"HP Recovery Amt: "+std::to_string(player->GetHPRecoveryPct()*player->GetMaxHealth()));
|
|
if(!ISBLANK(GetLoadoutItem(0))){
|
|
DrawShadowStringDecal({0,92},"Loadout Slot 1 Qty: "+std::to_string(GetLoadoutItem(0).lock()->Amt()));
|
|
}
|
|
DrawShadowStringDecal({0,12},"Button Hold Time: "+std::to_string(Menu::menus[INVENTORY_CONSUMABLES]->buttonHoldTime));
|
|
}
|
|
#endif
|
|
|
|
hudOverlay.Draw();
|
|
|
|
Pixel vignetteOverlayColor="Interface.Vignette Color"_Pixel;
|
|
const float vignetteTotalDisplayTime="Interface.Vignette Appearance Time"_F+"Interface.Vignette Fadeout Time"_F;
|
|
if(vignetteDisplayTime<"Interface.Vignette Fadeout Time"_F)vignetteOverlayColor.a=util::lerp(0,255,vignetteDisplayTime/"Interface.Vignette Fadeout Time"_F);
|
|
DrawDecal({0,0},GFX["vignette.png"].Decal(),{1.f,1.f},vignetteOverlayColor);
|
|
}
|
|
|
|
void AiL::RenderCooldowns(){
|
|
std::vector<Ability>cooldowns{
|
|
player->GetAbility1(),
|
|
player->GetAbility2(),
|
|
player->GetAbility3(),
|
|
player->GetAbility4(),
|
|
};
|
|
|
|
const auto DrawCooldown=[&](vf2d pos,Ability&a,int loadoutSlot=-1/*Set to 0-2 to get an item slot rendered instead*/){
|
|
bool circle=loadoutSlot==-1;
|
|
if(a.name!="???"){
|
|
|
|
vf2d keyDisplaySize=vf2d{GetTextSize(a.input->GetDisplayName())}*vf2d{0.5f,0.5f};
|
|
InputType controlType=KEY;
|
|
if(Input::UsingGamepad())controlType=CONTROLLER;
|
|
vf2d drawOffset={};
|
|
if(loadoutSlot!=-1){
|
|
drawOffset.y+=2.f;
|
|
}
|
|
a.input->DrawPrimaryInput(game,pos+vf2d{14,0.f}+drawOffset,"",255,controlType,{0.75f,0.75f});
|
|
|
|
if(a.cooldown>0.1){
|
|
vf2d iconScale={1,1};
|
|
if(loadoutSlot!=-1)iconScale={0.7f,0.7f};
|
|
DrawRotatedDecal(pos+vf2d{12,12},GFX[a.icon].Decal(),0,{12,12},iconScale,{255,255,255,64});
|
|
if(circle){
|
|
DrawPie(pos+vf2d{12,12},12,360-(a.cooldown/a.GetCooldownTime())*360,PixelLerp(a.barColor1,a.barColor2,(a.cooldown/a.GetCooldownTime())));
|
|
}else{
|
|
DrawSquarePie(pos+vf2d{12,12},10,360-(a.cooldown/a.GetCooldownTime())*360,PixelLerp(a.barColor1,a.barColor2,(a.cooldown/a.GetCooldownTime())));
|
|
}
|
|
std::stringstream cooldownTimeDisplay;
|
|
cooldownTimeDisplay<<std::fixed<<std::setprecision(1)<<a.cooldown;
|
|
DrawShadowStringPropDecal(pos+vf2d{12,12}-vf2d{float(GetTextSizeProp(cooldownTimeDisplay.str()).x*0.5),float(GetTextSizeProp(cooldownTimeDisplay.str()).y*1)}/2,cooldownTimeDisplay.str(),WHITE,BLACK,{0.5,1});
|
|
}else{
|
|
vf2d iconScale={1,1};
|
|
if(loadoutSlot!=-1)iconScale={0.7f,0.7f};
|
|
DrawRotatedDecal(pos+vf2d{12,12},GFX[a.icon].Decal(),0,{12,12},iconScale,WHITE);
|
|
}
|
|
|
|
Decal*overlayIcon=GFX["skill_overlay_icon.png"].Decal();
|
|
if(!circle)overlayIcon=GFX["square_skill_overlay_icon.png"].Decal();
|
|
DrawDecal(pos,overlayIcon);
|
|
if(a.input->Held()){
|
|
if(circle){
|
|
DrawPie(pos+vf2d{12,12},12,0,{255,255,255,64});
|
|
}else{
|
|
DrawSquarePie(pos+vf2d{12,12},10,0,{255,255,255,64});
|
|
}
|
|
}
|
|
|
|
Pixel shortNameCol=BLUE,manaCostShadowCol=DARK_BLUE,keyDisplayCol=YELLOW;
|
|
if(player->GetMana()<a.manaCost){
|
|
Decal*overlayOverlayIcon=GFX["skill_overlay_icon_overlay.png"].Decal();
|
|
if(!circle)overlayOverlayIcon=GFX["square_skill_overlay_icon_empty.png"].Decal();
|
|
DrawDecal(pos,overlayOverlayIcon,{1,1},DARK_RED);
|
|
shortNameCol=RED;
|
|
manaCostShadowCol=DARK_RED;
|
|
keyDisplayCol=RED;
|
|
}else
|
|
if(a.cooldown>0){
|
|
shortNameCol=GREY;
|
|
manaCostShadowCol=DARK_GREY;
|
|
keyDisplayCol=GREY;
|
|
}
|
|
|
|
if(loadoutSlot!=-1){
|
|
const std::weak_ptr<Item>loadoutItem=GetLoadoutItem(loadoutSlot);
|
|
if(!loadoutItem.expired()&&loadoutItem.lock()->it!=nullptr){
|
|
uint32_t itemAmt=loadoutItem.lock()->Amt();
|
|
if(itemAmt>0){
|
|
std::string amtString="x"+std::to_string(itemAmt);
|
|
vf2d qtySize=vf2d{GetTextSize(amtString)}*vf2d{0.5f,0.75f};
|
|
DrawShadowStringDecal(pos+vf2d{20,20}-qtySize/2,amtString,WHITE,BLACK,{0.5f,0.75f});
|
|
}else{
|
|
DrawDecal(pos,GFX["square_skill_overlay_icon_empty.png"].Decal(),{1,1},DARK_RED);
|
|
shortNameCol=RED;
|
|
manaCostShadowCol=DARK_RED;
|
|
keyDisplayCol=RED;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(a.manaCost>0){
|
|
vf2d manaCostSize=vf2d{GetTextSize(std::to_string(a.manaCost))}*vf2d{0.5f,0.75f};
|
|
DrawShadowStringDecal(pos+vf2d{20,4}-manaCostSize/2,std::to_string(a.manaCost),{192,192,255},manaCostShadowCol,{0.5f,0.75f});
|
|
}
|
|
|
|
vf2d shortNameSize=vf2d{GetTextSize(a.shortName)}*vf2d{0.5f,0.75f};
|
|
DrawShadowStringDecal(pos+vf2d{13,24}-shortNameSize/2,a.shortName,shortNameCol,{255,255,255,230},{0.5f,0.75f});
|
|
}
|
|
};
|
|
|
|
vf2d iconPos={8.f,float(ScreenHeight()-32)};
|
|
for(Ability&a:cooldowns){
|
|
if(a.name!="???"){
|
|
DrawCooldown(iconPos,a);
|
|
iconPos+=vf2d{32,0};
|
|
}
|
|
}
|
|
|
|
DrawCooldown({float(ScreenWidth()-32),float(ScreenHeight()-32)},player->GetRightClickAbility());
|
|
|
|
for(int i=-1;i<2;i++){
|
|
int loadoutSlot=i+1;//0-2
|
|
|
|
Ability*a=&GetPlayer()->useItem1;
|
|
if(loadoutSlot==1){a=&GetPlayer()->useItem2;}else
|
|
if(loadoutSlot==2){a=&GetPlayer()->useItem3;}
|
|
DrawCooldown({float(ScreenWidth()/2+i*26-12),float(ScreenHeight()-26)},*a,loadoutSlot);
|
|
}
|
|
}
|
|
|
|
void AiL::AddEffect(std::unique_ptr<Effect>foreground,std::unique_ptr<Effect> background){
|
|
AddEffect(std::move(background),true);
|
|
AddEffect(std::move(foreground));
|
|
}
|
|
|
|
void AiL::AddEffect(std::unique_ptr<Effect> foreground,bool back){
|
|
if(back){
|
|
backgroundEffectsToBeInserted.push_back(std::move(foreground));
|
|
} else {
|
|
foregroundEffectsToBeInserted.push_back(std::move(foreground));
|
|
}
|
|
}
|
|
|
|
vf2d AiL::GetWorldMousePos(){
|
|
return GetMousePos()+view.GetWorldOffset();
|
|
}
|
|
|
|
void AiL::SetupWorldShake(float duration){
|
|
if(!GameSettings::ScreenShakeEnabled())return;
|
|
worldShakeVel={13,-13};
|
|
worldShakeTime=duration;
|
|
worldShake=vf2d{player->GetPos()};
|
|
camera.SetTarget(worldShake);
|
|
Input::StartVibration();
|
|
}
|
|
|
|
|
|
void AiL::InitializeLevel(std::string mapFile,MapName map){
|
|
TMXParser level(mapFile);
|
|
|
|
size_t slashMarker = mapFile.find_last_of('/');
|
|
std::string baseDir=mapFile.substr(0,slashMarker+1);
|
|
MAP_DATA[map]=level.GetData();
|
|
|
|
for(auto&[key,size]:DATA["Levels"][map]){
|
|
if(key.starts_with("Loot")){
|
|
datafile data=DATA["Levels"][map][key];
|
|
int pctChance=100;
|
|
if(data.GetValueCount()>3)pctChance=data.GetInt(3U);
|
|
MAP_DATA[map].stageLoot.push_back(ItemMapData(data.GetString(0U),data.GetInt(1U),data.GetInt(2U),pctChance));
|
|
}else
|
|
if(key.starts_with("Skip Loadout Selection")){
|
|
MAP_DATA[map].skipLoadoutScreen=DATA["Levels"][map]["Skip Loadout Selection"].GetBool();
|
|
}
|
|
}
|
|
|
|
for(XMLTag&tag:MAP_DATA[map].TilesetData){
|
|
size_t slashMarkerSourceDir = tag.data["source"].find_last_of('/');
|
|
std::string baseSourceDir=tag.data["source"].substr(slashMarkerSourceDir+1);
|
|
if(MAP_TILESETS.find("assets/maps/"+baseSourceDir)==MAP_TILESETS.end()){
|
|
TSXParser tileset(baseDir+tag.data["source"]);
|
|
Renderable*r=NEW Renderable();
|
|
|
|
if(tileset.GetData().tilewidth==0||tileset.GetData().tileheight==0)ERR(std::format("WARNING! Failed to load map {}! Found a tileset {} with a width of {} and height of {}. Zero values are not allowed!",map,"assets/maps/"+baseSourceDir,tileset.GetData().tilewidth,tileset.GetData().tileheight));
|
|
MAP_TILESETS["assets/maps/"+baseSourceDir].tilewidth=tileset.GetData().tilewidth;
|
|
MAP_TILESETS["assets/maps/"+baseSourceDir].tileheight=tileset.GetData().tileheight;
|
|
MAP_TILESETS["assets/maps/"+baseSourceDir].tileset=r;
|
|
MAP_TILESETS["assets/maps/"+baseSourceDir].foregroundTiles=tileset.GetData().ForegroundTileData;
|
|
MAP_TILESETS["assets/maps/"+baseSourceDir].upperForegroundTiles=tileset.GetData().UpperForegroundTileData;
|
|
MAP_TILESETS["assets/maps/"+baseSourceDir].collision=tileset.GetData().CollisionData;
|
|
MAP_TILESETS["assets/maps/"+baseSourceDir].staircaseTiles=tileset.GetData().StaircaseData;
|
|
MAP_TILESETS["assets/maps/"+baseSourceDir].animationData=tileset.GetData().AnimationData;
|
|
MAP_TILESETS["assets/maps/"+baseSourceDir].reflectiveData=tileset.GetData().ReflectiveData;
|
|
MAP_TILESETS["assets/maps/"+baseSourceDir].tileRepeatData=tileset.GetData().TileRepeatData;
|
|
MAP_TILESETS["assets/maps/"+baseSourceDir].isTerrain=tileset.GetData().isTerrain;
|
|
MAP_TILESETS["assets/maps/"+baseSourceDir].collision.SetInitialized();
|
|
LOG("assets/maps/"+baseSourceDir<<" Animation Data Size: "<<MAP_TILESETS["assets/maps/"+baseSourceDir].animationData.size());
|
|
std::string mapPath="assets/maps/"+tileset.GetData().ImageData.data["source"];
|
|
if(gamepack.Loaded()){
|
|
r->Load(mapPath,&gamepack);
|
|
}else
|
|
if(std::filesystem::exists(mapPath)){
|
|
r->Load(mapPath);
|
|
if("GENERATE_GAMEPACK"_B){
|
|
gamepack.AddFile(mapPath);
|
|
}
|
|
}else{
|
|
LOG("WARNING! "<<mapPath<<" does not exist, auto-generating mock-up texture");
|
|
r->Create(tileset.GetData().imagewidth,tileset.GetData().imageheight);
|
|
SetDrawTarget(r->Sprite());
|
|
int tileXCount=tileset.GetData().imagewidth/tileset.GetData().tilewidth;
|
|
int tileYCount=tileset.GetData().imageheight/tileset.GetData().tileheight;
|
|
vi2d tileSize={tileset.GetData().tilewidth,tileset.GetData().tileheight};
|
|
|
|
const int colorCombinations=255*6;
|
|
|
|
for(int y=0;y<tileYCount;y++){
|
|
for(int x=0;x<tileXCount;x++){
|
|
int colorCycleIndex=(y*255+x)%colorCombinations;
|
|
Pixel tileCol=WHITE;
|
|
if(colorCycleIndex<255){
|
|
tileCol={uint8_t(colorCycleIndex%255),0,0};
|
|
}else
|
|
if(colorCycleIndex<255*2){
|
|
tileCol={0,uint8_t(colorCycleIndex%255),0};
|
|
}else
|
|
if(colorCycleIndex<255*3){
|
|
tileCol={0,0,uint8_t(colorCycleIndex%255)};
|
|
}else
|
|
if(colorCycleIndex<255*4){
|
|
tileCol={uint8_t(colorCycleIndex%255),uint8_t(colorCycleIndex%255),0};
|
|
}else
|
|
if(colorCycleIndex<255*5){
|
|
tileCol={0,uint8_t(colorCycleIndex%255),uint8_t(colorCycleIndex%255)};
|
|
}else{
|
|
tileCol={uint8_t(colorCycleIndex%255),0,uint8_t(colorCycleIndex%255)};
|
|
}
|
|
FillRect(vi2d{x,y}*tileSize,tileSize,tileCol);
|
|
DrawRect(vi2d{x,y}*tileSize,tileSize,GREY);
|
|
DrawString(vi2d{x,y}*tileSize+vi2d{1,1},std::to_string(y*255+x),DARK_GREY);
|
|
}
|
|
}
|
|
SetDrawTarget(nullptr);
|
|
r->Decal()->Update();
|
|
}
|
|
|
|
ComputeModeColors(MAP_TILESETS["assets/maps/"+baseSourceDir]);
|
|
}
|
|
}
|
|
|
|
if(MAP_DATA[map].MapData.optimized||MAP_DATA[map].MapData.provideOptimization){
|
|
LOG("Generating optimized map for Map "<<map);
|
|
MAP_DATA[map].optimizedTile=NEW Renderable();
|
|
MAP_DATA[map].optimizedTile->Create(MAP_DATA[map].MapData.width*MAP_DATA[map].MapData.tilewidth,MAP_DATA[map].MapData.height*MAP_DATA[map].MapData.tileheight);
|
|
SetDrawTarget(MAP_DATA[map].optimizedTile->Sprite());
|
|
Pixel::Mode prevMode=GetPixelMode();
|
|
SetPixelMode(Pixel::Mode::MASK);
|
|
Clear(BLANK);
|
|
for(int y=0;y<MAP_DATA[map].MapData.height;y++){
|
|
for(int x=0;x<MAP_DATA[map].MapData.width;x++){
|
|
for(auto&layer:MAP_DATA[map].LayerData){
|
|
int tileID=layer.tiles[y][x]-1;
|
|
if(tileID!=-1){
|
|
TilesheetData tileSheet=GetTileSheet(map,tileID);
|
|
int tileSheetWidth=MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite()->width/MAP_TILESETS.at(tileSheet.tilesetName).tilewidth;
|
|
int tileSheetHeight=MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite()->height/MAP_TILESETS.at(tileSheet.tilesetName).tileheight;
|
|
int tileSheetIndex=tileID-(tileSheet.firstgid-1);
|
|
int tileSheetX=tileSheetIndex%tileSheetWidth;
|
|
int tileSheetY=tileSheetIndex/tileSheetWidth;
|
|
vi2d pos=vi2d{x,y}*MAP_TILESETS.at(tileSheet.tilesetName).tilewidth;
|
|
DrawPartialSprite(pos,MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite(),vi2d{tileSheetX,tileSheetY}*MAP_TILESETS.at(tileSheet.tilesetName).tilewidth,{MAP_TILESETS.at(tileSheet.tilesetName).tilewidth,MAP_TILESETS.at(tileSheet.tilesetName).tileheight});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
SetPixelMode(prevMode);
|
|
MAP_DATA[map].optimizedTile->Decal()->Update();
|
|
SetDrawTarget(nullptr);
|
|
if(!MAP_DATA[map].MapData.provideOptimization){
|
|
MAP_DATA[map].LayerData.clear();
|
|
LOG(" Clearing Layer Data...");
|
|
}
|
|
}
|
|
}
|
|
|
|
void AiL::LoadLevel(MapName map,MusicChange changeMusic){
|
|
LoadingScreen::loading=true;
|
|
_PrepareLevel(map,changeMusic);
|
|
}
|
|
|
|
void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
|
|
LoadingScreen::Reset();
|
|
previousLevel=currentLevel;
|
|
currentLevel=map;
|
|
game->loadingWaitTime=0.f;
|
|
|
|
#pragma region Reset all data (Loading phase 1)
|
|
LoadingScreen::AddPhase([&](){
|
|
if(game->MAP_DATA.count(GetCurrentLevel())==0)ERR(std::format("WARNING! Could not load map {}! Does not exist! Refer to levels.txt for valid maps.",map));
|
|
if(game->MAP_DATA[GetCurrentLevel()].GetMapType()=="Hub"&&GameState::STATE!=GameState::states[States::GAME_HUB])ERR("WARNING! A hub level should only be initiated in the GAME_HUB game state!");
|
|
|
|
#pragma region Reset Environmental Audio
|
|
for(EnvironmentalAudio&audio:MAP_DATA[previousLevel].environmentalAudioData){
|
|
audio.Deactivate();
|
|
}
|
|
#pragma endregion
|
|
|
|
bossIndicatorPos.reset();
|
|
SPAWNER_LIST.clear();
|
|
SPAWNER_CONTROLLER={};
|
|
foregroundTileGroups.clear();
|
|
upperForegroundTileGroups.clear();
|
|
MONSTER_LIST.clear();
|
|
BULLET_LIST.clear();
|
|
DAMAGENUMBER_LIST.clear();
|
|
hudOverlay.Reset();
|
|
backgroundEffects.clear();
|
|
foregroundEffects.clear();
|
|
lockOnTargets.clear();
|
|
ItemDrop::drops.clear();
|
|
GameEvent::events.clear();
|
|
Audio::SetBGMPitch(1.f);
|
|
RepeatingSoundEffect::StopAllSounds();
|
|
#ifdef __EMSCRIPTEN__
|
|
Audio::muted=true;
|
|
Audio::UpdateBGMVolume();
|
|
#endif
|
|
zoomAdjustSpeed="Default Zoom Adjust Speed"_F;
|
|
targetZoom=1.f;
|
|
game->view.SetZoom(0.95f,game->view.WorldToScreen(game->camera.GetViewPosition()));
|
|
worldColor=WHITE;
|
|
worldColorFunc=[&](vi2d pos){return game->worldColor;};
|
|
levelTime=0;
|
|
bossName="";
|
|
bossDisplayTimer=0.f;
|
|
worldShakeTime=0.f;
|
|
encounterDuration=0;
|
|
totalDamageDealt=0;
|
|
encounterStarted=false;
|
|
totalBossEncounterMobs=0;
|
|
SetWindSpeed({});
|
|
Inventory::Clear("Monster Loot");
|
|
Inventory::Clear("Stage Loot");
|
|
Inventory::ResetLoadoutItemsUsed();
|
|
Input::StopVibration();
|
|
|
|
Input::SetLightbar({255,0,255});
|
|
GetPlayer()->hp=GetPlayer()->GetMaxHealth();
|
|
GetPlayer()->mana=GetPlayer()->GetMaxMana();
|
|
GetPlayer()->SetState(State::NORMAL);
|
|
GetPlayer()->GetCastInfo()={};
|
|
GetPlayer()->ResetAccumulatedXP();
|
|
GetPlayer()->_SetIframes(0.f);
|
|
GetPlayer()->SetInvisible(false);
|
|
GetPlayer()->ResetVelocity();
|
|
GetPlayer()->RemoveAllBuffs();
|
|
|
|
STEAMINPUT( //This is kind of a hack to refresh the in-game controls handle and button icons if for some reason it's not setup correctly.
|
|
Input::LoadSteamButtonIcons();
|
|
Input::ingameControlsHandle=SteamInput()->GetActionSetHandle("InGameControls");
|
|
)
|
|
|
|
ZONE_LIST=game->MAP_DATA[game->GetCurrentLevel()].ZoneData;
|
|
return true;
|
|
});
|
|
#pragma endregion
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
LoadingScreen::AddPhase([&](){
|
|
Audio::muted=true;
|
|
Audio::UpdateBGMVolume();
|
|
game->loadingWaitTime+=game->GetElapsedTime();
|
|
return game->loadingWaitTime>=0.4f;
|
|
});
|
|
#endif
|
|
|
|
#pragma region Monster Spawn Data Setup (Loading phase 2)
|
|
LoadingScreen::AddPhase([&](){
|
|
SetMosaicEffect(1U);
|
|
std::unordered_set<MonsterSpawnerID>IdsToDisable; //
|
|
if(MAP_DATA[GetCurrentLevel()].spawnControllerIDs.has_value()){
|
|
std::queue<MonsterSpawnerID>spawnController=MAP_DATA[GetCurrentLevel()].spawnControllerIDs.value();
|
|
while(!spawnController.empty()){
|
|
const int spawnerId=spawnController.front();
|
|
spawnController.pop();
|
|
auto result=IdsToDisable.insert(spawnerId);
|
|
if(!result.second)ERR(std::format("WARNING! Duplicate spawnerId {} detected when loading monster spawners. THIS SHOULD NOT BE HAPPENING!",spawnerId));
|
|
}
|
|
}
|
|
|
|
for(auto&[key,value]:MAP_DATA[GetCurrentLevel()].SpawnerData){
|
|
SpawnerTag&spawnData=MAP_DATA[GetCurrentLevel()].SpawnerData[key];
|
|
std::vector<std::pair<std::string,vf2d>>monster_list;
|
|
|
|
vf2d spawnerRadius=vf2d{spawnData.ObjectData.GetFloat("width"),spawnData.ObjectData.GetFloat("height")}/2;
|
|
for(XMLTag&monster:spawnData.monsters){
|
|
std::string monsterName=monster.GetString("value");
|
|
monster_list.push_back({monsterName,{monster.GetInteger("x")-spawnData.ObjectData.GetFloat("x"),monster.GetInteger("y")-spawnData.ObjectData.GetFloat("y")}});
|
|
}
|
|
|
|
const int spawnerId=spawnData.ObjectData.GetInteger("id");
|
|
|
|
if(SPAWNER_LIST.count(spawnerId))ERR(std::format("WARNING! Spawner ID {} for Map {} is somehow duplicated! THIS SHOULD NOT BE HAPPENING!",spawnData.ObjectData.GetInteger("id"),GetCurrentMapDisplayName()))
|
|
SPAWNER_LIST[spawnerId]=MonsterSpawner{{spawnData.ObjectData.GetFloat("x"),spawnData.ObjectData.GetFloat("y")},spawnerRadius*2,monster_list,spawnData.upperLevel,spawnData.bossNameDisplay};
|
|
if(IdsToDisable.count(spawnerId))SPAWNER_LIST.at(spawnerId).SetTriggered(true,false); //Force this spawner to be deactivated because it is in a spawn controller.
|
|
}
|
|
SPAWNER_CONTROLLER=MAP_DATA[GetCurrentLevel()].spawnControllerIDs;
|
|
return true;
|
|
});
|
|
#pragma endregion
|
|
|
|
#pragma region Identify Upper Foreground Tiles (Loading phase 3)
|
|
LoadingScreen::AddPhase([&](){
|
|
auto GetUpperZones=[&](){
|
|
for(auto&zoneSet:MAP_DATA[GetCurrentLevel()].ZoneData){
|
|
if(zoneSet.first=="UpperZone"){ //We are interested in all upper zones.
|
|
return zoneSet.second;
|
|
}
|
|
}
|
|
return std::vector<ZoneData>{};
|
|
};
|
|
for(ZoneData&zone:GetUpperZones()){
|
|
int zoneX=zone.zone.pos.x/game->GetCurrentMapData().tilewidth; //snap to grid
|
|
int zoneY=zone.zone.pos.y/game->GetCurrentMapData().tilewidth;
|
|
int zoneW=zone.zone.right().start.x/game->GetCurrentMapData().tilewidth-zoneX;
|
|
int zoneH=zone.zone.bottom().start.y/game->GetCurrentMapData().tilewidth-zoneY;
|
|
for(int x=zoneX;x<zoneX+zoneW;x++){
|
|
for(int y=zoneY;y<zoneY+zoneH;y++){
|
|
for(LayerTag&layer:MAP_DATA[GetCurrentLevel()].LayerData){
|
|
int tile=layer.tiles[y][x]-1;
|
|
TilesheetData tileSheet=GetTileSheet(currentLevel,tile);
|
|
int tileSheetIndex=tile-(tileSheet.firstgid-1);
|
|
if(IsForegroundTile(tileSheet,tileSheetIndex)){
|
|
layer.tiles[y][x]+=1000000;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
#pragma endregion
|
|
|
|
#pragma region Foreground and Upper Foreground Tile Fade Group Setup (Loading phase 3.5)
|
|
LoadingScreen::AddPhase([&](){
|
|
minimap.Reset();
|
|
return true;
|
|
});
|
|
#pragma endregion
|
|
|
|
#pragma region Foreground and Upper Foreground Tile Fade Group Setup (Loading phase 4)
|
|
LoadingScreen::AddPhase([&](){
|
|
std::set<vi2d>foregroundTilesAdded,upperForegroundTilesAdded;
|
|
for(int x=0;x<GetCurrentMapData().width;x++){
|
|
for(int y=0;y<GetCurrentMapData().height;y++){
|
|
int layerID=0;
|
|
for(LayerTag&layer:MAP_DATA[currentLevel].LayerData){
|
|
if(Unlock::IsUnlocked(layer.unlockCondition)){
|
|
int tileID=layer.tiles[y][x]-1;
|
|
if(tileID!=-1){
|
|
TilesheetData tileSheet=GetTileSheet(currentLevel,tileID);
|
|
int tileSheetWidth=MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite()->width/MAP_TILESETS.at(tileSheet.tilesetName).tilewidth;
|
|
int tileSheetHeight=MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite()->height/MAP_TILESETS.at(tileSheet.tilesetName).tileheight;
|
|
int tileSheetIndex=tileID-(tileSheet.firstgid-1);
|
|
int realTileSheetIndex=(tileID%1000000)-(tileSheet.firstgid-1);
|
|
int tileSheetX=realTileSheetIndex%tileSheetWidth;
|
|
int tileSheetY=realTileSheetIndex/tileSheetWidth;
|
|
int checkTileIndex=tileID;
|
|
int checkTileID=tileSheetIndex;
|
|
#pragma region TileGroupShenanigans
|
|
auto SetupTileGroups=[&](std::function<bool(TilesheetData,int)>IsForeground,TileRenderData tile,std::set<vi2d>&foregroundTilesIncluded,std::vector<TileGroup>&groups){
|
|
if(foregroundTilesIncluded.find({x,y})==foregroundTilesIncluded.end()&&IsForeground(tileSheet,tileSheetIndex)){
|
|
std::queue<vi2d>tileGroupChecks;
|
|
TileGroup group;
|
|
foregroundTilesIncluded.insert({x,y});
|
|
group.InsertTile(tile);
|
|
if(x>0&&foregroundTilesIncluded.find(vi2d{x,y}+vi2d{-1,0})==foregroundTilesIncluded.end())tileGroupChecks.push({x-1,y});
|
|
if(x<GetCurrentMapData().width-1&&foregroundTilesIncluded.find(vi2d{x,y}+vi2d{1,0})==foregroundTilesIncluded.end())tileGroupChecks.push({x+1,y});
|
|
if(y>0&&foregroundTilesIncluded.find(vi2d{x,y}+vi2d{0,-1})==foregroundTilesIncluded.end())tileGroupChecks.push({x,y-1});
|
|
if(y<GetCurrentMapData().height-1&&foregroundTilesIncluded.find(vi2d{x,y}+vi2d{0,1})==foregroundTilesIncluded.end())tileGroupChecks.push({x,y+1});
|
|
auto IterateThroughOtherLayers=[&](vi2d pos,bool loopAll=false){
|
|
int layer2ID=0;
|
|
bool hadForeground=false;
|
|
for(LayerTag&layer2:MAP_DATA[currentLevel].LayerData){
|
|
if(!loopAll&&&layer==&layer2){layer2ID++;continue;};
|
|
int tileID=layer2.tiles[pos.y][pos.x]-1;
|
|
TilesheetData tileSheet=GetTileSheet(currentLevel,tileID%1000000);
|
|
int tileSheetWidth=MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite()->width/MAP_TILESETS.at(tileSheet.tilesetName).tilewidth;
|
|
int tileSheetHeight=MAP_TILESETS.at(tileSheet.tilesetName).tileset->Sprite()->height/MAP_TILESETS.at(tileSheet.tilesetName).tileheight;
|
|
int tileSheetIndex=tileID-(tileSheet.firstgid-1);
|
|
int realTileSheetIndex=(tileID%1000000)-(tileSheet.firstgid-1);
|
|
int tileSheetX=realTileSheetIndex%tileSheetWidth;
|
|
int tileSheetY=realTileSheetIndex/tileSheetWidth;
|
|
TileRenderData tile={tileSheet,vi2d{pos.x,pos.y}*game->GetCurrentMapData().tilewidth,vi2d{tileSheetX,tileSheetY}*game->GetCurrentMapData().tilewidth,realTileSheetIndex,layer2ID};
|
|
if(IsForeground(tileSheet,tileSheetIndex)){
|
|
foregroundTilesIncluded.insert({pos.x,pos.y});
|
|
group.InsertTile(tile);
|
|
hadForeground=true;
|
|
}
|
|
layer2ID++;
|
|
}
|
|
return hadForeground;
|
|
};
|
|
IterateThroughOtherLayers({x,y});
|
|
while(!tileGroupChecks.empty()){
|
|
vi2d&pos=tileGroupChecks.front();
|
|
if(IterateThroughOtherLayers(pos,true)){
|
|
foregroundTilesIncluded.insert({pos.x,pos.y}); //Regardless of if we found a foreground tile or not, we need to add this to not get stuck in an infinite loop.
|
|
vi2d targetPos=pos+vi2d{-1,0};
|
|
if(pos.x>0&&foregroundTilesIncluded.find(targetPos)==foregroundTilesIncluded.end()){tileGroupChecks.push(targetPos);foregroundTilesIncluded.insert(targetPos);}
|
|
targetPos=pos+vi2d{1,0};
|
|
if(pos.x<GetCurrentMapData().width-1&&foregroundTilesIncluded.find(targetPos)==foregroundTilesIncluded.end()){tileGroupChecks.push(targetPos);foregroundTilesIncluded.insert(targetPos);}
|
|
targetPos=pos+vi2d{0,-1};
|
|
if(pos.y>0&&foregroundTilesIncluded.find(targetPos)==foregroundTilesIncluded.end()){tileGroupChecks.push(targetPos);foregroundTilesIncluded.insert(targetPos);}
|
|
targetPos=pos+vi2d{0,1};
|
|
if(pos.y<GetCurrentMapData().height-1&&foregroundTilesIncluded.find(targetPos)==foregroundTilesIncluded.end()){tileGroupChecks.push(targetPos);foregroundTilesIncluded.insert(targetPos);}
|
|
}
|
|
tileGroupChecks.pop();
|
|
}
|
|
groups.push_back(group);
|
|
}
|
|
};
|
|
TileRenderData tile={tileSheet,vi2d{x,y}*game->GetCurrentMapData().tilewidth,vi2d{tileSheetX,tileSheetY}*game->GetCurrentMapData().tilewidth,realTileSheetIndex,layerID};
|
|
SetupTileGroups([&](TilesheetData sheet,int tileID){return IsForegroundTile(sheet,tileID);},tile,foregroundTilesAdded,foregroundTileGroups);
|
|
SetupTileGroups([&](TilesheetData sheet,int tileID){return IsUpperForegroundTile(tileID);},tile,upperForegroundTilesAdded,upperForegroundTileGroups);
|
|
#pragma endregion
|
|
}
|
|
}
|
|
layerID++;
|
|
}
|
|
}
|
|
}
|
|
for(TileGroup&group:foregroundTileGroups){
|
|
std::sort(group.GetTiles().begin(),group.GetTiles().end(),[](TileRenderData&t1,TileRenderData&t2){return t1.layerID<t2.layerID;});
|
|
}
|
|
for(TileGroup&group:upperForegroundTileGroups){
|
|
std::sort(group.GetTiles().begin(),group.GetTiles().end(),[](TileRenderData&t1,TileRenderData&t2){return t1.layerID<t2.layerID;});
|
|
}
|
|
return true;
|
|
});
|
|
#pragma endregion
|
|
|
|
#pragma region Foreground and Upper Foreground Tile Fade Group Individual Object Grouping Splitting (Loading phase 5)
|
|
LoadingScreen::AddPhase([&](){
|
|
auto SplitUp=[&](std::vector<TileGroup>&group){
|
|
std::multimap<vi2d,TileRenderData>data;
|
|
using TileDataGroup=std::multimap<vi2d,TileRenderData>; //See below.
|
|
std::vector<TileDataGroup>splitUpData; //This stores every tile group with tiles as a multi map.
|
|
std::set<vi2d>iteratedTiles;
|
|
for(TileGroup&group:group){
|
|
for(TileRenderData&tile:group.GetTiles()){
|
|
data.insert({tile.pos,tile});
|
|
}
|
|
}
|
|
|
|
auto IsAdjacent=[](int tile1,int tile2,int tileSheetWidth){
|
|
return abs(tile1-tile2)==1||abs(tile1-tile2)>=tileSheetWidth-1&&abs(tile1-tile2)<=tileSheetWidth+1;
|
|
};
|
|
for(auto&[key,tile]:data){
|
|
if(iteratedTiles.count(key))continue;
|
|
vi2d loc=key;
|
|
auto loc_tiles=data.equal_range(loc);
|
|
for(auto&it=loc_tiles.first;it!=loc_tiles.second;++it){ //For each tile that exists at this position...
|
|
TileRenderData&tile=(*it).second;
|
|
bool groupFound=false;
|
|
for(TileDataGroup&group:splitUpData){ //See if this position can fit into any existing tile groups
|
|
for(int y=-game->GetCurrentMapData().tileheight;y<=game->GetCurrentMapData().tileheight;y+=game->GetCurrentMapData().tileheight){
|
|
for(int x=-game->GetCurrentMapData().tilewidth;x<=game->GetCurrentMapData().tilewidth;x+=game->GetCurrentMapData().tilewidth){
|
|
if(x!=0||y!=0){ //Check every adjacent location for a possible adjacent tile.
|
|
vi2d checkOffset=loc+vi2d{x,y};
|
|
auto find_tiles=group.equal_range(checkOffset);//Each tile group consists of tiles that may be adjacent to us. Find all tiles that are adjacent to us in this tile group.
|
|
for(auto&it=find_tiles.first;it!=find_tiles.second;++it){
|
|
//These are all tiles that were found adjacent to the location we are checking for. See if they match a potential group.
|
|
TileRenderData&foundTile=(*it).second;
|
|
if(tile.tileSheet.tilesetName==foundTile.tileSheet.tilesetName){ //Let's first see if they are even in the same tileset.
|
|
//Let's get how many tiles wide this tile sheet is.
|
|
int tileWidth=MAP_TILESETS.at(tile.tileSheet.tilesetName).tilewidth;
|
|
int tileSheetWidth=MAP_TILESETS.at(tile.tileSheet.tilesetName).tileset->Sprite()->width/tileWidth;
|
|
if(IsAdjacent(tile.tileID,foundTile.tileID,tileSheetWidth)){
|
|
group.insert({loc,tile});//We add this tile to the group! It is adjacent!
|
|
groupFound=true;
|
|
goto groupIterationDone;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
groupIterationDone:
|
|
if(!groupFound){
|
|
splitUpData.push_back({});
|
|
splitUpData.back().insert({loc,tile}); //Since we could not find a group to fit in, we had to start a brand new group.
|
|
}
|
|
}
|
|
iteratedTiles.insert(loc);
|
|
}
|
|
group.clear();
|
|
for(auto&split:splitUpData){
|
|
TileGroup newGroup;
|
|
for(auto&[key,value]:split){
|
|
newGroup.InsertTile(value);
|
|
}
|
|
group.push_back(newGroup);
|
|
}
|
|
};
|
|
SplitUp(foregroundTileGroups);
|
|
SplitUp(upperForegroundTileGroups);
|
|
std::sort(foregroundTileGroups.begin(),foregroundTileGroups.end(),[&](TileGroup&group1,TileGroup&group2){
|
|
return group1.GetCollisionRange().middle().y<group2.GetCollisionRange().middle().y;
|
|
});
|
|
std::sort(upperForegroundTileGroups.begin(),upperForegroundTileGroups.end(),[&](TileGroup&group1,TileGroup&group2){
|
|
return group1.GetCollisionRange().middle().y<group2.GetCollisionRange().middle().y;
|
|
});
|
|
return true;
|
|
});
|
|
#pragma endregion
|
|
|
|
#pragma region Bridge Layer Setup (Loading Phase 6)
|
|
LoadingScreen::AddPhase([&](){
|
|
bridgeLayerIndex=-1;
|
|
for(int counter=0;LayerTag&layer:MAP_DATA[GetCurrentLevel()].LayerData){
|
|
if(IsBridgeLayer(layer)){
|
|
bridgeLayerIndex=counter;
|
|
}
|
|
counter++;
|
|
}
|
|
return true;
|
|
});
|
|
#pragma endregion
|
|
|
|
#pragma region Setup NPCs (Loading Phase 7)
|
|
LoadingScreen::AddPhase([&](){
|
|
for(NPCData data:game->MAP_DATA[game->GetCurrentLevel()].npcs){
|
|
if(Unlock::IsUnlocked(data.unlockCondition)){
|
|
MONSTER_LIST.push_back(std::make_unique<Monster>(data.spawnPos,MONSTER_DATA[data.name]));
|
|
MONSTER_LIST.back()->ApplyIframes(INFINITE);
|
|
MONSTER_LIST.back()->npcData=data;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
#pragma endregion
|
|
|
|
#pragma region Setup Player and Camera (Loading Phase 8)
|
|
LoadingScreen::AddPhase([&](){
|
|
player->GetAbility1().cooldown=0.f;
|
|
player->GetAbility2().cooldown=0.f;
|
|
player->GetAbility3().cooldown=0.f;
|
|
player->GetAbility4().cooldown=0.f;
|
|
player->GetRightClickAbility().cooldown=0.f;
|
|
player->useItem1.cooldown=0.f;
|
|
player->useItem2.cooldown=0.f;
|
|
player->useItem3.cooldown=0.f;
|
|
|
|
player->upperLevel=false; //Assume player starts on lower level.
|
|
player->ForceSetPos(MAP_DATA[GetCurrentLevel()].MapData.playerSpawnLocation); //Normal set pos does one axis and then the other, so this will make sure that we actually end up at the right spot and ignore collision rules.
|
|
|
|
vf2d cameraStartPos=player->GetPos()+vf2d(-24*6,0);
|
|
camera.MoveCamera(cameraStartPos);
|
|
return true;
|
|
});
|
|
#pragma endregion
|
|
|
|
#pragma region Setup Pathfinding (Loading Phase 9)
|
|
LoadingScreen::AddPhase([&](){
|
|
Audio::SetAudioEvent("Default Volume");
|
|
#ifdef __EMSCRIPTEN__
|
|
Audio::muted=false;
|
|
Audio::UpdateBGMVolume();
|
|
#endif
|
|
pathfinder.Initialize();
|
|
return true;
|
|
});
|
|
#pragma endregion
|
|
|
|
if(changeMusic==PLAY_LEVEL_MUSIC){
|
|
#pragma region Audio Preparation (Loading Phase 10)
|
|
LoadingScreen::AddPhase([&](){
|
|
Audio::SetAudioEvent("Default Volume");
|
|
game->audioEngine.fullyLoaded=true; //We assume there's no audio to load, so we just set the audio as fully loaded by default.
|
|
if(MAP_DATA[GetCurrentLevel()].bgmSongName.length()>0){
|
|
Audio::PrepareBGM(MAP_DATA[GetCurrentLevel()].bgmSongName);
|
|
DisableFadeIn(true);
|
|
}
|
|
return true;
|
|
});
|
|
#pragma endregion
|
|
|
|
//Until the audio has stopped (by waiting for a set amount of time), we will respect the audio engine's wishes and not proceed.
|
|
LoadingScreen::DeferLoad([&](){return audioEngine.playBGMWaitTime==0.f;}); //This is the wait time for the audio engine to finish.
|
|
#pragma region Audio Channel Loading (Count based on Audio::GetPrepareBGMLoopIterations)
|
|
for(int i=0;i<Audio::GetPrepareBGMLoopIterations(MAP_DATA[GetCurrentLevel()].bgmSongName);i++){
|
|
LoadingScreen::AddPhase([&](){
|
|
Audio::UpdateLoop();
|
|
return true;
|
|
});
|
|
}
|
|
#pragma endregion
|
|
|
|
LoadingScreen::AddPhase([&](){
|
|
Audio::BGM&track=audioEngine.bgm[audioEngine.GetTrackName()];
|
|
for(int trackID:track.GetChannelIDs()){
|
|
audioEngine.Engine().Play(trackID,true);
|
|
}
|
|
Audio::muted=false;
|
|
Audio::UpdateBGMVolume();
|
|
return true;
|
|
});
|
|
}
|
|
|
|
LoadingScreen::AddPhase([&](){
|
|
STEAMUSERSTATS(
|
|
SteamUserStats()->StoreStats();
|
|
)
|
|
ClearGarbage();
|
|
return true;
|
|
});
|
|
}
|
|
|
|
bool AiL::IsUpperForegroundTile(int tileID){
|
|
return tileID>=1000000;
|
|
}
|
|
|
|
bool AiL::IsForegroundTile(TilesheetData sheet,int tileID){
|
|
return MAP_TILESETS.at(sheet.tilesetName).foregroundTiles.find(tileID)!=MAP_TILESETS.at(sheet.tilesetName).foregroundTiles.end();
|
|
}
|
|
|
|
const TilesheetData AiL::GetTileSheet(MapName map,int tileID)const{
|
|
const std::vector<XMLTag>&tileData=MAP_DATA.at(map).TilesetData;
|
|
if(tileData.size()==1){
|
|
size_t slashMarkerSourceDir = tileData[0].data.at("source").find_last_of('/');
|
|
std::string baseSourceDir=tileData[0].data.at("source").substr(slashMarkerSourceDir+1);
|
|
return {"assets/maps/"+baseSourceDir,1,MAP_TILESETS.at("assets/maps/"+baseSourceDir).tilecols[tileID]};
|
|
} else {
|
|
for (int i=1;i<tileData.size();i++){
|
|
int firstgid=stoi(tileData[i-1].data.at("firstgid"));
|
|
if(tileID%1000000<stoi(tileData[i].data.at("firstgid"))-1){
|
|
size_t slashMarkerSourceDir = tileData[size_t(i-1)].data.at("source").find_last_of('/');
|
|
std::string baseSourceDir=tileData[size_t(i-1)].data.at("source").substr(slashMarkerSourceDir+1);
|
|
if(tileID!=-1){
|
|
return {"assets/maps/"+baseSourceDir,firstgid,MAP_TILESETS.at("assets/maps/"+baseSourceDir).tilecols[tileID%1000000-(firstgid-1)]};
|
|
}else{
|
|
return {"assets/maps/"+baseSourceDir,firstgid,BLANK};
|
|
}
|
|
}
|
|
}
|
|
size_t slashMarkerSourceDir = tileData[tileData.size()-1].data.at("source").find_last_of('/');
|
|
std::string baseSourceDir=tileData[tileData.size()-1].data.at("source").substr(slashMarkerSourceDir+1);
|
|
int firstgid=stoi(tileData[tileData.size()-1].data.at("firstgid"));
|
|
return {"assets/maps/"+baseSourceDir,firstgid,MAP_TILESETS.at("assets/maps/"+baseSourceDir).tilecols[tileID%1000000-(firstgid-1)]};
|
|
}
|
|
}
|
|
|
|
bool AiL::HasTileCollision(MapName map,vf2d pos,bool upperLevel){
|
|
geom2d::rect<float>collisionRect=GetTileCollision(map,pos,upperLevel);
|
|
vi2d collisionRectSnapPos=vi2d{pos/float(game->GetCurrentMapData().tilewidth)}*game->GetCurrentMapData().tilewidth;
|
|
collisionRect.pos+=collisionRectSnapPos;
|
|
return geom2d::overlaps(collisionRect,pos);
|
|
}
|
|
|
|
bool AiL::IsBridgeLayer(LayerTag&layer){
|
|
return layer.tag.data.find("class")!=layer.tag.data.end()&&layer.tag.data["class"]=="Bridge";
|
|
}
|
|
|
|
bool AiL::IsOverlayLayer(LayerTag&layer){
|
|
return layer.tag.data.find("class")!=layer.tag.data.end()&&layer.tag.data["class"]=="Overlay";
|
|
}
|
|
|
|
const geom2d::rect<float>AiL::GetTileCollision(MapName map,vf2d pos,bool upperLevel)const{
|
|
const MapTag&mapData=MAP_DATA.at(map).MapData;
|
|
if(pos.x<0||pos.y<0||pos.x>=mapData.width*mapData.tilewidth||pos.y>=mapData.height*mapData.tilewidth)return NO_COLLISION;
|
|
if(GetCurrentMapData().optimized)return NO_COLLISION; //Overworld map has no collision.
|
|
|
|
bool hasTerrain=false;
|
|
for(const LayerTag&layer:MAP_DATA.at(map).LayerData){ //Figure out if any tile at this position is terrain. If so, we have a collision box to check.
|
|
if(Unlock::IsUnlocked(layer.unlockCondition)){
|
|
int tileID=layer.tiles[pos.y/mapData.tilewidth][pos.x/mapData.tilewidth]-1;
|
|
if(tileID==-1)continue;
|
|
const TilesheetData&data=GetTileSheet(map,tileID);
|
|
if(MAP_TILESETS.at(data.tilesetName).isTerrain){
|
|
hasTerrain=true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(!hasTerrain)return geom2d::rect<float>({0.f,0.f},{float(mapData.tilewidth),float(mapData.tilewidth)}); //We assume no terrain means we can't walk on this.
|
|
|
|
#pragma region Lower Bridge Collision Check
|
|
if(!upperLevel&&MAP_DATA.at(map).ZoneData.count("LowerBridgeCollision")){ //We are looking for lower bridge collisions.
|
|
for(const ZoneData&zone:MAP_DATA.at(map).ZoneData.at("LowerBridgeCollision")){
|
|
if(geom2d::contains(zone.zone,pos)){
|
|
return {{0,0},{float(mapData.tilewidth),float(mapData.tilewidth)}};
|
|
}
|
|
}
|
|
}
|
|
#pragma endregion
|
|
//The logic here is, if there's a tile on the bridge, we respect that tile instead if we're on the upper level. So we don't check other layers when we are on the upper level and there is a tile below us.
|
|
if(upperLevel&&bridgeLayerIndex!=-1){
|
|
#pragma region Layers Above Bridge Are Checked First
|
|
for(int counter=0;const LayerTag&layer:MAP_DATA.at(map).LayerData){
|
|
if(counter<=bridgeLayerIndex){
|
|
counter++;
|
|
continue;
|
|
}
|
|
if(Unlock::IsUnlocked(layer.unlockCondition)){
|
|
int tileID=layer.tiles[int(pos.y)/mapData.tilewidth][int(pos.x)/mapData.tilewidth]-1;
|
|
if(tileID!=-1&&MAP_TILESETS.at(GetTileSheet(map,tileID%1000000).tilesetName).collision.find(tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1)!=MAP_TILESETS.at(GetTileSheet(map,tileID%1000000).tilesetName).collision.end()){
|
|
const geom2d::rect<float>collisionRect=const_cast<TilesetData&>(MAP_TILESETS.at(GetTileSheet(map,tileID%1000000).tilesetName)).collision.at(tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1).collision;
|
|
return collisionRect;
|
|
}
|
|
}
|
|
counter++;
|
|
}
|
|
#pragma endregion
|
|
int tileID=MAP_DATA.at(map).LayerData[bridgeLayerIndex].tiles[int(pos.y)/mapData.tilewidth][int(pos.x)/mapData.tilewidth]-1;
|
|
if(tileID!=-1){
|
|
if(MAP_TILESETS.at(GetTileSheet(map,tileID%1000000).tilesetName).collision.find(tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1)!=MAP_TILESETS.at(GetTileSheet(map,tileID%1000000).tilesetName).collision.end()){
|
|
return const_cast<TilesetData&>(MAP_TILESETS.at(GetTileSheet(map,tileID%1000000).tilesetName)).collision.at(tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1).collision;
|
|
}
|
|
return NO_COLLISION;
|
|
}
|
|
}
|
|
geom2d::rect<float>foundRect=NO_COLLISION;
|
|
for(int counter=0;const LayerTag&layer:MAP_DATA.at(map).LayerData){
|
|
if(Unlock::IsUnlocked(layer.unlockCondition)){
|
|
//auto HasNoClass=[&](){return layer.tag.data.find("class")==layer.tag.data.end();};
|
|
if(counter!=bridgeLayerIndex){
|
|
int tileID=layer.tiles[int(pos.y)/mapData.tilewidth][int(pos.x)/mapData.tilewidth]-1;
|
|
if(tileID!=-1&&MAP_TILESETS.at(GetTileSheet(map,tileID%1000000).tilesetName).collision.find(tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1)!=MAP_TILESETS.at(GetTileSheet(map,tileID%1000000).tilesetName).collision.end()){
|
|
const geom2d::rect<float>collisionRect=const_cast<TilesetData&>(MAP_TILESETS.at(GetTileSheet(map,tileID%1000000).tilesetName)).collision.at(tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1).collision;
|
|
if(foundRect==NO_COLLISION){
|
|
foundRect=collisionRect;
|
|
}else{
|
|
//When we find another rectangle in the same square, we expand it to consume the area, whichever tile creates a larger surface or the combination of the two.
|
|
foundRect.pos.x=std::min(foundRect.pos.x,collisionRect.pos.x);
|
|
foundRect.pos.y=std::min(foundRect.pos.y,collisionRect.pos.y);
|
|
foundRect.size.x=std::max(foundRect.size.x,collisionRect.size.x);
|
|
foundRect.size.y=std::max(foundRect.size.y,collisionRect.size.y);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
counter++;
|
|
}
|
|
return foundRect;
|
|
}
|
|
|
|
Pixel AiL::GetTileColor(MapName map,vf2d pos,bool upperLevel){
|
|
MapTag&mapData=MAP_DATA[map].MapData;
|
|
if(pos.x<0||pos.y<0||pos.x>=mapData.width*mapData.tilewidth||pos.y>=mapData.height*mapData.tilewidth)return BLANK;
|
|
if(MAP_DATA[map].optimizedTile)return BLANK; //Overworld map has no collision.
|
|
|
|
|
|
bool hasTerrain=false;
|
|
for(const LayerTag&layer:MAP_DATA[map].LayerData){ //Figure out if any tile at this position is terrain. If so, we have a collision box to check.
|
|
if(Unlock::IsUnlocked(layer.unlockCondition)){
|
|
int tileID=layer.tiles[pos.y/mapData.tilewidth][pos.x/mapData.tilewidth]-1;
|
|
if(tileID==-1)continue;
|
|
const TilesheetData&data=GetTileSheet(map,tileID);
|
|
return data.tilecol;
|
|
}
|
|
}
|
|
|
|
return BLANK;
|
|
}
|
|
|
|
const MapName&AiL::GetCurrentLevel()const{
|
|
if(GameState::STATE!=nullptr&&GameState::STATE==GameState::states[States::STORY]){ //If we're inside a story, we expect the map/level name to be from the visual novel itself. Right now currentLevel would be WORLD_MAP which is not useful for a function like this.
|
|
return VisualNovel::novel.storyLevel;
|
|
}else{
|
|
return currentLevel;
|
|
}
|
|
}
|
|
|
|
void AiL::ChangePlayerClass(Class cl){
|
|
if(game->GetPlayer()->GetClass()==cl)return;
|
|
Ability itemAbility1=player->useItem1;
|
|
Ability itemAbility2=player->useItem2;
|
|
Ability itemAbility3=player->useItem3;
|
|
uint32_t oldMoney=player->money;
|
|
uint8_t level=player->level;
|
|
uint8_t levelCap=player->levelCap;
|
|
uint32_t totalXPEarned=player->totalXPEarned;
|
|
uint32_t currentLevelXP=player->currentLevelXP;
|
|
std::vector<std::weak_ptr<MenuComponent>>moneyListeners=Player::moneyListeners;
|
|
EntityStats previousStats=player->stats;
|
|
size_t cooldownSoundInstance=player->cooldownSoundInstance;
|
|
switch(cl){
|
|
case WARRIOR:{
|
|
player.reset(NEW Warrior(player.get()));
|
|
}break;
|
|
case THIEF:{
|
|
player.reset(NEW Thief(player.get()));
|
|
}break;
|
|
case TRAPPER:{
|
|
player.reset(NEW Trapper(player.get()));
|
|
}break;
|
|
case RANGER:{
|
|
player.reset(NEW Ranger(player.get()));
|
|
}break;
|
|
case WIZARD:{
|
|
player.reset(NEW Wizard(player.get()));
|
|
}break;
|
|
case WITCH:{
|
|
player.reset(NEW Witch(player.get()));
|
|
}break;
|
|
}
|
|
player->myClass={player->GetClassName()};
|
|
player->SetLevel(level);
|
|
player->levelCap=levelCap;
|
|
player->stats=previousStats;
|
|
player->SetBaseStat("Health",DATA.GetProperty(player->GetClassName()+".BaseHealth").GetInt());
|
|
player->hp=player->GetBaseStat("Health");
|
|
player->SetBaseStat("Mana",DATA.GetProperty("Player.BaseMana").GetInt());
|
|
player->mana=player->GetBaseStat("Mana");
|
|
player->SetBaseStat("Attack",DATA.GetProperty(player->GetClassName()+".BaseAtk").GetInt());
|
|
player->hpGrowthRate=float(DATA.GetProperty(player->GetClassName()+".HealthGrowthRate").GetReal());
|
|
player->atkGrowthRate=float(DATA.GetProperty(player->GetClassName()+".AtkGrowthRate").GetReal());
|
|
player->money=oldMoney;
|
|
player->cooldownSoundInstance=cooldownSoundInstance;
|
|
player->AddXP(totalXPEarned);
|
|
player->AddAccumulatedXP(totalXPEarned);
|
|
game->healthCounter.UpdateNumberPointer(&player->hp);
|
|
game->manaCounter.UpdateNumberPointer(&player->mana);
|
|
sig::Animation::SetupPlayerAnimations();
|
|
GetPlayer()->UpdateIdleAnimation(DOWN);
|
|
GetPlayer()->SetItem1UseFunc(itemAbility1);
|
|
GetPlayer()->SetItem2UseFunc(itemAbility2);
|
|
GetPlayer()->SetItem3UseFunc(itemAbility3);
|
|
camera.SetTarget(player->GetPos());
|
|
Component<MenuLabel>(CHARACTER_MENU,"Level Class Display")->SetLabel(std::format("Lv{} {}",game->GetPlayer()->Level(),game->GetPlayer()->GetClassName()));
|
|
Player::moneyListeners=moneyListeners;
|
|
GetPlayer()->InitializeMinimapImage();
|
|
}
|
|
|
|
void AiL::InitializeClasses(){
|
|
Warrior::Initialize();
|
|
Thief::Initialize();
|
|
Ranger::Initialize();
|
|
Trapper::Initialize();
|
|
Wizard::Initialize();
|
|
Witch::Initialize();
|
|
Warrior::InitializeClassAbilities();
|
|
Thief::InitializeClassAbilities();
|
|
Ranger::InitializeClassAbilities();
|
|
Trapper::InitializeClassAbilities();
|
|
Wizard::InitializeClassAbilities();
|
|
Witch::InitializeClassAbilities();
|
|
}
|
|
|
|
std::string AiL::GetString(std::string key){
|
|
return DATA.GetProperty(key).GetString();
|
|
}
|
|
|
|
datafilestringdata AiL::GetStringList(std::string key){
|
|
return {DATA,key};
|
|
}
|
|
|
|
int AiL::GetInt(std::string key){
|
|
return DATA.GetProperty(key).GetInt();
|
|
}
|
|
|
|
datafileintdata AiL::GetIntList(std::string key){
|
|
return {DATA,key};
|
|
}
|
|
|
|
float AiL::GetFloat(std::string key){
|
|
return float(DATA.GetProperty(key).GetReal());
|
|
}
|
|
|
|
datafilefloatdata AiL::GetFloatList(std::string key){
|
|
return {DATA,key};
|
|
}
|
|
|
|
double AiL::GetDouble(std::string key){
|
|
return DATA.GetProperty(key).GetReal();
|
|
}
|
|
|
|
datafiledoubledata AiL::GetDoubleList(std::string key){
|
|
return {DATA,key};
|
|
}
|
|
|
|
bool Steam_Init(){
|
|
#ifndef __EMSCRIPTEN__
|
|
if(SteamAPI_Init()){
|
|
if(SteamUtils()!=nullptr){
|
|
SteamUtils()->SetWarningMessageHook([](int severity,const char*message){
|
|
LOG(std::format("STEAM[{}]: {}",severity,std::string(message)));
|
|
});
|
|
}
|
|
STEAMUSERSTATS(
|
|
SteamUserStats()->RequestCurrentStats();
|
|
)
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
int main(const int argn,char**args)
|
|
{
|
|
debugLogger.open("debug.log");
|
|
LOG(std::format("Found {} args",argn));
|
|
bool usingSteam=true;
|
|
|
|
for(int i=0;i<argn;i++){
|
|
if(std::string(args[i])=="nosteam"){
|
|
LOG("nosteam flag detected. Disabling steam API...");
|
|
usingSteam=false;
|
|
}
|
|
LOG(std::format("{}: {}",i,args[i]));
|
|
}
|
|
|
|
if(!std::filesystem::exists("assets/config/configuration.txt")){
|
|
ERR("WARNING! Could not find initial config file! Aborting.");
|
|
return false;
|
|
}
|
|
{
|
|
utils::datafile configFile;
|
|
utils::datafile::Read(configFile,"assets/config/configuration.txt");
|
|
usingSteam&=configFile["steam_api"].GetBool();
|
|
if(usingSteam){
|
|
#ifndef __EMSCRIPTEN__
|
|
if(SteamAPI_RestartAppIfNecessary(2895980U))return false; //Immediately quit if steam is detected and can be started through it.
|
|
if(Steam_Init()){
|
|
LOG("Steam API Initialized successfully!");
|
|
}else{
|
|
LOG("Steam API failed to initialize!");
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
{
|
|
AiL demo;
|
|
|
|
demo.UsingSteamAPI(usingSteam);
|
|
|
|
#pragma region Load Window Settings
|
|
utils::datafile loadSystemFile;
|
|
|
|
std::string loadSystemFilename="save_file_path"_S+"system.conf";
|
|
|
|
vi2d windowPosConf={30,30};
|
|
vi2d windowSizeConf=WINDOW_SIZE*4;
|
|
bool fullscreenConf=false;
|
|
bool vsyncConf=GameSettings::VSyncEnabled();
|
|
|
|
if(std::filesystem::exists(loadSystemFilename)){
|
|
utils::datafile::Read(loadSystemFile,loadSystemFilename);
|
|
if(loadSystemFile.HasProperty("Window Pos")){
|
|
GameSettings::SetWindowPos({loadSystemFile["Window Pos"].GetInt(0),loadSystemFile["Window Pos"].GetInt(1)});
|
|
windowPosConf={loadSystemFile["Window Pos"].GetInt(0),loadSystemFile["Window Pos"].GetInt(1)};
|
|
}
|
|
if(loadSystemFile.HasProperty("Window Size"))windowSizeConf={loadSystemFile["Window Size"].GetInt(0),loadSystemFile["Window Size"].GetInt(1)};
|
|
size_t requiredSize=0;
|
|
|
|
#ifdef WIN32
|
|
getenv_s( &requiredSize, NULL, 0, "SteamTenfoot");
|
|
#else
|
|
const char*bigPicture=getenv("SteamTenfoot");
|
|
if(bigPicture){
|
|
requiredSize=strlen(bigPicture);
|
|
LOG("Big Picture reported a length of "<<requiredSize);
|
|
}
|
|
#endif
|
|
if(loadSystemFile.HasProperty("Fullscreen"))fullscreenConf=loadSystemFile["Fullscreen"].GetBool();
|
|
if(loadSystemFile.HasProperty("VSync"))vsyncConf=loadSystemFile["VSync"].GetBool();
|
|
if(requiredSize>0)fullscreenConf=true;
|
|
}
|
|
#pragma endregion
|
|
|
|
if (demo.Construct(windowPosConf.x, windowPosConf.y, windowSizeConf.x, windowSizeConf.y, WINDOW_SIZE.x, WINDOW_SIZE.y, 4, 4, fullscreenConf, vsyncConf))
|
|
demo.Start();
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
#ifndef __EMSCRIPTEN__
|
|
#ifndef __linux__
|
|
HANDLE hLogFile;
|
|
hLogFile = CreateFile(L"assets/memoryleak.txt", GENERIC_WRITE,
|
|
FILE_SHARE_WRITE, NULL, CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL, NULL);
|
|
_CrtSetReportMode(_CRT_WARN,_CRTDBG_MODE_FILE);
|
|
_CrtSetReportFile(_CRT_WARN,hLogFile);
|
|
_CrtDumpMemoryLeaks();
|
|
CloseHandle(hLogFile);
|
|
|
|
std::ifstream file("assets/memoryleak.txt");
|
|
bool leaked=false;
|
|
while(file.good()){
|
|
std::string line;
|
|
std::getline(file,line);
|
|
if(line.find("AiL\\")!=std::string::npos){
|
|
if(!leaked){
|
|
leaked=true;
|
|
LOG(std::endl<<std::endl<<std::endl<<"Memory leak detected!");
|
|
}
|
|
LOG(line);
|
|
std::getline(file,line);
|
|
LOG(line);
|
|
}
|
|
}
|
|
if(leaked)ERR("")
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifndef _DEBUG
|
|
#ifdef _WIN32
|
|
void get_command_line_args( int * argc, char *** argv )
|
|
{
|
|
// Get the command line arguments as wchar_t strings
|
|
wchar_t ** wargv = CommandLineToArgvW( GetCommandLineW(), argc );
|
|
if (!wargv) { *argc = 0; *argv = NULL; return; }
|
|
|
|
// Count the number of bytes necessary to store the UTF-8 versions of those strings
|
|
int n = 0;
|
|
for (int i = 0; i < *argc; i++)
|
|
n += WideCharToMultiByte( CP_UTF8, 0, wargv[i], -1, NULL, 0, NULL, NULL ) + 1;
|
|
|
|
// Allocate the argv[] array + all the UTF-8 strings
|
|
*argv = (char**)(malloc( (*argc + 1) * sizeof(char *) + n ));
|
|
if (!*argv) { *argc = 0; return; }
|
|
|
|
// Convert all wargv[] --> argv[]
|
|
char * arg = (char *)&((*argv)[*argc + 1]);
|
|
for (int i = 0; i < *argc; i++)
|
|
{
|
|
(*argv)[i] = arg;
|
|
arg += WideCharToMultiByte( CP_UTF8, 0, wargv[i], -1, arg, n, NULL, NULL ) + 1;
|
|
}
|
|
(*argv)[*argc] = NULL;
|
|
}
|
|
int CALLBACK WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow){
|
|
int argc;
|
|
char ** argv;
|
|
get_command_line_args( &argc, &argv );
|
|
|
|
for(int n = 0;n < argc;n++)
|
|
printf( " %d : %s\n", n, argv[n] );
|
|
main(argc,argv);
|
|
free( argv );
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
datafilestringdata operator ""_s(const char*key,std::size_t len){
|
|
AiL::OutputDebugInfo(key,len);
|
|
return {DATA,std::string(key,len)};
|
|
}
|
|
|
|
datafilebooldata operator ""_b(const char*key,std::size_t len){
|
|
AiL::OutputDebugInfo(key,len);
|
|
return {DATA,std::string(key,len)};
|
|
}
|
|
|
|
datafileintdata operator ""_i(const char*key,std::size_t len){
|
|
AiL::OutputDebugInfo(key,len);
|
|
return {DATA,std::string(key,len)};
|
|
}
|
|
|
|
datafilefloatdata operator ""_f(const char*key,std::size_t len){
|
|
AiL::OutputDebugInfo(key,len);
|
|
return {DATA,std::string(key,len)};
|
|
}
|
|
|
|
datafiledoubledata operator ""_d(const char*key,std::size_t len){
|
|
AiL::OutputDebugInfo(key,len);
|
|
return {DATA,std::string(key,len)};
|
|
}
|
|
|
|
Pixel operator ""_Pixel(const char*key,std::size_t len){
|
|
AiL::OutputDebugInfo(key,len);
|
|
return {uint8_t(DATA.GetProperty(std::string(key,len)).GetInt(0)),uint8_t(DATA.GetProperty(std::string(key,len)).GetInt(1)),uint8_t(DATA.GetProperty(std::string(key,len)).GetInt(2)),uint8_t(DATA.GetProperty(std::string(key,len)).GetInt(3))};
|
|
}
|
|
|
|
std::string operator ""_S(const char*key,std::size_t len){
|
|
AiL::OutputDebugInfo(key,len);
|
|
return DATA.GetProperty(std::string(key,len)).GetString();
|
|
}
|
|
|
|
bool operator ""_B(const char*key,std::size_t len){
|
|
AiL::OutputDebugInfo(key,len);
|
|
return DATA.GetProperty(std::string(key,len)).GetBool();
|
|
}
|
|
|
|
int operator ""_I(const char*key,std::size_t len){
|
|
AiL::OutputDebugInfo(key,len);
|
|
return DATA.GetProperty(std::string(key,len)).GetInt();
|
|
}
|
|
|
|
float operator ""_F(const char*key,std::size_t len){
|
|
AiL::OutputDebugInfo(key,len);
|
|
return float(DATA.GetProperty(std::string(key,len)).GetReal());
|
|
}
|
|
|
|
float operator ""_FRange(const char*key,std::size_t len){
|
|
AiL::OutputDebugInfo(key,len);
|
|
return float(util::random(float(DATA.GetProperty(std::string(key,len)).GetReal(1)-DATA.GetProperty(std::string(key,len)).GetReal(0)))+DATA.GetProperty(std::string(key,len)).GetReal(0));
|
|
}
|
|
float operator ""_Pct(long double pct){
|
|
return pct/100;
|
|
}
|
|
|
|
double operator ""_D(const char*key,std::size_t len){
|
|
AiL::OutputDebugInfo(key,len);
|
|
return DATA.GetProperty(std::string(key,len)).GetReal();
|
|
}
|
|
|
|
datafile operator ""_A(const char*key,std::size_t len){
|
|
AiL::OutputDebugInfo(key,len);
|
|
return DATA.GetProperty(std::string(key,len));
|
|
}
|
|
|
|
vf2d operator ""_V(const char*key,std::size_t len){
|
|
AiL::OutputDebugInfo(key,len);
|
|
return DATA.GetProperty(std::string(key,len)).GetVf2d();
|
|
}
|
|
|
|
void AiL::OutputDebugInfo(const char*key,std::size_t len){
|
|
#ifdef _DEBUG
|
|
if(utils::datafile::DEBUG_ACCESS_OPTIONS){
|
|
std::string k=std::string(key);
|
|
if(!k.starts_with("debug_")){
|
|
LOG("Reading "<<k);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool AiL::IsReflectiveTile(TilesheetData tileSheet,int tileID){
|
|
return MAP_TILESETS.at(tileSheet.tilesetName).reflectiveData.find(tileID)!=MAP_TILESETS.at(tileSheet.tilesetName).reflectiveData.end();
|
|
}
|
|
|
|
bool AiL::OnUserDestroy(){
|
|
gameEnd=true;
|
|
if(!savingFile){
|
|
#ifndef __EMSCRIPTEN__
|
|
SteamAPI_Shutdown();
|
|
#endif
|
|
GFX.Reset();
|
|
for(auto&[key,value]:MAP_DATA){
|
|
if(MAP_DATA[key].optimizedTile!=nullptr){
|
|
delete MAP_DATA[key].optimizedTile;
|
|
}
|
|
}
|
|
for(auto&[key,value]:MAP_TILESETS){
|
|
delete value.tileset;
|
|
}
|
|
for(auto&[key,value]:GameState::states){
|
|
delete value;
|
|
}
|
|
Menu::CleanupAllMenus();
|
|
for(auto&[key,value]:MonsterData::imgs){
|
|
delete value;
|
|
}
|
|
BACKDROP_DATA.clear();
|
|
FOREDROP_DATA.clear();
|
|
return true;
|
|
}else{
|
|
return false; //Something is preventing us from quitting. We wait patiently...
|
|
}
|
|
}
|
|
|
|
void AiL::InitializeLevels(){
|
|
for(auto&[key,size]:DATA["Levels"]){
|
|
InitializeLevel("map_path"_S+DATA["Levels"][key]["Map File"].GetString(),key);
|
|
}
|
|
|
|
std::set<std::string>cpNames;
|
|
for(ConnectionPoint&cp:State_OverworldMap::connections){
|
|
if(cpNames.count(cp.name)>0)ERR(std::format("WARNING! More than one connection point has the same name: {} THIS IS NOT ALLOWED!",cp.name));
|
|
cpNames.insert(cp.name);
|
|
}
|
|
|
|
for(ConnectionPoint&cp:State_OverworldMap::connections){
|
|
if(cp.levelDataExists)continue;
|
|
if(VisualNovel::storyLevelData.count(cp.map)){ //Visual novel story data for story levels.
|
|
cp.levelDataExists=true;
|
|
}
|
|
if(MAP_DATA.find(cp.map)!=MAP_DATA.end()){
|
|
MAP_DATA[cp.map].name=cp.name;
|
|
|
|
//Boss arena map zone check.
|
|
if(MAP_DATA[cp.map].GetMapType()=="Boss"&&MAP_DATA[cp.map].GetZones().at("BossArena").size()==0)ERR(std::format("WARNING! Map {} doesn't have a boss arena region defined! Add an object of class BossArena to the map.",MAP_DATA[cp.map].GetMapDisplayName()));
|
|
|
|
for(std::string spawn:MAP_DATA[cp.map].spawns){
|
|
cp.spawns.push_back(MONSTER_DATA.at(spawn).GetDisplayName());
|
|
}
|
|
cp.levelDataExists=true;
|
|
}
|
|
}
|
|
|
|
for(auto&[key,size]:DATA["Backdrops"]){
|
|
Renderable&backdrop=BACKDROP_DATA[key];
|
|
LoadResource(backdrop,"backdrop_directory"_S+DATA["Backdrops"][key].GetString(),false,false);
|
|
}
|
|
for(auto&[key,size]:DATA["Foredrops"]){
|
|
Renderable&backdrop=FOREDROP_DATA[key];
|
|
LoadResource(backdrop,"backdrop_directory"_S+DATA["Foredrops"][key].GetString(),false,false);
|
|
}
|
|
|
|
Test::RunMapTests();
|
|
}
|
|
|
|
Monster&AiL::SpawnMonster(vf2d pos,MonsterData&data,bool upperLevel,bool isBossSpawn){
|
|
monstersToBeSpawned.push_back(Monster(pos,data,upperLevel,isBossSpawn));
|
|
if(isBossSpawn){
|
|
totalBossEncounterMobs++;
|
|
}
|
|
return monstersToBeSpawned.back();
|
|
}
|
|
|
|
void AiL::DrawPie(vf2d center,float radius,float degreesCut,Pixel col){
|
|
DrawPolygonDecal(nullptr,circleCooldownPoints,circleCooldownPoints,std::max(1,int(degreesCut/4)),center,radius,col);
|
|
}
|
|
|
|
void AiL::DrawSquarePie(vf2d center,float radius,float degreesCut,Pixel col){
|
|
DrawPolygonDecal(nullptr,squareCircleCooldownPoints,squareCircleCooldownPoints,std::max(1,int(degreesCut/4)),center,radius,col);
|
|
}
|
|
|
|
void AiL::InitializeDefaultKeybinds(){
|
|
Player::KEY_ABILITY1.AddKeybind({KEY,Q});
|
|
Player::KEY_ABILITY1.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::FACE_L)});
|
|
Player::KEY_ABILITY1.AddKeybind({STEAM,Steam::ABILITY_1});
|
|
Player::KEY_ABILITY2.AddKeybind({KEY,E});
|
|
Player::KEY_ABILITY2.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::FACE_U)});
|
|
Player::KEY_ABILITY2.AddKeybind({STEAM,Steam::ABILITY_2});
|
|
Player::KEY_ABILITY3.AddKeybind({KEY,R});
|
|
Player::KEY_ABILITY3.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::R1)});
|
|
Player::KEY_ABILITY3.AddKeybind({STEAM,Steam::ABILITY_3});
|
|
Player::KEY_ABILITY4.AddKeybind({KEY,F});
|
|
Player::KEY_ABILITY4.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::R2)});
|
|
Player::KEY_ABILITY4.AddKeybind({STEAM,Steam::ABILITY_4});
|
|
Player::KEY_DEFENSIVE.AddKeybind({MOUSE,Mouse::RIGHT});
|
|
Player::KEY_DEFENSIVE.AddKeybind({KEY,SPACE});
|
|
Player::KEY_DEFENSIVE.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::FACE_D)});
|
|
Player::KEY_DEFENSIVE.AddKeybind({STEAM,Steam::DEFENSIVE});
|
|
Player::KEY_ITEM1.AddKeybind({KEY,K1});
|
|
Player::KEY_ITEM1.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::L1)});
|
|
Player::KEY_ITEM1.AddKeybind({STEAM,Steam::ITEM_1});
|
|
Player::KEY_ITEM2.AddKeybind({KEY,K2});
|
|
Player::KEY_ITEM2.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::L2)});
|
|
Player::KEY_ITEM2.AddKeybind({STEAM,Steam::ITEM_2});
|
|
Player::KEY_ITEM3.AddKeybind({KEY,K3});
|
|
Player::KEY_ITEM3.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::SELECT)});
|
|
Player::KEY_ITEM3.AddKeybind({STEAM,Steam::ITEM_3});
|
|
KEY_ATTACK.AddKeybind({KEY,SHIFT});
|
|
KEY_ATTACK.AddKeybind({MOUSE,Mouse::LEFT});
|
|
KEY_ATTACK.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::FACE_R)});
|
|
KEY_ATTACK.AddKeybind({STEAM,Steam::BASIC_ATTACK});
|
|
KEY_LEFT.AddKeybind({KEY,Key::A});
|
|
KEY_LEFT.AddKeybind({KEY,LEFT});
|
|
KEY_LEFT.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::DPAD_L)});
|
|
KEY_LEFT.AddKeybind({STEAM,Steam::LEFT});
|
|
KEY_RIGHT.AddKeybind({KEY,D});
|
|
KEY_RIGHT.AddKeybind({KEY,RIGHT});
|
|
KEY_RIGHT.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::DPAD_R)});
|
|
KEY_RIGHT.AddKeybind({STEAM,Steam::RIGHT});
|
|
KEY_UP.AddKeybind({KEY,W});
|
|
KEY_UP.AddKeybind({KEY,UP});
|
|
KEY_UP.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::DPAD_U)});
|
|
KEY_UP.AddKeybind({STEAM,Steam::UP});
|
|
KEY_DOWN.AddKeybind({KEY,S});
|
|
KEY_DOWN.AddKeybind({KEY,DOWN});
|
|
KEY_DOWN.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::DPAD_D)});
|
|
KEY_DOWN.AddKeybind({STEAM,Steam::DOWN});
|
|
KEY_CONFIRM.AddKeybind({MOUSE,Mouse::LEFT});
|
|
KEY_CONFIRM.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::FACE_R)});
|
|
//KEY_CONFIRM.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::START)});
|
|
KEY_CONFIRM.AddKeybind({KEY,Z});
|
|
KEY_CONFIRM.AddKeybind({KEY,ENTER});
|
|
KEY_CONFIRM.AddKeybind({STEAM,Steam::CONFIRM});
|
|
KEY_BACK.AddKeybind({KEY,X});
|
|
KEY_BACK.AddKeybind({KEY,SHIFT});
|
|
KEY_BACK.AddKeybind({KEY,ESCAPE});
|
|
KEY_BACK.AddKeybind({STEAM,Steam::BACK});
|
|
KEY_BACK.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::FACE_D)});
|
|
KEY_MENU.AddKeybind({KEY,ESCAPE});
|
|
KEY_MENU.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::START)});
|
|
KEY_MENU.AddKeybind({STEAM,Steam::MENU_PAUSE});
|
|
|
|
KEY_ENTER.AddKeybind({KEY,ENTER});
|
|
|
|
KEY_FASTSCROLLUP.AddKeybind({KEY,Q});
|
|
KEY_FASTSCROLLUP.AddKeybind({KEY,PGUP});
|
|
KEY_FASTSCROLLUP.AddKeybind({KEY,NP8});
|
|
KEY_FASTSCROLLUP.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::L1)});
|
|
KEY_FASTSCROLLUP.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::L2)});
|
|
KEY_FASTSCROLLUP.AddKeybind({STEAM,Steam::FAST_SCROLL_UP});
|
|
KEY_FASTSCROLLDOWN.AddKeybind({KEY,E});
|
|
KEY_FASTSCROLLDOWN.AddKeybind({KEY,PGDN});
|
|
KEY_FASTSCROLLDOWN.AddKeybind({KEY,NP2});
|
|
KEY_FASTSCROLLDOWN.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::R1)});
|
|
KEY_FASTSCROLLDOWN.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::R2)});
|
|
KEY_FASTSCROLLDOWN.AddKeybind({STEAM,Steam::FAST_SCROLL_DOWN});
|
|
|
|
KEY_CHANGE_LOADOUT.AddKeybind({KEY,R});
|
|
KEY_CHANGE_LOADOUT.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::FACE_U)});
|
|
KEY_CHANGE_LOADOUT.AddKeybind({STEAM,Steam::FUNCTION_1});
|
|
|
|
KEY_START.AddKeybind({KEY,SPACE});
|
|
KEY_START.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::START)});
|
|
KEY_START.AddKeybind({STEAM,Steam::MENU_PAUSE});
|
|
KEY_CONTROLLER_START.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::START)});
|
|
KEY_CONTROLLER_START.AddKeybind({STEAM,Steam::MENU_PAUSE});
|
|
KEY_SELECT.AddKeybind({KEY,CTRL});
|
|
KEY_SELECT.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::SELECT)});
|
|
KEY_SELECT.AddKeybind({STEAM,Steam::LOCK_UNLOCK_ACC});
|
|
|
|
KEY_FACEUP.AddKeybind({KEY,R});
|
|
KEY_FACEUP.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::FACE_U)});
|
|
KEY_FACEUP.AddKeybind({STEAM,Steam::FUNCTION_1});
|
|
KEY_FACERIGHT.AddKeybind({KEY,Z});
|
|
KEY_FACERIGHT.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::FACE_R)});
|
|
KEY_FACERIGHT.AddKeybind({STEAM,Steam::CONFIRM});
|
|
KEY_FACELEFT.AddKeybind({KEY,F});
|
|
KEY_FACELEFT.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::FACE_L)});
|
|
KEY_FACELEFT.AddKeybind({STEAM,Steam::FUNCTION_2});
|
|
KEY_FACEDOWN.AddKeybind({KEY,X});
|
|
KEY_FACEDOWN.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::FACE_D)});
|
|
KEY_FACEDOWN.AddKeybind({STEAM,Steam::BACK});
|
|
|
|
KEY_SCROLLLEFT.AddKeybind({ANALOG,static_cast<int>(GPAxes::LX)});
|
|
KEY_SCROLLLEFT.AddKeybind({ANALOG,static_cast<int>(GPAxes::RX)});
|
|
KEY_SCROLLLEFT.AddKeybind({STEAM,Steam::SCROLL});
|
|
KEY_SCROLLRIGHT.AddKeybind({ANALOG,static_cast<int>(GPAxes::LX)});
|
|
KEY_SCROLLRIGHT.AddKeybind({ANALOG,static_cast<int>(GPAxes::RX)});
|
|
KEY_SCROLLRIGHT.AddKeybind({STEAM,Steam::SCROLL});
|
|
KEY_SCROLLUP.AddKeybind({ANALOG,static_cast<int>(GPAxes::LY)});
|
|
KEY_SCROLLUP.AddKeybind({ANALOG,static_cast<int>(GPAxes::RY)});
|
|
KEY_SCROLLUP.AddKeybind({STEAM,Steam::SCROLL});
|
|
KEY_SCROLLDOWN.AddKeybind({ANALOG,static_cast<int>(GPAxes::LY)});
|
|
KEY_SCROLLDOWN.AddKeybind({ANALOG,static_cast<int>(GPAxes::RY)});
|
|
KEY_SCROLLDOWN.AddKeybind({STEAM,Steam::SCROLL});
|
|
KEY_SCROLLVERT.AddKeybind({ANALOG,static_cast<int>(GPAxes::LY)});
|
|
KEY_SCROLLVERT.AddKeybind({ANALOG,static_cast<int>(GPAxes::RY)});
|
|
KEY_SCROLLVERT.AddKeybind({STEAM,Steam::SCROLL});
|
|
KEY_SCROLLVERT_L.AddKeybind({ANALOG,static_cast<int>(GPAxes::LY)});
|
|
KEY_SCROLLVERT_L.AddKeybind({STEAM,Steam::SCROLL});
|
|
KEY_SCROLLVERT_R.AddKeybind({ANALOG,static_cast<int>(GPAxes::RY)});
|
|
KEY_SCROLLVERT_R.AddKeybind({STEAM,Steam::SCROLL});
|
|
KEY_SCROLLHORZ_L.AddKeybind({ANALOG,static_cast<int>(GPAxes::LX)});
|
|
KEY_SCROLLHORZ_L.AddKeybind({STEAM,Steam::SCROLL});
|
|
KEY_SCROLLHORZ_R.AddKeybind({ANALOG,static_cast<int>(GPAxes::RX)});
|
|
KEY_SCROLLHORZ_R.AddKeybind({STEAM,Steam::SCROLL});
|
|
KEY_SCROLLHORZ.AddKeybind({ANALOG,static_cast<int>(GPAxes::LX)});
|
|
KEY_SCROLLHORZ.AddKeybind({ANALOG,static_cast<int>(GPAxes::RX)});
|
|
KEY_SCROLLHORZ.AddKeybind({STEAM,Steam::SCROLL});
|
|
KEY_SCROLL.AddKeybind({KEY,ARROWS});
|
|
KEY_SCROLL.AddKeybind({ANALOG,static_cast<int>(GPAxes::ALL)});
|
|
KEY_SCROLL.AddKeybind({STEAM,Steam::SCROLL});
|
|
|
|
KEY_SHOULDER.AddKeybind({KEY,SHOULDER});
|
|
KEY_SHOULDER.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::SHOULDER)});
|
|
KEY_SHOULDER.AddKeybind({STEAM,Steam::FAST_SCROLL_UP});
|
|
KEY_SHOULDER2.AddKeybind({STEAM,Steam::FAST_SCROLL_DOWN});
|
|
KEY_FACEDOWN.AddKeybind({STEAM,Steam::FAST_SCROLL_DOWN});
|
|
KEY_FACEDOWN.AddKeybind({STEAM,Steam::FAST_SCROLL_UP});
|
|
|
|
KEY_UNEQUIP.AddKeybind({KEY,R});
|
|
KEY_UNEQUIP.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::FACE_U)});
|
|
KEY_FACEDOWN.AddKeybind({STEAM,Steam::FUNCTION_1});
|
|
|
|
KEY_TOGGLE_MAP.AddKeybind({KEY,TAB});
|
|
KEY_TOGGLE_MAP.AddKeybind({CONTROLLER,static_cast<int>(GPButtons::R3)});
|
|
KEY_TOGGLE_MAP.AddKeybind({STEAM,Steam::TOGGLE_MAP});
|
|
|
|
KEY_MOUSE_RIGHT.AddKeybind({MOUSE,Mouse::RIGHT});
|
|
|
|
#define TieMenuNameToMenuInputGroup(KEY_NAME) \
|
|
InputGroup::menuNamesToInputGroups[DATA["Inputs"][#KEY_NAME].GetString()]=&KEY_NAME; \
|
|
InputGroup::menuInputGroups.push_back(DATA["Inputs"][#KEY_NAME].GetString());
|
|
#define TieMenuNameToGameplayInputGroup(KEY_NAME) \
|
|
InputGroup::menuNamesToInputGroups[DATA["Inputs"][#KEY_NAME].GetString()]=&KEY_NAME; \
|
|
InputGroup::gameplayInputGroups.push_back(DATA["Inputs"][#KEY_NAME].GetString());
|
|
|
|
TieMenuNameToMenuInputGroup(KEY_CONFIRM)
|
|
TieMenuNameToMenuInputGroup(KEY_LEFT);
|
|
TieMenuNameToMenuInputGroup(KEY_RIGHT);
|
|
TieMenuNameToMenuInputGroup(KEY_UP);
|
|
TieMenuNameToMenuInputGroup(KEY_DOWN);
|
|
TieMenuNameToMenuInputGroup(KEY_MENU);
|
|
TieMenuNameToMenuInputGroup(KEY_BACK);
|
|
TieMenuNameToMenuInputGroup(KEY_FACEUP);
|
|
TieMenuNameToMenuInputGroup(KEY_FACELEFT);
|
|
TieMenuNameToGameplayInputGroup(KEY_ATTACK);
|
|
TieMenuNameToGameplayInputGroup(Player::KEY_ABILITY1);
|
|
TieMenuNameToGameplayInputGroup(Player::KEY_ABILITY2);
|
|
TieMenuNameToGameplayInputGroup(Player::KEY_ABILITY3);
|
|
TieMenuNameToGameplayInputGroup(Player::KEY_ABILITY4);
|
|
TieMenuNameToGameplayInputGroup(Player::KEY_DEFENSIVE);
|
|
TieMenuNameToGameplayInputGroup(Player::KEY_ITEM1);
|
|
TieMenuNameToGameplayInputGroup(Player::KEY_ITEM2);
|
|
TieMenuNameToGameplayInputGroup(Player::KEY_ITEM3);
|
|
TieMenuNameToGameplayInputGroup(KEY_TOGGLE_MAP);
|
|
|
|
InputGroup::menuNamesToInputGroups.SetInitialized();
|
|
}
|
|
|
|
void AiL::SetBossNameDisplay(std::string name,float time){
|
|
const bool HasNotBeenDisplayedYet=bossName=="";
|
|
bossName=name;
|
|
if(HasNotBeenDisplayedYet)bossDisplayTimer=time; //Only display once.
|
|
}
|
|
|
|
bool AiL::InBossEncounter(){
|
|
return bossName!="";
|
|
}
|
|
|
|
void AiL::StartBossEncounter(){
|
|
if(!encounterStarted){
|
|
encounterStarted=true;
|
|
totalDamageDealt=0;
|
|
encounterDuration=0.f;
|
|
}
|
|
}
|
|
|
|
void AiL::DisplayBossEncounterInfo(){
|
|
if(bossDisplayTimer>0){
|
|
std::string displayText="- "+bossName+" -";
|
|
|
|
uint8_t alpha=0;
|
|
if(bossDisplayTimer>4){
|
|
alpha=uint8_t((5-bossDisplayTimer)*255);
|
|
}else
|
|
if(bossDisplayTimer>1){
|
|
alpha=255;
|
|
}else{
|
|
alpha=uint8_t((bossDisplayTimer)*255);
|
|
}
|
|
vf2d textScale={3,5};
|
|
textScale.x=std::min(3.f,float(ScreenWidth())/GetTextSizeProp(displayText).x);
|
|
DrawShadowStringPropDecal(vf2d{float(ScreenWidth()/2),float(ScreenHeight()/2)}-vf2d{GetTextSizeProp(displayText)}*textScale/2,displayText,{252, 186, 3, alpha},{128,0,0,alpha},textScale,std::numeric_limits<float>::max(),2);
|
|
}
|
|
if(InBossEncounter()){
|
|
Pixel displayCol=totalBossEncounterMobs==0?Pixel{224, 133, 29}:WHITE;
|
|
std::string timeDisplay=util::timerStr(encounterDuration);
|
|
vf2d timerTextSize=GetTextSizeProp(timeDisplay);
|
|
DrawShadowStringPropDecal(vf2d{ScreenWidth()-2-timerTextSize.x,2+10*0},timeDisplay,displayCol);
|
|
vf2d displayTextSize=GetTextSizeProp(bossName);
|
|
DrawShadowStringPropDecal(vf2d{ScreenWidth()-2-displayTextSize.x,2+10*1},bossName,displayCol);
|
|
if(encounterDuration>=1){
|
|
std::string dpsText="DPS: "+std::to_string(int(totalDamageDealt/encounterDuration));
|
|
vf2d dpsDisplayTextSize=GetTextSizeProp(dpsText);
|
|
DrawShadowStringPropDecal(vf2d{ScreenWidth()-2-dpsDisplayTextSize.x,2+10*2},dpsText,displayCol);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AiL::BossDamageDealt(int damage){
|
|
totalDamageDealt+=damage;
|
|
}
|
|
|
|
void AiL::ReduceBossEncounterMobCount(){
|
|
totalBossEncounterMobs--;
|
|
if(totalBossEncounterMobs<0){
|
|
ERR("WARNING! Boss Encounter mob count is less than zero, THIS SHOULD NOT BE HAPPENING!");
|
|
}
|
|
}
|
|
|
|
void AiL::RenderMenu(){
|
|
if(Menu::stack.size()>0){
|
|
if(Menu::alreadyClicked){ //Do not run a click event for this round.
|
|
Menu::alreadyClicked=false;
|
|
}else{
|
|
if(!MenuClicksDeactivated()){
|
|
Menu::stack.back()->Update(this);
|
|
}
|
|
}
|
|
for(Menu*menu:Menu::stack){
|
|
if(menu->cover)FillRectDecal({0,0},WINDOW_SIZE,{0,0,0,uint8_t(255*0.4)});
|
|
menu->Draw(this);
|
|
}
|
|
}
|
|
|
|
#pragma region Menu Navigation Debug Info
|
|
#ifdef _DEBUG
|
|
if("debug_menu_navigation_info"_I){
|
|
if(Menu::stack.size()>0){
|
|
std::weak_ptr<MenuComponent>component=Menu::stack.back()->GetSelection();
|
|
if(!component.expired()){
|
|
DrawShadowStringDecal({2,2},"Selection: "+component.lock()->GetName());
|
|
}
|
|
std::weak_ptr<MenuComponent>keyComponent=Menu::stack.back()->GetKeySelection();
|
|
if(!keyComponent.expired()){
|
|
DrawShadowStringDecal({2,14},"Key Selection: "+keyComponent.lock()->GetName());
|
|
}
|
|
}
|
|
DrawShadowStringDecal({2,26},"MOUSE NAVIGATION: "+std::to_string(Menu::MOUSE_NAVIGATION));
|
|
}
|
|
#endif
|
|
#pragma endregion
|
|
}
|
|
|
|
void AiL::InitializeGraphics(){
|
|
circleCooldownPoints.clear();
|
|
squareCircleCooldownPoints.clear();
|
|
GFX.Reset();
|
|
Menu::themes.Reset();
|
|
|
|
//These two create item graphics, so they should be after the GFX structure gets reset!
|
|
ItemAttribute::Initialize();
|
|
ItemInfo::InitializeItems();
|
|
|
|
circleCooldownPoints.push_back({0,0});
|
|
squareCircleCooldownPoints.push_back({0,0});
|
|
for(int i=0;i<=360;i+=4){
|
|
float angle=util::degToRad(float(i))-PI/2;
|
|
if(i==0){circleCooldownPoints.push_back(vf2d{cos(angle),sin(angle)});}
|
|
circleCooldownPoints.push_back(vf2d{cos(angle),sin(angle)});
|
|
vf2d point=vf2d{cos(angle),sin(angle)}*sqrt(2.1f);
|
|
point.x=std::clamp(point.x,-1.f,1.f);
|
|
point.y=std::clamp(point.y,-1.f,1.f);
|
|
if(i==0){squareCircleCooldownPoints.push_back(point);}
|
|
squareCircleCooldownPoints.push_back(point);
|
|
}
|
|
|
|
for(auto&val:DATA["Images"].GetKeys()){
|
|
std::string key=val.first;
|
|
std::string imgFile=DATA["Images"][key].GetString(0);
|
|
LOG("Loading image "+imgFile+"...");
|
|
bool filtering=false;
|
|
bool clamping=false;
|
|
if(DATA["Images"][key].GetValueCount()>1){
|
|
filtering=bool(DATA["Images"][key].GetInt(1));
|
|
}
|
|
if(DATA["Images"][key].GetValueCount()>2){
|
|
clamping=bool(DATA["Images"][key].GetInt(2));
|
|
}
|
|
if(!GFX.count(imgFile)&&LoadResource(GFX[imgFile],"GFX_Prefix"_S+imgFile,filtering,clamping)!=rcode::OK){
|
|
ERR(" WARNING! Failed to load "+imgFile+" from game pack!")
|
|
}
|
|
}
|
|
|
|
//Specifically split up the 9 patch image into multiple pieces for each theme.
|
|
const auto&unordered_map=DATA["Themes"].GetKeys();
|
|
std::vector<std::pair<std::string,size_t>>mappedKeys;
|
|
mappedKeys.reserve(unordered_map.size());
|
|
for(auto&key:unordered_map){
|
|
mappedKeys.push_back(key);
|
|
}
|
|
std::sort(mappedKeys.begin(),mappedKeys.end(),[](std::pair<std::string,size_t>&key1,std::pair<std::string,size_t>&key2){return key1.second<key2.second;});
|
|
for(auto&[key,value]:mappedKeys){
|
|
std::string themeName=key;
|
|
std::string imgPath=DATA["Themes"][themeName]["filename"].GetString();
|
|
Renderable&img=GFX["theme_img_directory"_S+imgPath+".png"];
|
|
LoadResource(img,"GFX_Prefix"_S+"theme_img_directory"_S+imgPath+".png");
|
|
Renderable&sourceImg=img;
|
|
Pixel::Mode prevMode=GetPixelMode();
|
|
SetPixelMode(Pixel::Mode::MASK);
|
|
for(int x=0;x<3;x++){
|
|
for(int y=0;y<3;y++){
|
|
std::string patchKey=themeName+"_"+std::to_string(x)+std::to_string(y)+".png";
|
|
Renderable&patchImg=GFX[patchKey];
|
|
patchImg.Create("Interface.9PatchSize"_i[0],"Interface.9PatchSize"_i[1],false,false);
|
|
SetDrawTarget(patchImg.Sprite());
|
|
Clear(BLANK);
|
|
DrawPartialSprite({0,0},sourceImg.Sprite(),{x*"Interface.9PatchSize"_i[0],y*"Interface.9PatchSize"_i[1]},{"Interface.9PatchSize"_i[0],"Interface.9PatchSize"_i[1]});
|
|
patchImg.Decal()->Update();
|
|
SetDrawTarget(nullptr);
|
|
}
|
|
}
|
|
SetPixelMode(prevMode);
|
|
|
|
LOG("Theme "<<themeName<<" Loaded.");
|
|
|
|
if(DATA["Themes"][themeName].HasProperty("CustomBack")){
|
|
std::string backPath=DATA["Themes"][themeName]["CustomBack"].GetString();
|
|
LOG(" Custom background detected, Loading "<<backPath<<"...");
|
|
if(!GFX.count(backPath)){
|
|
Renderable&background=GFX[backPath];
|
|
LoadResource(background,"GFX_Prefix"_S+backPath,false,false);
|
|
}
|
|
Menu::themes[themeName]=Theme{themeName,imgPath,bool(DATA["Themes"][themeName]["Tiled"].GetInt()),DATA["Themes"][themeName]["ButtonColor"].GetPixel(),DATA["Themes"][themeName]["HighlightColor"].GetPixel(),GFX.at(backPath).Decal()};
|
|
}else{
|
|
Menu::themes[themeName]=Theme{themeName,imgPath,bool(DATA["Themes"][themeName]["Tiled"].GetInt()),DATA["Themes"][themeName]["ButtonColor"].GetPixel(),DATA["Themes"][themeName]["HighlightColor"].GetPixel()};
|
|
}
|
|
}
|
|
|
|
for(std::string img:VisualNovel::graphicsToLoad){
|
|
Renderable&image=GFX[img];
|
|
LoadResource(image,"GFX_Prefix"_S+img);
|
|
}
|
|
LOG(VisualNovel::graphicsToLoad.size()<<" images for visual novel engine have been loaded.");
|
|
|
|
SetFontSprite("GFX_Prefix"_S+"font.png",&gamepack,"GENERATE_GAMEPACK"_B);
|
|
LOG("Custom font loaded.");
|
|
|
|
Menu::themes.SetInitialized();
|
|
LOG(Menu::themes.size()<<" themes have been loaded.");
|
|
GFX.SetInitialized();
|
|
LOG(GFX.size()<<" images have been loaded.");
|
|
}
|
|
|
|
const Map&AiL::GetCurrentMap()const{
|
|
return MAP_DATA.at(GetCurrentLevel());
|
|
}
|
|
const MapTag&AiL::GetCurrentMapData()const{
|
|
return GetCurrentMap().MapData;
|
|
}
|
|
const MapName&AiL::GetCurrentMapName()const{
|
|
return GetCurrentLevel();
|
|
}
|
|
|
|
void AiL::ValidateGameStatus(){
|
|
if(Unlock::unlocks.size()==0){
|
|
ERR("WARNING! There are no unlocks set! This was probably not intentional! It means no areasa on the overworld are accessible!");
|
|
}
|
|
if(!Unlock::IsUnlocked(State_OverworldMap::GetCurrentConnectionPoint())){
|
|
ERR("WARNING! The current connection point is not unlocked! This is not supposed to be happening.")
|
|
}
|
|
if(ItemDrop::gravity!="ItemDrop.Item Drop Gravity"_F){
|
|
ERR("WARNING! Gravity constant for item drops was not initialized to "<<"Item Drop Gravity"_F<<". Actual value: "<<ItemDrop::gravity);
|
|
}
|
|
for(auto&[name,map]:MAP_DATA){
|
|
for(EnvironmentalAudio&audio:map.environmentalAudioData){
|
|
if(EnvironmentalAudio::SOUND_DATA.find(audio.audioName)==EnvironmentalAudio::SOUND_DATA.end())ERR(std::format("WARNING! Could not find environmental audio data {} for Map {}. Check audio/environmentalaudio.txt configuration!",audio.audioName,map.name));
|
|
}
|
|
}
|
|
|
|
auto MapStoryDataExists = [&](std::string_view mapName){
|
|
return MAP_DATA.count(MapName(mapName))>0||
|
|
VisualNovel::storyLevelData.count(std::string(mapName))>0;
|
|
};
|
|
|
|
if(!MapStoryDataExists("NPCs.Sherman.Potion Crafting Unlock Condition"_S))ERR(std::format("WARNING! Sherman's Potion Crafting Unlock Condition: {} is not a valid map!","NPCs.Sherman.Potion Crafting Unlock Condition"_S))
|
|
if(!MapStoryDataExists("NPCs.Greg.Camp Notification Unlock Condition"_S))ERR(std::format("WARNING! Greg's Potion Crafting Unlock Condition: {} is not a valid map!","NPCs.Greg.Camp Notification Unlock Condition"_S))
|
|
|
|
#pragma region Map Spawn Statistics
|
|
if("display_spawn_report"_I){
|
|
for(auto&[map,data]:MAP_DATA){
|
|
std::map<std::string,long>monsterCounts;
|
|
for(auto&[key,value]:MAP_DATA[map].SpawnerData){
|
|
SpawnerTag&spawnData=MAP_DATA[map].SpawnerData[key];
|
|
|
|
vf2d spawnerRadius=vf2d{spawnData.ObjectData.GetFloat("width"),spawnData.ObjectData.GetFloat("height")}/2;
|
|
for(XMLTag&monster:spawnData.monsters){
|
|
std::string monsterName=monster.GetString("value");
|
|
monsterCounts[monsterName]++;
|
|
}
|
|
}
|
|
LOG("Spawns Report for "<<map<<":");
|
|
for(auto&[monster,count]:monsterCounts){
|
|
LOG("\t"<<count<<"x "<<monster);
|
|
}
|
|
std::map<ItemInfo*,long>totalDrops;
|
|
LOG("Monte Carlo Test: 100000 tries:");
|
|
for(int i=0;i<100000;i++){
|
|
//Try to kill every single monster and see what drops.
|
|
for(auto&[monster,count]:monsterCounts){
|
|
Monster m=Monster{{0,0},MONSTER_DATA[monster]};
|
|
for(int j=0;j<count;j++){
|
|
auto drops=m.SpawnDrops();
|
|
for(auto&[itemInfo,count]:drops){
|
|
totalDrops[itemInfo]+=count;
|
|
}
|
|
ItemDrop::drops.clear(); //Since we're testing clear the drop list...
|
|
}
|
|
}
|
|
}
|
|
LOG("Average: ");
|
|
for(auto&[itemInfo,count]:totalDrops){
|
|
LOG("\t"<<itemInfo->Name()<<" x"<<std::format("{:.3}",count/100000.));
|
|
}
|
|
}
|
|
}
|
|
#pragma endregion
|
|
}
|
|
|
|
void AiL::RenderVersionInfo(){
|
|
saveGameDisplayTime=std::max(0.f,saveGameDisplayTime-game->GetElapsedTime());
|
|
if(saveGameDisplayTime>3.f){
|
|
DrawShadowStringDecal({4.f,4.f},"Saving Game...",{255,255,255,uint8_t((sin(game->GetRunTime())+1)*127)},{0,0,0,uint8_t((sin(game->GetRunTime())+1)*127)});
|
|
}else
|
|
if(saveGameDisplayTime>0.f){
|
|
uint8_t alpha=uint8_t(util::lerp(0,255,saveGameDisplayTime/3.f));
|
|
DrawShadowStringDecal({4.f,4.f},"Save Complete.",{255,255,255,alpha},{0,0,0,alpha});
|
|
}
|
|
|
|
std::string versionStr("v" + std::to_string(VERSION_MAJOR) + "." + std::to_string(VERSION_MINOR) + "." + std::to_string(VERSION_PATCH) + "." + std::to_string(VERSION_BUILD));
|
|
DrawShadowStringDecal(vf2d{ GetScreenSize() } - vf2d{ GetTextSize(versionStr) }*0.4f,versionStr,WHITE,BLACK,{0.4f,0.4f},std::numeric_limits<float>::max(),0.4f);
|
|
}
|
|
|
|
int AiL::GetCurrentChapter(){
|
|
return chapter;
|
|
}
|
|
|
|
void AiL::SetChapter(int chapter){
|
|
this->chapter=chapter;
|
|
for(std::weak_ptr<MenuComponent>component:Menu::chapterListeners){
|
|
component.lock()->OnChapterUpdate(chapter);
|
|
}
|
|
}
|
|
|
|
const std::weak_ptr<Item>AiL::GetLoadoutItem(int slot){
|
|
if(slot<0||slot>GetLoadoutSize()-1)ERR("Invalid inventory slot "+std::to_string(slot)+", please choose a slot in range (0-"+std::to_string(GetLoadoutSize()-1)+").");
|
|
return loadout[slot];
|
|
}
|
|
|
|
void AiL::RestockLoadoutItems(){
|
|
for(int i=0;i<GetLoadoutSize();i++){
|
|
if(!ISBLANK(GetLoadoutItem(i))){
|
|
SetLoadoutItem(i,GetLoadoutItem(i).lock()->ActualName());
|
|
}
|
|
}
|
|
}
|
|
|
|
void AiL::SetLoadoutItem(int slot,std::string itemName){
|
|
if(slot<0||slot>GetLoadoutSize()-1)ERR("Invalid inventory slot "+std::to_string(slot)+", please choose a slot in range (0-"+std::to_string(GetLoadoutSize()-1)+").");
|
|
if(Inventory::GetItemCount(itemName)>0){
|
|
if(loadout[slot]&&!ISBLANK(loadout[slot])&&loadout[slot]->Amt()>0){
|
|
//If the slot isn't blank, we should clear the item from being selected in the consumables list.
|
|
auto&itemsList=Component<InventoryScrollableWindowComponent>(INVENTORY_CONSUMABLES,"inventory")->GetComponents();
|
|
auto item=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&component){
|
|
if(DYNAMIC_POINTER_CAST<MenuItemButton>(component)->GetItem().lock()->ActualName()==loadout[slot]->ActualName()){
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
if(item!=itemsList.end()){
|
|
DYNAMIC_POINTER_CAST<MenuItemButton>(*item)->selected=-1;
|
|
}else ERR(std::format("WARNING! Could not find item {} in inventory while deselecting a loadout item from the consumables menu! THIS SHOULD NOT BE HAPPENING!",loadout[slot]->ActualName()));
|
|
}
|
|
loadout[slot]=Inventory::CopyItem(itemName)[0];
|
|
GetLoadoutItem(slot).lock()->amt=std::min((uint32_t)"Player.Item Loadout Limit"_I,GetLoadoutItem(slot).lock()->Amt()); //We are only allowed to have up to 10 maximum of an item on a journey.
|
|
InputGroup*inputGroup=nullptr;
|
|
switch(slot){
|
|
case 0:{
|
|
inputGroup=&Player::KEY_ITEM1;
|
|
}break;
|
|
case 1:{
|
|
inputGroup=&Player::KEY_ITEM2;
|
|
}break;
|
|
case 2:{
|
|
inputGroup=&Player::KEY_ITEM3;
|
|
}break;
|
|
}
|
|
Ability itemAbility{itemName,"","","Item.Item Cooldown Time"_F,0,inputGroup,"items/"+itemName+".png",VERY_DARK_RED,DARK_RED,PrecastData{GetLoadoutItem(slot).lock()->CastTime(),0,0},true};
|
|
itemAbility.actionPerformedDuringCast=GetLoadoutItem(slot).lock()->UseDuringCast();
|
|
itemAbility.itemAbility=true;
|
|
|
|
switch(slot){
|
|
case 0:{
|
|
itemAbility.action=[&](Player*p,vf2d pos={}){
|
|
return game->UseLoadoutItem(0);
|
|
};
|
|
game->GetPlayer()->SetItem1UseFunc(itemAbility);
|
|
Component<MenuItemItemButton>(MenuType::ITEM_LOADOUT,"Loadout Item 1")->SetItem(loadout[slot]);
|
|
}break;
|
|
case 1:{
|
|
itemAbility.action=[&](Player*p,vf2d pos={}){
|
|
return game->UseLoadoutItem(1);
|
|
};
|
|
game->GetPlayer()->SetItem2UseFunc(itemAbility);
|
|
Component<MenuItemItemButton>(MenuType::ITEM_LOADOUT,"Loadout Item 2")->SetItem(loadout[slot]);
|
|
}break;
|
|
case 2:{
|
|
itemAbility.action=[&](Player*p,vf2d pos={}){
|
|
return game->UseLoadoutItem(2);
|
|
};
|
|
game->GetPlayer()->SetItem3UseFunc(itemAbility);
|
|
Component<MenuItemItemButton>(MenuType::ITEM_LOADOUT,"Loadout Item 3")->SetItem(loadout[slot]);
|
|
}break;
|
|
}
|
|
|
|
//Set the loadout slot selection for this loadout item.
|
|
auto&itemsList=Component<InventoryScrollableWindowComponent>(INVENTORY_CONSUMABLES,"inventory")->GetComponents();
|
|
auto item=std::find_if(itemsList.begin(),itemsList.end(),[&](auto&component){
|
|
if(DYNAMIC_POINTER_CAST<MenuItemButton>(component)->GetItem().lock()->ActualName()==loadout[slot]->ActualName()){
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
if(item!=itemsList.end()){
|
|
DYNAMIC_POINTER_CAST<MenuItemButton>(*item)->selected=slot;
|
|
}else ERR(std::format("WARNING! Could not find item {} in inventory while selecting a loadout item from the consumables menu! THIS SHOULD NOT BE HAPPENING!",loadout[slot]->ActualName()));
|
|
|
|
}else{
|
|
ERR("Trying to set item "+itemName+" in Loadout slot "+std::to_string(slot)+" when said item does not exist in our inventory!");
|
|
}
|
|
}
|
|
|
|
bool AiL::UseLoadoutItem(int slot){
|
|
if(slot<0||slot>GetLoadoutSize()-1)ERR("Invalid inventory slot "+std::to_string(slot)+", please choose a slot in range (0-"+std::to_string(GetLoadoutSize()-1)+").");
|
|
if(!ISBLANK(GetLoadoutItem(slot).lock())&&GetLoadoutItem(slot).lock()->Amt()>0){
|
|
Tutorial::GetTask(TutorialTaskName::USE_RECOVERY_ITEMS).I(A::ITEM_USE_COUNT)++;
|
|
Inventory::UseItem(GetLoadoutItem(slot).lock()->ActualName());
|
|
Inventory::AddLoadoutItemUsed(GetLoadoutItem(slot).lock()->ActualName(),slot);
|
|
GetLoadoutItem(slot).lock()->amt--;
|
|
if(GetLoadoutItem(slot).lock()->UseSound().length()>0){
|
|
SoundEffect::PlaySFX(GetLoadoutItem(slot).lock()->UseSound(),SoundEffect::CENTERED);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void AiL::ClearLoadoutItem(int slot){
|
|
if(slot<0||slot>GetLoadoutSize()-1)ERR("Invalid inventory slot "+std::to_string(slot)+", please choose a slot in range (0-"+std::to_string(GetLoadoutSize()-1)+").");
|
|
loadout[slot].reset();
|
|
InputGroup*inputGroup=nullptr;
|
|
switch(slot){
|
|
case 0:{
|
|
inputGroup=&Player::KEY_ITEM1;
|
|
}break;
|
|
case 1:{
|
|
inputGroup=&Player::KEY_ITEM2;
|
|
}break;
|
|
case 2:{
|
|
inputGroup=&Player::KEY_ITEM3;
|
|
}break;
|
|
}
|
|
Ability itemAbility{"???","","",0,0,inputGroup,""};
|
|
switch(slot){
|
|
case 0:{
|
|
itemAbility.action=[&](Player*p,vf2d pos={}){
|
|
return game->UseLoadoutItem(0);
|
|
};
|
|
game->GetPlayer()->SetItem1UseFunc(itemAbility);
|
|
Component<MenuItemItemButton>(MenuType::ITEM_LOADOUT,"Loadout Item 1")->SetItem(Item::BLANK);
|
|
Component<MenuItemItemButton>(MenuType::ITEM_LOADOUT,"Loadout Item 1")->UpdateIcon();
|
|
}break;
|
|
case 1:{
|
|
itemAbility.action=[&](Player*p,vf2d pos={}){
|
|
return game->UseLoadoutItem(1);
|
|
};
|
|
game->GetPlayer()->SetItem2UseFunc(itemAbility);
|
|
Component<MenuItemItemButton>(MenuType::ITEM_LOADOUT,"Loadout Item 2")->SetItem(Item::BLANK);
|
|
Component<MenuItemItemButton>(MenuType::ITEM_LOADOUT,"Loadout Item 2")->UpdateIcon();
|
|
}break;
|
|
case 2:{
|
|
itemAbility.action=[&](Player*p,vf2d pos={}){
|
|
return game->UseLoadoutItem(2);
|
|
};
|
|
game->GetPlayer()->SetItem3UseFunc(itemAbility);
|
|
Component<MenuItemItemButton>(MenuType::ITEM_LOADOUT,"Loadout Item 3")->SetItem(Item::BLANK);
|
|
Component<MenuItemItemButton>(MenuType::ITEM_LOADOUT,"Loadout Item 3")->UpdateIcon();
|
|
}break;
|
|
}
|
|
}
|
|
|
|
void AiL::RenderFadeout(){
|
|
if(Audio::BGMFullyLoaded()){
|
|
game->DisableFadeIn(false);
|
|
}
|
|
uint8_t alpha=0;
|
|
if(fadeOutDuration>0){
|
|
fadeOutDuration=std::max(0.f,fadeOutDuration-GetElapsedTime());
|
|
if(fadeOutDuration==0){
|
|
SetMosaicEffect(1U);
|
|
GameState::_ChangeState(transitionState);
|
|
}else{
|
|
SetMosaicEffect(util::lerp(1U,mosaicEffectTransition,1-(fadeOutDuration/fadeOutTotalTime)));
|
|
alpha=uint8_t(util::lerp(0,255,1-(fadeOutDuration/fadeOutTotalTime)));
|
|
}
|
|
}else
|
|
if(fadeInDuration>0){
|
|
if(!disableFadeIn){
|
|
fadeInDuration=std::max(0.f,fadeInDuration-GetElapsedTime());
|
|
alpha=uint8_t(util::lerp(255,0,1-(fadeInDuration/fadeOutTotalTime)));
|
|
}else{
|
|
alpha=255;
|
|
}
|
|
}
|
|
FillRectDecal({0,0},GetScreenSize(),{0,0,0,alpha});
|
|
|
|
#ifdef _DEBUG
|
|
if("debug_transition_info"_I){
|
|
DrawShadowStringDecal({2,2},"Alpha: "+std::to_string(alpha));
|
|
DrawShadowStringDecal({2,14},"Disable Fade In: "+std::to_string(disableFadeIn));
|
|
DrawShadowStringDecal({2,26},"Fully Loaded: "+std::to_string(audioEngine.fullyLoaded));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool AiL::MenuClicksDeactivated()const{
|
|
return fadeOutDuration>0||disableFadeIn||LoadingScreen::loading;
|
|
}
|
|
|
|
bool AiL::GamePaused(){
|
|
return
|
|
fadeOutDuration>0
|
|
||disableFadeIn
|
|
||Menu::IsMenuOpen()&&Menu::stack.front()==Menu::menus[MenuType::PAUSE]/*The pause menu would be the only thing open and would be the menu in front.*/
|
|
||LoadingScreen::loading;
|
|
}
|
|
|
|
void AiL::EndGame(){
|
|
gameEnd=true;
|
|
}
|
|
|
|
#ifndef __EMSCRIPTEN__
|
|
::discord::Result AiL::SetupDiscord(){
|
|
auto result=::discord::Core::Create(1186719371750555780,DiscordCreateFlags_NoRequireDiscord,&Discord);
|
|
if(result==::discord::Result::Ok){
|
|
Discord->SetLogHook(
|
|
discord::LogLevel::Debug, [](discord::LogLevel level, const char* message) {
|
|
std::cerr << "Log(" << static_cast<uint32_t>(level) << "): " << message << "\n";
|
|
});
|
|
LOG("Connected to Discord!");
|
|
UpdateDiscordStatus("Main Menu",player.get()->GetClassName());
|
|
}else{
|
|
LOG("Could not connect to Discord. Error Code "<<int(result));
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
void AiL::UpdateDiscordStatus(std::string levelName,std::string className){
|
|
std::string originalClassName=className;
|
|
bool retry=false;
|
|
#ifndef __EMSCRIPTEN__
|
|
if(Discord){
|
|
::discord::Activity newActivity{};
|
|
newActivity.SetDetails(levelName.c_str());
|
|
discord::ActivityTimestamps×tamps=newActivity.GetTimestamps();
|
|
timestamps.SetStart(gameStarted);
|
|
newActivity.SetType(discord::ActivityType::Playing);
|
|
discord::ActivityAssets&assets=newActivity.GetAssets();
|
|
assets.SetLargeImage("ail_512");
|
|
assets.SetLargeText(game->sAppName.c_str());
|
|
|
|
if(levelName!="Main Menu"){
|
|
newActivity.SetState(std::format("Level {} {}",player->Level(),className).c_str());
|
|
assets.SetSmallText(std::format("{} the {}",SaveFile::GetSaveFileName(),className).c_str());
|
|
std::for_each(className.begin(),className.end(),[](char&c){c=std::tolower(c);});
|
|
assets.SetSmallImage(("nico-"+className+"_512").c_str());
|
|
}
|
|
Discord->ActivityManager().UpdateActivity(newActivity,[](::discord::Result result){
|
|
if(result==::discord::Result::Ok){
|
|
LOG("Discord Activity successfully updated!");
|
|
}else{
|
|
LOG("Could not update Discord Activity. Error Code "<<int(result));
|
|
}
|
|
});
|
|
}else{
|
|
if(SetupDiscord()==::discord::Result::Ok){
|
|
retry=true;
|
|
}
|
|
}
|
|
|
|
if(SteamFriends()!=nullptr){
|
|
if(levelName!="Main Menu"){
|
|
SteamFriends()->SetRichPresence("status",std::format("Level {} {} - Exploring {}",player->Level(),originalClassName,levelName.c_str()).c_str());
|
|
}else{
|
|
SteamFriends()->SetRichPresence("status","Main Menu");
|
|
}
|
|
SteamFriends()->SetRichPresence("steam_display","#Status");
|
|
}else{
|
|
if(steamAPIEnabled&&Steam_Init()){
|
|
retry=true;
|
|
LOG("Steam API Initialized successfully!");
|
|
}
|
|
}
|
|
if(retry){
|
|
UpdateDiscordStatus(levelName,className);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void AiL::InitializePlayerLevelCap(){
|
|
while(DATA["PlayerXP"].HasProperty(std::format("LEVEL[{}]",player->levelCap+1))){
|
|
player->levelCap++;
|
|
}
|
|
if(player->levelCap<=1)ERR("Could not detect level cap properly!")
|
|
LOG("Level cap detected as "<<int(player->levelCap));
|
|
}
|
|
|
|
void AiL::ResetGame(bool changeToMainMenu){
|
|
if(changeToMainMenu){
|
|
GameState::ChangeState(States::MAIN_MENU,0.5f);
|
|
}
|
|
for(int i=int(EquipSlot::HELMET);i<=int(EquipSlot::RING2);i<<=1){
|
|
Inventory::UnequipItem(EquipSlot(i));
|
|
}
|
|
for(auto&[cat,items]:ITEM_CATEGORIES){
|
|
Inventory::Clear(cat);
|
|
}
|
|
game->SetRuntime(0.0);
|
|
player->level=1;
|
|
player->stats.Reset();
|
|
player->ResetAccumulatedXP();
|
|
player->totalXPEarned=0;
|
|
player->SetMoney(100U);
|
|
player->Initialize();
|
|
for(int i=0;i<GetLoadoutSize();i++){
|
|
game->ClearLoadoutItem(i);
|
|
}
|
|
Unlock::Initialize();
|
|
State_OverworldMap::SetStageMarker("Player.Starting Location"_S);
|
|
State_OverworldMap::UpdateCurrentConnectionPoint(*State_OverworldMap::currentConnectionPoint);
|
|
State_OverworldMap::ResetConnectionPoints();
|
|
SetChapter(1);
|
|
SaveFile::SetSaveFileName("");
|
|
Tutorial::Initialize();
|
|
minimap.SetMinimapMode(MinimapMode::SMALL);
|
|
minimap.EraseChunkData();
|
|
}
|
|
|
|
void AiL::OnRequestCompleted(const std::string_view receivedData)const{
|
|
responseCallback(receivedData);
|
|
}
|
|
|
|
std::string operator ""_FS(const char*key,std::size_t len){
|
|
AiL::OutputDebugInfo(key,len);
|
|
return DATA.GetProperty(std::string(key,len)).GetFullString();
|
|
}
|
|
|
|
void AiL::DisableFadeIn(const bool disable){
|
|
disableFadeIn=disable;
|
|
}
|
|
|
|
void AiL::SetWorldColorFunc(std::function<Pixel(vi2d)>func){
|
|
worldColorFunc=func;
|
|
}
|
|
|
|
void AiL::SetWorldColor(Pixel worldCol){
|
|
worldColor=worldCol;
|
|
}
|
|
|
|
const Pixel&AiL::GetWorldColor()const{
|
|
return worldColor;
|
|
}
|
|
|
|
const std::map<std::string,std::vector<::ZoneData>>&AiL::GetZones(const std::string_view mapName)const{
|
|
if(GetCurrentMapDisplayName()==mapName)return GetZones();
|
|
return MAP_DATA.at(std::string(mapName)).ZoneData;
|
|
}
|
|
|
|
const std::map<std::string,std::vector<::ZoneData>>&AiL::GetZones()const{
|
|
return ZONE_LIST;
|
|
}
|
|
|
|
void AiL::AddZone(const std::string_view zoneName,const ZoneData&zone){
|
|
if(ZONE_LIST.count(std::string(zoneName))==0)ERR(std::format("WARNING! Trying to add non-existent Zone Key {} to zone list of map {}. THIS IS NOT ALLOWED!",zoneName,std::string(GetCurrentMapName())));
|
|
ZONE_LIST[std::string(zoneName)].push_back(zone);
|
|
}
|
|
|
|
const std::string_view AiL::GetCurrentMapDisplayName()const{
|
|
return GetCurrentMap().GetMapDisplayName();
|
|
}
|
|
|
|
const uint8_t AiL::BossEncounterMobCount()const{
|
|
return totalBossEncounterMobs;
|
|
}
|
|
|
|
void AiL::GetAnyKeyRelease(Key key){
|
|
if(key!=0){
|
|
#pragma region New keyboard input binding listener
|
|
using A=Attribute;
|
|
if(Menu::IsMenuOpen()&&Menu::stack.back()==Menu::menus[NEW_INPUT]){
|
|
if(Menu::menus[NEW_INPUT]->B(A::IS_KEYBOARD)){//We are requesting a brand new input.
|
|
using A=Attribute;
|
|
if(InputGroup::menuNamesToInputGroups.count(Menu::menus[NEW_INPUT]->S(A::KEYBIND))){
|
|
InputGroup::menuNamesToInputGroups[Menu::menus[NEW_INPUT]->S(A::KEYBIND)]->SetNewPrimaryKeybind(Input{KEY,key});
|
|
Menu::alreadyClicked=true;
|
|
Menu::CloseMenu();
|
|
}
|
|
}else{
|
|
Menu::CloseMenu();
|
|
}
|
|
}
|
|
#pragma endregion
|
|
|
|
GameState::STATE->GetAnyKeyRelease(key);
|
|
}
|
|
}
|
|
|
|
void AiL::GetAnyKeyPress(Key key){
|
|
if(key!=0){
|
|
GameState::STATE->GetAnyKeyPress(key);
|
|
}
|
|
}
|
|
|
|
void AiL::GetAnyMouseRelease(int32_t mouseButton){
|
|
#pragma region New mouse input binding listener
|
|
using A=Attribute;
|
|
if(Menu::IsMenuOpen()&&Menu::stack.back()==Menu::menus[NEW_INPUT]){
|
|
|
|
if(Menu::menus[NEW_INPUT]->B(A::IS_KEYBOARD)){//We are requesting a brand new input.
|
|
using A=Attribute;
|
|
const std::vector<std::string>*inputGroup=&InputGroup::menuInputGroups; //First we detect which input group this mouse binding is for. Each group can individually have one key bound to each mouse button, that's why we first detect which group we actually care about.
|
|
for(const std::string&groupName:InputGroup::gameplayInputGroups){
|
|
if(groupName==Menu::menus[NEW_INPUT]->S(A::KEYBIND)){
|
|
inputGroup=&InputGroup::gameplayInputGroups;
|
|
}
|
|
}
|
|
|
|
//Next we need to iterate through that group and check for any already-bound buttons for the mouse button we are about to apply. Get rid of the keybinds from there.
|
|
for(const std::string&menuItem:*inputGroup){
|
|
InputGroup&group=*InputGroup::menuNamesToInputGroups[menuItem];
|
|
std::vector<Input>keybindsToRemove;
|
|
for(Input&input:group.keyOrder){
|
|
if(input.GetType()==MOUSE&&input.GetKeyCode()==mouseButton){
|
|
keybindsToRemove.push_back(input);
|
|
}
|
|
}
|
|
for(Input&input:keybindsToRemove){
|
|
group.RemoveKeybind(input);
|
|
}
|
|
}
|
|
|
|
if(InputGroup::menuNamesToInputGroups.count(Menu::menus[NEW_INPUT]->S(A::KEYBIND))){
|
|
InputGroup::menuNamesToInputGroups[Menu::menus[NEW_INPUT]->S(A::KEYBIND)]->AddKeybind(Input{MOUSE,mouseButton});
|
|
Menu::alreadyClicked=true;
|
|
Menu::CloseMenu();
|
|
}
|
|
}else{
|
|
Menu::CloseMenu();
|
|
}
|
|
}
|
|
#pragma endregion
|
|
|
|
GameState::STATE->GetAnyMouseRelease(mouseButton);
|
|
lastMouseMovement=0.f;
|
|
}
|
|
|
|
void AiL::GetAnyMousePress(int32_t mouseButton){
|
|
GameState::STATE->GetAnyMousePress(mouseButton);
|
|
lastMouseMovement=0.f;
|
|
}
|
|
|
|
void AiL::GetAnyMouseHeld(int32_t mouseButton){
|
|
lastMouseMovement=0.f;
|
|
}
|
|
|
|
const float AiL::LastMouseMovement()const{
|
|
return lastMouseMovement;
|
|
}
|
|
|
|
const bool AiL::GameInitialized()const {
|
|
return gameInitialized;
|
|
}
|
|
|
|
rcode AiL::LoadResource(Renderable&renderable,std::string_view imgPath,bool filter,bool clamp){
|
|
rcode returnCode;
|
|
if(gamepack.Loaded()){
|
|
returnCode=renderable.Load(std::string(imgPath),&gamepack,filter,clamp);
|
|
}else{
|
|
returnCode=renderable.Load(std::string(imgPath),nullptr,filter,clamp);
|
|
if("GENERATE_GAMEPACK"_B){
|
|
gamepack.AddFile(std::string(imgPath));
|
|
}
|
|
}
|
|
return returnCode;
|
|
}
|
|
|
|
void AiL::UpdateMonsters(){
|
|
for(std::shared_ptr<Monster>&m:MONSTER_LIST){
|
|
if(m->markedForDeletion){
|
|
AMonsterIsMarkedForDeletion();
|
|
continue;
|
|
}
|
|
m->Update(game->GetElapsedTime());
|
|
}
|
|
for(Monster&m:game->monstersToBeSpawned){
|
|
size_t prevCapacity=MONSTER_LIST.capacity();
|
|
std::shared_ptr<Monster>newMonster{std::make_shared<Monster>(m)};
|
|
newMonster->weakPtr=newMonster;
|
|
MONSTER_LIST.emplace_back(std::move(newMonster));
|
|
if(MONSTER_LIST.capacity()>prevCapacity)LOG(std::format("WARNING! The monster list has automatically reserved more space and resized to {}! This caused one potential frame where bullet/effect hitlists that stored information on what monsters were hit to potentially be hit a second time or cause monsters that should've been hit to never be hit. Consider starting with a larger default reserved size for MONSTER_LIST if your intention was to have this many monsters!",MONSTER_LIST.capacity()));
|
|
}
|
|
if(aMonsterIsMarkedForDeletion)std::erase_if(MONSTER_LIST,[&](const std::shared_ptr<Monster>&m){return m->markedForDeletion;});
|
|
aMonsterIsMarkedForDeletion=false;
|
|
game->monstersToBeSpawned.clear();
|
|
}
|
|
|
|
int AiL::GetLoadoutSize()const{
|
|
return loadout.size();
|
|
}
|
|
|
|
void AiL::ActivateActionSetForAllControllers(InputActionSetHandle_t actionSetHandle){
|
|
STEAMINPUT(
|
|
for(int i=0;i<Input::controllerCount;i++){
|
|
SteamInput()->ActivateActionSet(Input::steamControllers[i],actionSetHandle);
|
|
}
|
|
)
|
|
}
|
|
|
|
const float AiL::GetEncounterDuration()const{
|
|
return encounterDuration;
|
|
}
|
|
|
|
void AiL::ShowDamageVignetteOverlay(){
|
|
vignetteDisplayTime="Interface.Vignette Appearance Time"_F+"Interface.Vignette Fadeout Time"_F;
|
|
}
|
|
|
|
void AiL::GlobalGameUpdates(){
|
|
bossIndicatorPos={};
|
|
|
|
levelTime+=GetElapsedTime();
|
|
SteamAPI_RunCallbacks();
|
|
STEAMINPUT(
|
|
ActivateActionSetForAllControllers(Input::ingameControlsHandle);
|
|
Input::UpdateSteamInput();
|
|
)
|
|
|
|
#pragma region Damage Numbers update
|
|
for(std::vector<std::shared_ptr<DamageNumber>>::iterator it=DAMAGENUMBER_LIST.begin();it!=DAMAGENUMBER_LIST.end();++it){
|
|
DamageNumber*dn=(*it).get();
|
|
if(dn->pauseTime>0){
|
|
dn->pauseTime-=GetElapsedTime();
|
|
} else{
|
|
dn->lifeTime+=GetElapsedTime();
|
|
if(dn->lifeTime<=1){
|
|
if(dn->lifeTime<DamageNumber::MOVE_UP_TIME){
|
|
if(dn->invertedDirection){
|
|
dn->pos.y+=dn->riseSpd*GetElapsedTime();
|
|
}else{
|
|
dn->pos.y-=dn->riseSpd*GetElapsedTime();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#pragma endregion
|
|
|
|
#pragma region Marked Targets Update
|
|
if(lockOnTargets.size()>0){
|
|
lastLockOnTargetTime=std::max(0.f,lastLockOnTargetTime-GetElapsedTime());
|
|
if(lastLockOnTargetTime<=0.f){
|
|
const auto&[monster,stackCount,time]=lockOnTargets.front();
|
|
if(!monster.expired()){
|
|
monster.lock()->AddBuff(BuffType::TRAPPER_MARK,time,stackCount);
|
|
SoundEffect::PlaySFX("Lock On",monster.lock()->GetPos());
|
|
lastLockOnTargetTime=0.2f;
|
|
}
|
|
lockOnTargets.erase(lockOnTargets.begin());
|
|
}
|
|
}
|
|
#pragma endregion
|
|
|
|
if(GetMousePos()!=lastMousePos){
|
|
lastMouseMovement=0.f;
|
|
lastMousePos=GetMousePos();
|
|
}else lastMouseMovement+=GetElapsedTime();
|
|
|
|
vignetteDisplayTime=std::max(0.f,vignetteDisplayTime-GetElapsedTime());
|
|
|
|
if(Audio::Engine().IsPlaying(GetPlayer()->cooldownSoundInstance)){
|
|
Audio::Engine().SetVolume(GetPlayer()->cooldownSoundInstance,Audio::GetCalculatedSFXVolume("Audio.Casting Sound Volume"_F/100.f));
|
|
}
|
|
|
|
if(!GamePaused()){
|
|
GameState::STATE->OnUserUpdate(this);
|
|
}else{
|
|
ClearTimedOutGarbage();
|
|
}
|
|
}
|
|
|
|
|
|
const bool AiL::QuitRequested()const{
|
|
return gameEnd;
|
|
}
|
|
void AiL::SetQuitAllowed(bool quittingAllowed){
|
|
savingFile=!quittingAllowed;
|
|
}
|
|
|
|
|
|
const bool AiL::PreviousStageCompleted()const{
|
|
return prevStageCompleted;
|
|
}
|
|
void AiL::SetCompletedStageFlag(){
|
|
prevStageCompleted=true;
|
|
}
|
|
void AiL::ResetCompletedStageFlag(){
|
|
prevStageCompleted=false;
|
|
}
|
|
void AiL::ComputeModeColors(TilesetData&tileset){
|
|
for(int y=0;y<tileset.tileset->Sprite()->height/tileset.tileheight;y++){
|
|
for(int x=0;x<tileset.tileset->Sprite()->width/tileset.tilewidth;x++){
|
|
#pragma region Individual tile iteration
|
|
std::unordered_map<uint32_t,int>pixelCounts;
|
|
Pixel modeCol=BLANK;
|
|
int maxCount=0;
|
|
vi2d pixelOffset=vi2d{x,y}*tileset.tilewidth;
|
|
for(int pixelY=0;pixelY<tileset.tileheight;pixelY++){
|
|
for(int pixelX=0;pixelX<tileset.tilewidth;pixelX++){
|
|
vi2d targetPixel=vi2d{pixelX,pixelY}+pixelOffset;
|
|
Pixel tileCol=tileset.tileset->Sprite()->GetPixel(targetPixel);
|
|
pixelCounts[tileCol.n]++;
|
|
if(pixelCounts[tileCol.n]>maxCount){
|
|
modeCol=tileCol;
|
|
pixelCounts[tileCol.n]=maxCount;
|
|
}
|
|
}
|
|
}
|
|
tileset.tilecols.push_back(modeCol);
|
|
#pragma endregion
|
|
}
|
|
}
|
|
}
|
|
|
|
void AiL::UpdateEntities(){
|
|
UpdateEffects(GetElapsedTime());
|
|
GetPlayer()->Update(GetElapsedTime());
|
|
UpdateMonsters();
|
|
|
|
ItemDrop::UpdateDrops(GetElapsedTime());
|
|
UpdateBullets(GetElapsedTime());
|
|
}
|
|
|
|
void AiL::AMonsterIsMarkedForDeletion(){
|
|
aMonsterIsMarkedForDeletion=true;
|
|
}
|
|
|
|
void AiL::SetBossIndicatorPos(const vf2d pos){
|
|
bossIndicatorPos=pos;
|
|
}
|
|
|
|
Overlay&AiL::GetOverlay(){
|
|
return hudOverlay;
|
|
}
|
|
|
|
void AiL::SetOverlay(std::string animationName,Pixel overlayCol){
|
|
hudOverlay=Overlay{animationName,overlayCol};
|
|
}
|
|
|
|
void AiL::SetWindSpeed(vf2d newWindSpd){
|
|
windSpd=newWindSpd;
|
|
}
|
|
const vf2d&AiL::GetWindSpeed()const{
|
|
return windSpd;
|
|
}
|
|
|
|
void AiL::UsingSteamAPI(const bool usingSteam){
|
|
steamAPIEnabled=usingSteam;
|
|
}
|
|
|
|
void AiL::InitializePlayer(){
|
|
player=std::make_unique<Warrior>();
|
|
}
|
|
|
|
void AiL::SetWorldZoom(float newZoomScale){
|
|
targetZoom=newZoomScale;
|
|
}
|
|
|
|
void AiL::PlayFootstepSound(){
|
|
bool inWater=true;
|
|
|
|
for(const LayerTag&layer:GetCurrentMap().LayerData){
|
|
int tileID=layer.tiles[player->GetY()/24][player->GetX()/24]-1;
|
|
if(tileID!=-1&&!IsReflectiveTile(GetTileSheet(GetCurrentLevel(),tileID),tileID)){
|
|
inWater=false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(inWater){
|
|
SoundEffect::PlaySFX("Footstep - Wet",SoundEffect::CENTERED);
|
|
}else{
|
|
SoundEffect::PlaySFX("Footstep",SoundEffect::CENTERED);
|
|
}
|
|
}
|
|
|
|
const std::map<std::string,TilesetData>&AiL::GetTilesets()const{
|
|
return MAP_TILESETS;
|
|
}
|
|
|
|
void AiL::AddToMarkedTargetList(std::tuple<std::weak_ptr<Monster>,StackCount,MarkTime>markData){
|
|
lockOnTargets.emplace_back(markData);
|
|
} |