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.
 
 
 
 
 
 
AdventuresInLestoria/Adventures in Lestoria/AdventuresInLestoria.cpp

2741 lines
104 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 © 2023 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"
#ifndef __EMSCRIPTEN__
#include "discord.h"
#endif
INCLUDE_EMITTER_LIST
INCLUDE_ITEM_CATEGORIES
INCLUDE_BACKDROP_DATA
bool _DEBUG_MAP_LOAD_INFO = false;
//360x240
vi2d WINDOW_SIZE={24*15,24*10};
safemap<std::string,Animate2D::FrameSequence>ANIMATION_DATA;
std::vector<Monster>MONSTER_LIST;
std::vector<MonsterSpawner>SPAWNER_LIST;
std::vector<std::shared_ptr<DamageNumber>>DAMAGENUMBER_LIST;
std::vector<std::unique_ptr<Bullet>>BULLET_LIST;
safemap<std::string,Renderable>GFX;
utils::datafile DATA;
AiL*game;
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;
#ifndef __EMSCRIPTEN__
::discord::Core*Discord{};
#endif
float AiL::SIZE_CHANGE_SPEED=1;
AiL::AiL()
{
utils::datafile::Read(DATA,"assets/config/configuration.txt");
_DEBUG_MAP_LOAD_INFO=bool("debug_map_load_info"_I);
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 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);
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){
std::cout<<cl<<std::endl;
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 ENVIRONMENTAL_AUDIO_CONFIG = CONFIG_PATH + "environmental_audio_config"_S;
utils::datafile::Read(DATA,ENVIRONMENTAL_AUDIO_CONFIG);
utils::datafile::DEBUG_ACCESS_OPTIONS="debug_access_options"_I;
sAppName = "GAME_NAME"_S;
game=this;
gameStarted=time(NULL);
}
bool AiL::OnUserCreate(){
Font::init();
InitializeDefaultKeybinds();
VisualNovel::Initialize();
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);
ItemAttribute::Initialize();
ItemInfo::InitializeItems();
player=std::make_unique<Warrior>();
InitializePlayerLevelCap();
InitializeGraphics();
InitializeClasses();
Monster::InitializeStrategies();
//Animations
sig::Animation::InitializeAnimations();
MonsterData::InitializeMonsterData();
sig::Animation::SetupPlayerAnimations();
view=TileTransformedView{GetScreenSize(),{1,1}};
Menu::InitializeMenus();
Inventory::AddItem("Minor Health Potion",16);
Inventory::AddItem("Bandages",10);
Inventory::AddItem("Green Slime Remains",40);
Inventory::AddItem("Blue Slime Remains",22);
Inventory::AddItem("Copper Armor");
Inventory::AddItem("Copper Pants");
Inventory::AddItem("Copper Helmet");
Inventory::AddItem("Copper Shoes");
Inventory::AddItem("Shell Helmet");
Inventory::AddItem("Shell Armor");
Inventory::AddItem("Shell Armor");
Inventory::AddItem("Shell Armor");
Inventory::AddItem("Bone Armor");
Inventory::AddItem("Shell Gloves");
Inventory::AddItem("Shell Gloves",3);
Inventory::AddItem("Shell Shoes");
Inventory::AddItem("Bone Pants");
Inventory::AddItem("Bone Gloves");
Inventory::AddItem("Elixir of Bear Strength",3);
Inventory::AddItem("Leather Helmet");
Inventory::AddItem("Leather Pants");
Inventory::AddItem("Leather Gloves");
Inventory::AddItem("Leather Shoes");
Inventory::AddItem("Wooden Sword");
Inventory::AddItem("Laser Sword");
Inventory::AddItem("Shell Sword");
Inventory::AddItem("Ring of the Slime King",3);
Audio::Initialize();
EnvironmentalAudio::Initialize();
SoundEffect::Initialize();
LoadLevel("starting_map"_S);
ChangePlayerClass(WARRIOR);
GameState::Initialize();
Unlock::Initialize();
ItemDrop::Initialize();
Merchant::Initialize();
TitleScreen::Initialize();
Stats::InitializeDamageReductionTable();
utils::datafile::INITIAL_SETUP_COMPLETE=true;
ValidateGameStatus(); //Checks to make sure everything has been initialized properly.
#ifndef __EMSCRIPTEN__
SetupDiscord();
#endif
return true;
}
bool AiL::OnUserUpdate(float fElapsedTime){
levelTime+=fElapsedTime;
if(!GamePaused()){
GameState::STATE->OnUserUpdate(this);
}
Audio::Update();
RenderWorld(GetElapsedTime());
GameState::STATE->Draw(this);
RenderMenu();
RenderFadeout();
RenderVersionInfo();
#ifndef __EMSCRIPTEN__
if(Discord){
auto result=Discord->RunCallbacks();
if(result!=::discord::Result::Ok){
std::cout<<"Discord Error Code "<<int(result)<<std::endl;
delete Discord;
Discord=nullptr;
}
}
#endif
return !gameEnd;
}
bool AiL::LeftHeld(){
return KEY_LEFT.Held();
}
bool AiL::RightHeld(){
return KEY_RIGHT.Held();
}
bool AiL::UpHeld(){
return KEY_UP.Held();
}
bool AiL::DownHeld(){
return KEY_DOWN.Held();
}
bool AiL::LeftReleased(){
return KEY_LEFT.Released();
}
bool AiL::RightReleased(){
return KEY_RIGHT.Released();
}
bool AiL::UpReleased(){
return KEY_UP.Released();
}
bool AiL::DownReleased(){
return KEY_DOWN.Released();
}
void AiL::HandleUserInput(float fElapsedTime){
if(!Menu::stack.empty())return; //A window being opened means there's no user input allowed.
bool setIdleAnimation=true;
bool heldDownMovementKey=false; //Is true when a movement key has been held down.
if(GetKey(F1).bPressed){
ConsoleShow(F1);
}
if(GetMouseWheel()>0){
switch(player->GetClass()){
case WARRIOR:{
ChangePlayerClass(RANGER);
}break;
case RANGER:{
ChangePlayerClass(WIZARD);
}break;
case WIZARD:{
ChangePlayerClass(WARRIOR);
}break;
}
}
if(GetMouseWheel()<0){
switch(player->GetClass()){
case WARRIOR:{
ChangePlayerClass(WIZARD);
}break;
case RANGER:{
ChangePlayerClass(WARRIOR);
}break;
case WIZARD:{
ChangePlayerClass(RANGER);
}break;
}
}
if(player->GetVelocity()==vf2d{0,0}&&player->CanMove()){
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);
if (dat.tileset->staircaseTiles.find(tileID)!=dat.tileset->staircaseTiles.end()){
return dat.tileset->staircaseTiles[tileID].data["value"];
}
}
return std::string("NONE");
};
std::string staircaseDirection=GetPlayerStaircaseDirection();
if(RightHeld()){
player->SetX(player->GetX()+fElapsedTime*"Player.MoveSpd"_F*player->GetMoveSpdMult());
player->movementVelocity.x="Player.MoveSpd"_F;
if(staircaseDirection=="RIGHT"){
player->SetY(player->GetY()-"Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult());
player->movementVelocity.y=-"Player.StaircaseClimbSpd"_F;
} else
if(staircaseDirection=="LEFT"){
player->SetY(player->GetY()+"Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult());
player->movementVelocity.y="Player.StaircaseClimbSpd"_F;
}
player->SetFacingDirection(RIGHT);
if(player->GetState()==State::NORMAL||player->GetState()==State::PREP_CAST){
player->UpdateWalkingAnimation(RIGHT);
}
setIdleAnimation=false;
heldDownMovementKey=true;
}
if(LeftHeld()){
player->SetX(player->GetX()-fElapsedTime*"Player.MoveSpd"_F*player->GetMoveSpdMult());
player->movementVelocity.x=-"Player.MoveSpd"_F;
if(staircaseDirection=="RIGHT"){
player->SetY(player->GetY()+"Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult());
player->movementVelocity.y="Player.StaircaseClimbSpd"_F;
} else
if(staircaseDirection=="LEFT"){
player->SetY(player->GetY()-"Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult());
player->movementVelocity.y=-"Player.StaircaseClimbSpd"_F;
}
if(setIdleAnimation){
player->SetFacingDirection(LEFT);
if(player->GetState()==State::NORMAL||player->GetState()==State::PREP_CAST){
player->UpdateWalkingAnimation(LEFT);
}
}
setIdleAnimation=false;
heldDownMovementKey=true;
}
if(UpHeld()){
player->SetY(player->GetY()-fElapsedTime*"Player.MoveSpd"_F*player->GetMoveSpdMult());
player->movementVelocity.y=-"Player.MoveSpd"_F*fElapsedTime;
if(setIdleAnimation){
player->SetFacingDirection(UP);
if(player->GetState()==State::NORMAL||player->GetState()==State::PREP_CAST){
player->UpdateWalkingAnimation(UP);
}
}
setIdleAnimation=false;
heldDownMovementKey=true;
}
if(DownHeld()){
player->SetY(player->GetY()+fElapsedTime*"Player.MoveSpd"_F*player->GetMoveSpdMult());
player->movementVelocity.y="Player.MoveSpd"_F*fElapsedTime;
if(setIdleAnimation){
player->SetFacingDirection(DOWN);
if(player->GetState()==State::NORMAL||player->GetState()==State::PREP_CAST){
player->UpdateWalkingAnimation(DOWN);
}
}
setIdleAnimation=false;
heldDownMovementKey=true;
}
}
if(UpReleased()){
player->SetLastReleasedMovementKey(UP);
player->movementVelocity.y=0;
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()){
player->SetLastReleasedMovementKey(RIGHT);
player->movementVelocity.x=0;
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()){
player->SetLastReleasedMovementKey(LEFT);
player->movementVelocity.x=0;
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()){
player->SetLastReleasedMovementKey(DOWN);
player->movementVelocity.y=0;
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(heldDownMovementKey){
player->footstepTimer+=GetElapsedTime();
if(player->footstepTimer>"Player.Footstep Timer"_F){
player->footstepTimer-="Player.Footstep Timer"_F;
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);
}
}
}
if(setIdleAnimation){
switch(player->GetLastReleasedMovementKey()){
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);
}
}
}
if(GetKey(I).bPressed){
Menu::OpenMenu(INVENTORY_CONSUMABLES);
}
if(GetKey(O).bPressed){
ItemDrop::SpawnItem(&ITEM_DATA.at("Green Slime Remains"),player->GetPos(),player->OnUpperLevel());
}
if(GetKey(L).bHeld){
game->GetPlayer()->AddXP(1);
}
}
void AiL::UpdateCamera(float fElapsedTime){
lastWorldShakeAdjust=std::max(0.f,lastWorldShakeAdjust-fElapsedTime);
if(worldShakeTime>0){
worldShakeTime-=fElapsedTime;
if(worldShakeTime<=0){
camera.SetTarget(player->GetPos());
}
if(lastWorldShakeAdjust==0){
lastWorldShakeAdjust=0.02f;
worldShakeVel.x*=-1;
worldShakeVel.y*=-1;
}
worldShake=player->GetPos()+worldShakeVel;
}
camera.Update(fElapsedTime);
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){
Bullet*b=(*it).get();
b->UpdateFadeTime(fElapsedTime);
b->Update(fElapsedTime);
b->animation.UpdateState(b->internal_animState,fElapsedTime);
if(!b->deactivated){
float totalDistance=(b->vel*fElapsedTime).mag();
int iterations=int(std::max(1.f,(b->vel*fElapsedTime).mag()));
int totalIterations=iterations;
vf2d finalBulletPos=b->pos+b->vel*fElapsedTime;
const auto CollisionCheck=[&](){
if(b->friendly){
for(Monster&m:MONSTER_LIST){
if(geom2d::overlaps(geom2d::circle(m.GetPos(),12*m.GetSizeMult()),geom2d::circle(b->pos,b->radius))){
if(b->hitList.find(&m)==b->hitList.end()&&m.Hurt(b->damage,b->OnUpperLevel(),0)){
if(!b->hitsMultiple){
if(b->MonsterHit(m)){
b->dead=true;
return false;
}
}
b->hitList[&m]=true;
}
}
}
} else {
if(geom2d::overlaps(geom2d::circle(player->GetPos(),12*player->GetSizeMult()/2),geom2d::circle(b->pos,b->radius))){
if(player->Hurt(b->damage,b->OnUpperLevel(),0)){
if(b->PlayerHit(player.get())){
b->dead=true;
return false;
}
}
}
}
return true;
};
while(iterations>0){
iterations--;
b->pos+=(b->vel*fElapsedTime)/float(totalIterations);
if(!CollisionCheck()){
goto nextBullet;
}
}
b->pos=finalBulletPos;
if(!CollisionCheck()){
goto nextBullet;
}
}else{
b->pos+=b->vel*fElapsedTime;
}
if(b->pos.x+b->radius<view.GetWorldTL().x-WINDOW_SIZE.x||b->pos.x-b->radius>view.GetWorldBR().x+WINDOW_SIZE.x||b->pos.y+b->radius<view.GetWorldTL().y-WINDOW_SIZE.y||b->pos.y-b->radius>view.GetWorldBR().y+WINDOW_SIZE.y){
b->dead=true;
continue;
}
b->lifetime-=fElapsedTime;
if(b->lifetime<=0){
b->dead=true;
continue;
}
nextBullet:
while(false);
}
std::erase_if(BULLET_LIST,[](std::unique_ptr<Bullet>&b){return b->dead;});
}
void AiL::HurtEnemies(vf2d pos,float radius,int damage,bool upperLevel,float z){
for(Monster&m:MONSTER_LIST){
if(geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m.GetPos(),12*m.GetSizeMult()))){
m.Hurt(damage,upperLevel,z);
}
}
}
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();
tilePreparationList.clear();
tileForegroundList.clear();
Player*pl=GetPlayer();
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){
Bullet*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(ZoneData&zone:MAP_DATA[GetCurrentLevel()].ZoneData["EndZone"]){
if(zone.isUpper){
upperEndZones.push_back(zone);
}else{
endZones.push_back(zone);
}
}
std::sort(monstersBeforeUpper.begin(),monstersBeforeUpper.end(),[](Monster*m1,Monster*m2){return m1->GetPos().y<m2->GetPos().y;});
std::sort(monstersBeforeLower.begin(),monstersBeforeLower.end(),[](Monster*m1,Monster*m2){return m1->GetPos().y<m2->GetPos().y;});
std::sort(monstersAfterUpper.begin(),monstersAfterUpper.end(),[](Monster*m1,Monster*m2){return m1->GetPos().y<m2->GetPos().y;});
std::sort(monstersAfterLower.begin(),monstersAfterLower.end(),[](Monster*m1,Monster*m2){return m1->GetPos().y<m2->GetPos().y;});
}
void AiL::RenderTile(vi2d pos,TilesheetData tileSheet,int tileSheetIndex,vi2d tileSheetPos){
if(tileSheet.tileset->animationData.count(tileSheetIndex)){
int animationDuration_ms=int(tileSheet.tileset->animationData[tileSheetIndex].size()*"animation_tile_precision"_I);
int animatedIndex=tileSheet.tileset->animationData[tileSheetIndex][size_t(fmod(levelTime*1000.f,animationDuration_ms)/"animation_tile_precision"_I)];
int tileSheetWidth=tileSheet.tileset->tileset->Sprite()->width/tileSheet.tileset->tilewidth;
int tileSheetX=animatedIndex%tileSheetWidth;
int tileSheetY=animatedIndex/tileSheetWidth;
view.DrawPartialDecal(pos*game->GetCurrentMapData().tilewidth,{float(tileSheet.tileset->tilewidth),float(tileSheet.tileset->tileheight)},tileSheet.tileset->tileset->Decal(),vi2d{tileSheetX,tileSheetY}*tileSheet.tileset->tilewidth,{float(tileSheet.tileset->tilewidth),float(tileSheet.tileset->tileheight)});
}else{
view.DrawPartialDecal(pos*game->GetCurrentMapData().tilewidth,{float(tileSheet.tileset->tilewidth),float(tileSheet.tileset->tileheight)},tileSheet.tileset->tileset->Decal(),tileSheetPos*tileSheet.tileset->tilewidth,{float(tileSheet.tileset->tilewidth),float(tileSheet.tileset->tileheight)});
}
}
void AiL::RenderTile(TileRenderData&tileSheet,Pixel col){
if(tileSheet.tileSheet.tileset->animationData.count(tileSheet.tileID%1000000)){
int animationDuration_ms=int(tileSheet.tileSheet.tileset->animationData[tileSheet.tileID%1000000].size()*"animation_tile_precision"_I);
int animatedIndex=tileSheet.tileSheet.tileset->animationData[tileSheet.tileID%1000000][size_t(fmod(levelTime*1000.f,animationDuration_ms)/"animation_tile_precision"_I)];
int tileSheetWidth=tileSheet.tileSheet.tileset->tileset->Sprite()->width/tileSheet.tileSheet.tileset->tilewidth;
int tileSheetX=animatedIndex%tileSheetWidth;
int tileSheetY=animatedIndex/tileSheetWidth;
view.DrawPartialDecal(tileSheet.pos,{float(tileSheet.tileSheet.tileset->tilewidth),float(tileSheet.tileSheet.tileset->tileheight)},tileSheet.tileSheet.tileset->tileset->Decal(),vi2d{tileSheetX,tileSheetY},{float(tileSheet.tileSheet.tileset->tilewidth),float(tileSheet.tileSheet.tileset->tileheight)},col);
}else{
view.DrawPartialDecal(tileSheet.pos,{float(tileSheet.tileSheet.tileset->tilewidth),float(tileSheet.tileSheet.tileset->tileheight)},tileSheet.tileSheet.tileset->tileset->Decal(),tileSheet.tileSheetPos,{float(tileSheet.tileSheet.tileset->tilewidth),float(tileSheet.tileSheet.tileset->tileheight)},col);
}
}
void AiL::RenderWorld(float fElapsedTime){
LayerTag*bridgeLayer=nullptr;
bool bridgeLayerFade=false;
Player*pl=GetPlayer();
PopulateRenderLists();
auto RenderPlayer=[&](vf2d pos,vf2d scale){
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 %"});
view.DrawPartialRotatedDecal(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,attackBuffs.size()>0?Pixel{255,uint8_t(255*abs(sin(1.4*attackBuffs[0].duration))),uint8_t(255*abs(sin(1.4*attackBuffs[0].duration)))}:WHITE);
SetDecalMode(DecalMode::NORMAL);
if(player->GetState()==State::BLOCK){
view.DrawDecal(player->GetPos()-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
if(GetCurrentMap().backdrop.length()>0){
DrawPartialDecal({0,0},WINDOW_SIZE,BACKDROP_DATA[GetCurrentMap().backdrop].Decal(),"Backdrop Config.Speed Ratio"_F*-camera.GetPosition()+view.GetWorldOffset(),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(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(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=tileSheet.tileset->tileset->Sprite()->width/tileSheet.tileset->tilewidth;
int tileSheetHeight=tileSheet.tileset->tileset->Sprite()->height/tileSheet.tileset->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});
#ifdef _DEBUG
if("debug_collision_boxes"_I){
if(tileSheet.tileset->collision.find(tileSheetIndex)!=tileSheet.tileset->collision.end()){
geom2d::rect<float>collision=tileSheet.tileset->collision[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::REFLECTIVE_TILES:{
visibleTiles.insert({x,y});
for(LayerTag&layer:MAP_DATA[currentLevel].LayerData){
int tileID=layer.tiles[y][x]-1;
if(tileID!=-1){
TilesheetData tileSheet=GetTileSheet(currentLevel,tileID);
int tileSheetWidth=tileSheet.tileset->tileset->Sprite()->width/tileSheet.tileset->tilewidth;
int tileSheetHeight=tileSheet.tileset->tileset->Sprite()->height/tileSheet.tileset->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(tileSheet.tileset->collision.find(tileSheetIndex)!=tileSheet.tileset->collision.end()){
geom2d::rect<float>collision=tileSheet.tileset->collision[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(),"Backdrop Config.Speed Ratio"_F*-camera.GetPosition()+tileWorldPos,{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;
}
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)};
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});
}
}
}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(),"Backdrop Config.Speed Ratio"_F*-camera.GetPosition()+tileWorldPos,{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
//DrawDecal({0,0},MAP_TILESETS["assets/maps/"+MAP_DATA[LEVEL1].TilesetData[1].data["source"]]->Decal());
for(Monster&m:MONSTER_LIST){
m.strategyDraw(this);
}
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);
}
for(Effect*e:backgroundEffectsLower){
e->rendered=false;
}
for(ZoneData&zone:endZones){
RenderZone(zone.zone);
}
for(int index:dropsBeforeLower){
ItemDrop::drops[index].Draw();
}
for(Monster*m:monstersBeforeLower){
m->Draw();
}
if(!player->upperLevel){
RenderPlayer(player->GetPos(),{1,1});
}
for(int index:dropsAfterLower){
ItemDrop::drops[index].Draw();
}
for(Monster*m:monstersAfterLower){
m->Draw();
}
for(Bullet*b:bulletsLower){
b->rendered=false;
}
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=GetWorldMousePos()-vf2d{GFX["circle.png"].Sprite()->width*scale.x/2,GFX["circle.png"].Sprite()->height*scale.y/2};
float distance=float(sqrt(pow(player->GetX()-GetWorldMousePos().x,2)+pow(player->GetY()-GetWorldMousePos().y,2)));
if(distance>precastRange){//Clamp the distance.
vf2d pointToCursor = {GetWorldMousePos().x-player->GetX(),GetWorldMousePos().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)){
group.playerBehind=true;
group.fadeFactor=std::min(group.fadeFactor+fElapsedTime,TileGroup::FADE_TIME);
} else {
group.playerBehind=false;
group.fadeFactor=std::max(group.fadeFactor-fElapsedTime,0.f);
}
for(TileRenderData&tile:group.GetTiles()){
tile.tileOpacity=group.fadeFactor;
if(tile.tileSheet.tileset->collision.find(tile.tileID)!=tile.tileSheet.tileset->collision.end()){
tilePreparationList.push_back(&tile);
}else{
tileForegroundList.push_back(&tile);
}
}
}
}
#pragma endregion
std::sort(tilePreparationList.begin(),tilePreparationList.end(),[](TileRenderData*tile1,TileRenderData*tile2){return tile1->pos.y<tile2->pos.y||tile1->pos.y==tile2->pos.y&&tile1->layerID<tile2->layerID;});
std::sort(tileForegroundList.begin(),tileForegroundList.end(),[](TileRenderData*tile1,TileRenderData*tile2){return tile1->layerID<tile2->layerID;});
#pragma region Foreground Rendering w/Depth
int tilePrevY=0;
for(TileRenderData*tile:tilePreparationList){
tilePrevY=tile->pos.y+12;
#pragma region Depth Ordered Rendering
for(Effect*e:backgroundEffectsLower){
if(!e->rendered&&e->pos.y<tilePrevY){
e->rendered=true;
e->Draw();
}
}
for(Bullet*b:bulletsLower){
if(!b->rendered&&b->pos.y<tilePrevY){
b->rendered=true;
b->Draw();
}
}
#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(distToPlayer<24*3&&tile->tileOpacity>0&&tile->tileSheet.tileset->collision.find(tile->tileID)!=tile->tileSheet.tileset->collision.end()){
geom2d::rect<float>collision=tile->tileSheet.tileset->collision[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 Bullet and Effect Rendering
for(Effect*e:backgroundEffectsLower){
if(!e->rendered){
e->Draw();
}
}
for(Bullet*b:bulletsLower){
if(!b->rendered){
b->Draw();
}
}
#pragma endregion
#pragma region Permanent Foreground Rendering
for(TileRenderData*tile:tileForegroundList){
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&&tile->tileSheet.tileset->collision.find(tile->tileID)!=tile->tileSheet.tileset->collision.end()){
geom2d::rect<float>collision=tile->tileSheet.tileset->collision[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
for(Effect*e:foregroundEffectsLower){
e->Draw();
}
tilePreparationList.clear();
tileForegroundList.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=tileSheet.tileset->tileset->Sprite()->width/tileSheet.tileset->tilewidth;
int tileSheetHeight=tileSheet.tileset->tileset->Sprite()->height/tileSheet.tileset->tileheight;
int tileSheetIndex=tileID-(tileSheet.firstgid-1);
int tileSheetX=tileSheetIndex%tileSheetWidth;
int tileSheetY=tileSheetIndex/tileSheetWidth;
view.DrawPartialDecal(vi2d{x,y}*game->GetCurrentMapData().tilewidth,{float(tileSheet.tileset->tilewidth),float(tileSheet.tileset->tileheight)},tileSheet.tileset->tileset->Decal(),vi2d{tileSheetX,tileSheetY}*game->GetCurrentMapData().tilewidth,{float(tileSheet.tileset->tilewidth),float(tileSheet.tileset->tileheight)},{255,255,255,uint8_t(255-bridgeFadeFactor/TileGroup::FADE_TIME*TileGroup::FADE_AMT)});
#ifdef _DEBUG
if("debug_collision_boxes"_I){
if(tileSheet.tileset->collision.find(tileSheetIndex)!=tileSheet.tileset->collision.end()){
geom2d::rect<float>collision=tileSheet.tileset->collision[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(Effect*e:backgroundEffectsUpper){
e->rendered=false;
}
for(ZoneData&zone:upperEndZones){
RenderZone(zone.zone);
}
for(int index:dropsBeforeUpper){
ItemDrop::drops[index].Draw();
}
for(Monster*m:monstersBeforeUpper){
m->Draw();
}
if(player->upperLevel){
RenderPlayer(player->GetPos(),{1,1});
}
for(int index:dropsAfterUpper){
ItemDrop::drops[index].Draw();
}
for(Monster*m:monstersAfterUpper){
m->Draw();
}
for(Bullet*b:bulletsUpper){
b->rendered=false;
}
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)){
group.playerBehind=true;
group.fadeFactor=std::min(group.fadeFactor+fElapsedTime,TileGroup::FADE_TIME);
} else {
group.playerBehind=false;
group.fadeFactor=std::max(group.fadeFactor-fElapsedTime,0.f);
}
for(TileRenderData&tile:group.GetTiles()){
tile.tileOpacity=group.fadeFactor;
if(tile.tileSheet.tileset->collision.find(tile.tileID)!=tile.tileSheet.tileset->collision.end()){
tilePreparationList.push_back(&tile);
}else{
tileForegroundList.push_back(&tile);
}
}
}
}
#pragma endregion
std::sort(tilePreparationList.begin(),tilePreparationList.end(),[](TileRenderData*tile1,TileRenderData*tile2){return tile1->pos.y<tile2->pos.y||tile1->pos.y==tile2->pos.y&&tile1->layerID<tile2->layerID;});
std::sort(tileForegroundList.begin(),tileForegroundList.end(),[](TileRenderData*tile1,TileRenderData*tile2){return tile1->pos.y<tile2->pos.y||tile1->pos.y==tile2->pos.y&&tile1->layerID<tile2->layerID;});
#pragma region Upper Foreground Rendering w/Depth
tilePrevY=0;
for(TileRenderData*tile:tilePreparationList){
tilePrevY=tile->pos.y+12;
#pragma region Depth Ordered Upper Rendering
for(Effect*e:backgroundEffectsUpper){
if(!e->rendered&&e->pos.y<tilePrevY){
e->rendered=true;
e->Draw();
}
}
for(Bullet*b:bulletsUpper){
if(!b->rendered&&b->pos.y<tilePrevY){
b->rendered=true;
b->Draw();
}
}
#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(distToPlayer<24*3&&tile->tileOpacity>0&&tile->tileSheet.tileset->collision.find(tile->tileID)!=tile->tileSheet.tileset->collision.end()){
geom2d::rect<float>collision=tile->tileSheet.tileset->collision[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 Bullet and Effect Rendering
for(Effect*e:backgroundEffectsUpper){
if(!e->rendered){
e->Draw();
}
}
for(Bullet*b:bulletsUpper){
if(!b->rendered){
b->Draw();
}
}
#pragma endregion
#pragma region Permanent Upper Foreground Rendering
for(TileRenderData*tile:tileForegroundList){
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&&tile->tileSheet.tileset->collision.find(tile->tileID)!=tile->tileSheet.tileset->collision.end()){
geom2d::rect<float>collision=tile->tileSheet.tileset->collision[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(Effect*e:foregroundEffectsUpper){
e->Draw();
}
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-=fElapsedTime;
} else{
dn->lifeTime+=fElapsedTime;
if(dn->lifeTime<=1){
if(dn->lifeTime<DamageNumber::MOVE_UP_TIME){
if(dn->invertedDirection){
dn->pos.y+=40*fElapsedTime;
}else{
dn->pos.y-=20*fElapsedTime;
}
}
}
}
switch(dn->type){
case HEALTH_LOSS:{
std::string text=std::to_string(dn->damage);
if(!dn->friendly){
view.DrawStringPropDecal(dn->pos-GetTextSizeProp(text)/2,text,DARK_RED);
}else{
view.DrawShadowStringPropDecal(dn->pos-GetTextSizeProp(text)/2,text,RED,VERY_DARK_GREY);
}
}break;
case HEALTH_GAIN:{
std::string text="+"+std::to_string(dn->damage);
if(!dn->friendly){
view.DrawStringPropDecal(dn->pos-GetTextSizeProp(text)/2,text,DARK_GREEN);
}else{
view.DrawShadowStringPropDecal(dn->pos-GetTextSizeProp(text)/2,text,GREEN,VERY_DARK_GREY);
}
}break;
case MANA_GAIN:{
std::string text="+"+std::to_string(dn->damage);
if(dn->friendly){
view.DrawShadowStringPropDecal(dn->pos-GetTextSizeProp(text)/2,text,BLUE,VERY_DARK_GREY);
}
}break;
case INTERRUPT:{
std::string text="Interrupted!";
if(dn->friendly){
view.DrawShadowStringPropDecal(dn->pos-GetTextSizeProp(text)/2,text,BLACK,VERY_DARK_GREY,{0.5,1});
}
}break;
case CRIT:{
std::string text=std::to_string(dn->damage);
if(!dn->friendly){
view.DrawShadowStringPropDecal(dn->pos-GetTextSizeProp(text)/2,text,YELLOW,DARK_YELLOW);
}else{
view.DrawStringPropDecal(dn->pos-GetTextSizeProp(text)/2,text,BLACK);
}
}break;
}
}
#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*AiL::GetPlayer(){
return player.get();
}
void AiL::RenderHud(){
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});
}
DrawDecal({2,2},GFX["heart.png"].Decal());
DrawDecal({2,20},GFX["mana.png"].Decal());
std::string text=player->GetHealth()>0?std::to_string(player->GetHealth()):"X";
std::string text_mana=std::to_string(player->GetMana());
DrawShadowStringPropDecal({20,3},text,WHITE,BLACK,{2,2},INFINITE);
DrawShadowStringPropDecal({24,23},text_mana,{192,192,255},BLACK,{1.5f,1.5f},INFINITE);
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();
#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,1},"Selection: "+Menu::menus[INVENTORY_CONSUMABLES]->selection.str());
DrawShadowStringDecal({0,12},"Button Hold Time: "+std::to_string(Menu::menus[INVENTORY_CONSUMABLES]->buttonHoldTime));
}
#endif
}
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!="???"){
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 keyDisplaySize=vf2d{GetTextSize(a.input->GetDisplayName())}*vf2d{0.5f,0.5f};
DrawShadowStringDecal(pos+vf2d{12,-2}-keyDisplaySize/2,a.input->GetDisplayName(),keyDisplayCol,BLACK,{0.5f,0.5f},std::numeric_limits<float>::max(),1);
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){
worldShakeVel={13,-13};
worldShakeTime=duration;
worldShake=vf2d{player->GetPos()};
camera.SetTarget(worldShake);
}
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(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();
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].isTerrain=tileset.GetData().isTerrain;
std::cout<<"assets/maps/"+baseSourceDir<<" Animation Data Size: "<<MAP_TILESETS["assets/maps/"+baseSourceDir].animationData.size()<<std::endl;
std::string mapPath="assets/maps/"+tileset.GetData().ImageData.data["source"];
if(std::filesystem::exists(mapPath)){
r->Load(mapPath);
}else{
std::cout<<"WARNING! "<<mapPath<<" does not exist, auto-generating mock-up texture"<<std::endl;
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();
}
}
}
if(MAP_DATA[map].MapData.optimized){
std::cout<<"Generating optimized map for Map "<<map<<std::endl;
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=tileSheet.tileset->tileset->Sprite()->width/tileSheet.tileset->tilewidth;
int tileSheetHeight=tileSheet.tileset->tileset->Sprite()->height/tileSheet.tileset->tileheight;
int tileSheetIndex=tileID-(tileSheet.firstgid-1);
int tileSheetX=tileSheetIndex%tileSheetWidth;
int tileSheetY=tileSheetIndex/tileSheetWidth;
vi2d pos=vi2d{x,y}*tileSheet.tileset->tilewidth;
DrawPartialSprite(pos,tileSheet.tileset->tileset->Sprite(),vi2d{tileSheetX,tileSheetY}*tileSheet.tileset->tilewidth,{tileSheet.tileset->tilewidth,tileSheet.tileset->tileheight});
}
}
}
}
SetPixelMode(prevMode);
MAP_DATA[map].optimizedTile->Decal()->Update();
SetDrawTarget(nullptr);
std::cout<<" Clearing Layer Data..."<<std::endl;
MAP_DATA[map].LayerData.clear();
}
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[map].name=cp.name;
for(std::string spawn:MAP_DATA[map].spawns){
cp.spawns.push_back(spawn);
}
cp.levelDataExists=true;
}
}
}
void AiL::LoadLevel(MapName map){
SPAWNER_LIST.clear();
foregroundTileGroups.clear();
upperForegroundTileGroups.clear();
MONSTER_LIST.clear();
currentLevel=map;
levelTime=0;
bossName="";
encounterDuration=0;
totalDamageDealt=0;
encounterStarted=false;
totalBossEncounterMobs=0;
Inventory::Clear("Monster Loot");
Inventory::Clear("Stage Loot");
GetPlayer()->hp=GetPlayer()->GetMaxHealth();
GetPlayer()->mana=GetPlayer()->GetMaxMana();
GetPlayer()->SetState(State::NORMAL);
GetPlayer()->GetCastInfo()={};
GetPlayer()->ResetAccumulatedXP();
#pragma region Reset Environmental Audio
for(EnvironmentalAudio&audio:MAP_DATA[map].environmentalAudioData){
audio.Deactivate();
}
#pragma endregion
#pragma region Monster Spawn Data Setup
for(auto&[key,value]:MAP_DATA[map].SpawnerData){
SpawnerTag&spawnData=MAP_DATA[map].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")}});
}
SPAWNER_LIST.push_back(MonsterSpawner{{spawnData.ObjectData.GetFloat("x"),spawnData.ObjectData.GetFloat("y")},spawnerRadius*2,monster_list,spawnData.upperLevel,spawnData.bossNameDisplay});
}
#pragma endregion
#pragma region Identify Upper Foreground Tiles
auto GetUpperZones=[&](){
for(auto&zoneSet:MAP_DATA[map].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[map].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;
}
}
}
}
}
#pragma endregion
#pragma region Foreground and Upper Foreground Tile Fade Group Setup
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){
int tileID=layer.tiles[y][x]-1;
if(tileID!=-1){
TilesheetData tileSheet=GetTileSheet(currentLevel,tileID);
int tileSheetWidth=tileSheet.tileset->tileset->Sprite()->width/tileSheet.tileset->tilewidth;
int tileSheetHeight=tileSheet.tileset->tileset->Sprite()->height/tileSheet.tileset->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=tileSheet.tileset->tileset->Sprite()->width/tileSheet.tileset->tilewidth;
int tileSheetHeight=tileSheet.tileset->tileset->Sprite()->height/tileSheet.tileset->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;});
}
#pragma endregion
#pragma region Foreground and Upper Foreground Tile Fade Group Individual Object Grouping Splitting
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=-24;y<=24;y+=24){
for(int x=-24;x<=24;x+=24){
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.tileset==foundTile.tileSheet.tileset){ //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=tile.tileSheet.tileset->tilewidth;
int tileSheetWidth=tile.tileSheet.tileset->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);
#pragma endregion
#pragma region Bridge Layer Setup
bridgeLayerIndex=-1;
for(int counter=0;LayerTag&layer:MAP_DATA[map].LayerData){
if(IsBridgeLayer(layer)){
bridgeLayerIndex=counter;
}
counter++;
}
#pragma endregion
player->upperLevel=false; //Assume player starts on lower level.
player->ForceSetPos(MAP_DATA[map].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);
pathfinder.Initialize();
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[map].bgmSongName.length()>0){
Audio::PlayBGM(MAP_DATA[map].bgmSongName);
DisableFadeIn(true);
}
}
bool AiL::IsUpperForegroundTile(int tileID){
return tileID>=1000000;
}
bool AiL::IsForegroundTile(TilesheetData sheet,int tileID){
return sheet.tileset->foregroundTiles.find(tileID)!=sheet.tileset->foregroundTiles.end();
}
TilesheetData AiL::GetTileSheet(MapName map,int tileID){
std::vector<XMLTag>&tileData=MAP_DATA[map].TilesetData;
if(tileData.size()==1){
size_t slashMarkerSourceDir = tileData[0].data["source"].find_last_of('/');
std::string baseSourceDir=tileData[0].data["source"].substr(slashMarkerSourceDir+1);
return {&MAP_TILESETS["assets/maps/"+baseSourceDir],1};
} else {
for (int i=1;i<tileData.size();i++){
if(tileID%1000000<stoi(tileData[i].data["firstgid"])-1){
size_t slashMarkerSourceDir = tileData[size_t(i-1)].data["source"].find_last_of('/');
std::string baseSourceDir=tileData[size_t(i-1)].data["source"].substr(slashMarkerSourceDir+1);
return {&MAP_TILESETS["assets/maps/"+baseSourceDir],stoi(tileData[i-1].data["firstgid"])};
}
}
size_t slashMarkerSourceDir = tileData[tileData.size()-1].data["source"].find_last_of('/');
std::string baseSourceDir=tileData[tileData.size()-1].data["source"].substr(slashMarkerSourceDir+1);
return {&MAP_TILESETS["assets/maps/"+baseSourceDir],stoi(tileData[tileData.size()-1].data["firstgid"])};
}
}
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";
}
geom2d::rect<float>AiL::GetTileCollision(MapName map,vf2d pos,bool upperLevel){
if(pos.x<0||pos.y<0||pos.x>=GetCurrentMapData().width*game->GetCurrentMapData().tilewidth||pos.y>=GetCurrentMapData().height*game->GetCurrentMapData().tilewidth)return NO_COLLISION;
if(GetCurrentMap().optimizedTile)return NO_COLLISION; //Overworld map has no collision.
bool hasTerrain=false;
for(const LayerTag&layer:GetCurrentMap().LayerData){ //Figure out if any tile at this position is terrain. If so, we have a collision box to check.
int tileID=layer.tiles[pos.y/GetCurrentMapData().tilewidth][pos.x/GetCurrentMapData().tilewidth]-1;
if(tileID==-1)continue;
const TilesheetData&data=GetTileSheet(GetCurrentLevel(),tileID);
if(data.tileset->isTerrain){
hasTerrain=true;
break;
}
}
if(!hasTerrain)return geom2d::rect<float>({0.f,0.f},{float(GetCurrentMapData().tilewidth),float(GetCurrentMapData().tilewidth)}); //We assume no terrain means we can't walk on this.
#pragma region Lower Bridge Collision Check
if(!upperLevel){ //We are looking for lower bridge collisions.
for(ZoneData&zone:MAP_DATA[map].ZoneData["LowerBridgeCollision"]){
if(geom2d::contains(zone.zone,pos)){
return {{0,0},{float(game->GetCurrentMapData().tilewidth),float(game->GetCurrentMapData().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){
int tileID=MAP_DATA[map].LayerData[bridgeLayerIndex].tiles[int(pos.y)/GetCurrentMapData().tilewidth][int(pos.x)/GetCurrentMapData().tilewidth]-1;
if(tileID!=-1){
if (GetTileSheet(map,tileID%1000000).tileset->collision.find(tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1)!=GetTileSheet(map,tileID%1000000).tileset->collision.end()){
return GetTileSheet(map,tileID%1000000).tileset->collision[tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1].collision;
}
return NO_COLLISION;
}
}
geom2d::rect<float>foundRect=NO_COLLISION;
for(int counter=0;LayerTag&layer:MAP_DATA[map].LayerData){
//auto HasNoClass=[&](){return layer.tag.data.find("class")==layer.tag.data.end();};
if(counter!=bridgeLayerIndex){
int tileID=layer.tiles[int(pos.y)/GetCurrentMapData().tilewidth][int(pos.x)/GetCurrentMapData().tilewidth]-1;
if(tileID!=-1&&GetTileSheet(map,tileID%1000000).tileset->collision.find(tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1)!=GetTileSheet(map,tileID%1000000).tileset->collision.end()){
geom2d::rect<float>collisionRect=GetTileSheet(map,tileID%1000000).tileset->collision[tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1].collision;
if(foundRect.pos==NO_COLLISION.pos&&foundRect.size==NO_COLLISION.size){
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;
}
const MapName&AiL::GetCurrentLevel()const{
return currentLevel;
}
std::map<std::string,std::vector<ZoneData>>&AiL::GetZoneData(MapName map){
return MAP_DATA[map].ZoneData;
}
void AiL::ChangePlayerClass(Class cl){
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::set<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->level=level;
player->levelCap=levelCap;
player->totalXPEarned=totalXPEarned;
player->currentLevelXP=currentLevelXP;
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;
sig::Animation::SetupPlayerAnimations();
GetPlayer()->UpdateIdleAnimation(DOWN);
GetPlayer()->SetItem1UseFunc(itemAbility1);
GetPlayer()->SetItem2UseFunc(itemAbility2);
GetPlayer()->SetItem3UseFunc(itemAbility3);
camera.SetTarget(player->GetPos());
Player::moneyListeners=moneyListeners;
}
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};
}
int main()
{
{
AiL demo;
if (demo.Construct(WINDOW_SIZE.x, WINDOW_SIZE.y, 4, 4))
demo.Start();
}
#ifdef _DEBUG
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;
std::cout<<std::endl<<std::endl<<std::endl<<"Memory leak detected!"<<std::endl;
}
std::cout<<line<<std::endl;
std::getline(file,line);
std::cout<<line<<std::endl;
}
}
if(leaked)ERR("")
#endif
return 0;
}
datafilestringdata operator ""_s(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();
}
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));
}
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_")){
std::cout<<"Reading "<<k<<std::endl;
}
}
#endif
}
bool AiL::IsReflectiveTile(TilesheetData tileSheet,int tileID){
return tileSheet.tileset->reflectiveData.find(tileID)!=tileSheet.tileset->reflectiveData.end();
}
bool AiL::OnUserDestroy(){
if(GameState::STATE!=GameState::states[States::MAIN_MENU]){ //If we're on the main menu, we don't have a save file loaded. So we would skip saving the game.
SaveFile::SaveGame();
}
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();
return true;
}
void AiL::InitializeLevels(){
for(auto&[key,size]:DATA["Levels"]){
InitializeLevel("map_path"_S+operator""_S(("Levels."+key).c_str(),("Levels."+key).size()),key);
}
for(auto&[key,size]:DATA["Backdrops"]){
Renderable&backdrop=BACKDROP_DATA[key];
backdrop.Load("backdrop_directory"_S+DATA["Backdrops"][key].GetString(),nullptr,false,false);
}
Test::RunMapTests();
}
void AiL::SpawnMonster(vf2d pos,MonsterData&data,bool upperLevel,bool isBossSpawn){
monstersToBeSpawned.push_back(Monster(pos,data,upperLevel,isBossSpawn));
if(isBossSpawn){
totalBossEncounterMobs++;
}
}
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_ABILITY2.AddKeybind({KEY,E});
Player::KEY_ABILITY3.AddKeybind({KEY,R});
Player::KEY_ABILITY4.AddKeybind({KEY,F});
Player::KEY_DEFENSIVE.AddKeybind({MOUSE,Mouse::RIGHT});
Player::KEY_ITEM1.AddKeybind({KEY,K1});
Player::KEY_ITEM2.AddKeybind({KEY,K2});
Player::KEY_ITEM3.AddKeybind({KEY,K3});
KEY_ATTACK.AddKeybind({MOUSE,Mouse::LEFT});
KEY_LEFT.AddKeybind({KEY,LEFT});
KEY_LEFT.AddKeybind({KEY,A});
KEY_RIGHT.AddKeybind({KEY,RIGHT});
KEY_RIGHT.AddKeybind({KEY,D});
KEY_UP.AddKeybind({KEY,UP});
KEY_UP.AddKeybind({KEY,W});
KEY_DOWN.AddKeybind({KEY,DOWN});
KEY_DOWN.AddKeybind({KEY,S});
KEY_CONFIRM.AddKeybind({MOUSE,Mouse::LEFT});
KEY_CONFIRM.AddKeybind({KEY,ENTER});
KEY_MENU.AddKeybind({KEY,ESCAPE});
}
void AiL::SetBossNameDisplay(std::string name,float time){
bossName=name;
bossDisplayTimer=time;
}
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};
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(!GamePaused()&&Menu::stack.size()>0){
Menu::stack.back()->Update(this);
}
if(Menu::stack.size()>0){
for(Menu*menu:Menu::stack){
if(menu->cover)FillRectDecal({0,0},WINDOW_SIZE,{0,0,0,uint8_t(255*0.4)});
menu->Draw(this);
}
}
}
void AiL::InitializeGraphics(){
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);
std::cout<<"Loading image "+imgFile+"..."<<std::endl;
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)&&GFX[imgFile].Load("GFX_Prefix"_S+imgFile,nullptr,filtering,clamping)!=rcode::OK){
ERR(" WARNING! Failed to load "+imgFile+"!")
}
}
//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"];
img.Load("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);
std::cout<<"Theme "<<themeName<<" Loaded."<<std::endl;
if(DATA["Themes"][themeName].HasProperty("CustomBack")){
std::string backPath=DATA["Themes"][themeName]["CustomBack"].GetString();
std::cout<<" Custom background detected, Loading "<<backPath<<"..."<<std::endl;
if(!GFX.count(backPath)){
Renderable&background=GFX[backPath];
background.Load("GFX_Prefix"_S+backPath,nullptr,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];
image.Load("GFX_Prefix"_S+img);
}
std::cout<<VisualNovel::graphicsToLoad.size()<<" images for visual novel engine have been loaded."<<std::endl;
Menu::themes.SetInitialized();
std::cout<<Menu::themes.size()<<" themes have been loaded."<<std::endl;
GFX.SetInitialized();
std::cout<<GFX.size()<<" images have been loaded."<<std::endl;
}
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(IToggleable::uninitializedToggleGroupItems.size()>0){
for(IToggleable*item:IToggleable::uninitializedToggleGroupItems){
std::cout<<"\tUninitialized Toggle Item Ptr: 0x"<<std::hex<<item<<std::endl;
}
ERR("Error TOGGLE!!! Please turn on debug_toggleable_items in configuration.txt and re-run the program to see which toggleable item did not properly get a toggleable group set.");
}
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));
}
}
}
void AiL::RenderVersionInfo(){
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(MenuComponent*component:Menu::chapterListeners){
component->OnChapterUpdate(chapter);
}
}
const std::weak_ptr<Item>AiL::GetLoadoutItem(int slot){
if(slot<0||slot>loadout.size()-1)ERR("Invalid inventory slot "+std::to_string(slot)+", please choose a slot in range (0-"+std::to_string(loadout.size()-1)+").");
return loadout[slot];
}
void AiL::SetLoadoutItem(int slot,std::string itemName){
if(slot<0||slot>loadout.size()-1)ERR("Invalid inventory slot "+std::to_string(slot)+", please choose a slot in range (0-"+std::to_string(loadout.size()-1)+").");
if(Inventory::GetItemCount(itemName)>0){
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();
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;
}
}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>loadout.size()-1)ERR("Invalid inventory slot "+std::to_string(slot)+", please choose a slot in range (0-"+std::to_string(loadout.size()-1)+").");
if(GetLoadoutItem(slot).lock()->Amt()>0){
Inventory::UseItem(GetLoadoutItem(slot).lock()->ActualName());
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>loadout.size()-1)ERR("Invalid inventory slot "+std::to_string(slot)+", please choose a slot in range (0-"+std::to_string(loadout.size()-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);
};
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){
GameState::_ChangeState(transitionState);
}
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::GamePaused(){
return fadeOutDuration>0||disableFadeIn;
}
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";
});
std::cout<<"Connected to Discord!"<<std::endl;
UpdateDiscordStatus("Main Menu",player.get()->GetClassName());
}else{
std::cout<<"Could not connect to Discord. Error Code "<<int(result)<<std::endl;
}
return result;
}
#endif
void AiL::UpdateDiscordStatus(std::string levelName,std::string className){
#ifndef __EMSCRIPTEN__
if(Discord){
::discord::Activity newActivity{};
newActivity.SetDetails(levelName.c_str());
discord::ActivityTimestamps&timestamps=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("Level {} {}",player->Level(),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){
std::cout<<"Discord Activity successfully updated!"<<std::endl;
}else{
std::cout<<"Could not update Discord Activity. Error Code "<<int(result)<<std::endl;
}
});
}else{
if(SetupDiscord()==::discord::Result::Ok){
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!")
std::cout<<"Level cap detected as "<<int(player->levelCap)<<std::endl;
}
void AiL::ResetGame(){
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);
}
player->level=1;
player->stats.Reset();
player->ResetAccumulatedXP();
player->totalXPEarned=0;
player->SetMoney(100U);
for(int i=0;i<loadout.size();i++){
game->ClearLoadoutItem(i);
}
Unlock::unlocks.clear();
Unlock::Initialize();
State_OverworldMap::SetStageMarker("Stage I-I");
State_OverworldMap::UpdateCurrentConnectionPoint(*State_OverworldMap::currentConnectionPoint);
SetChapter(1);
SaveFile::SetSaveFileName("");
}
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;
}