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/Crawler/Crawler.cpp

1378 lines
51 KiB

#include "olcPGEX_TransformedView.h"
#include "Crawler.h"
#include "olcUTIL_Camera2D.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 "utils.h"
#include <set>
#include <queue>
#include "Emitter.h"
#include "config.h"
#include "safemap.h"
INCLUDE_EMITTER_LIST
//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;
safemap<std::string,MapName>LEVEL_NAMES;
utils::datafile DATA;
Crawler*game;
Key Crawler::KEY_ABILITY1=Q;
Key Crawler::KEY_ABILITY2=E;
Key Crawler::KEY_ABILITY3=R;
Key Crawler::KEY_ABILITY4=F;
float Crawler::SIZE_CHANGE_SPEED=1;
Crawler::Crawler()
{
sAppName = "Crawler Concept";
game=this;
utils::datafile::Read(DATA,"assets/config/configuration.txt");
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);
DEBUG_PATHFINDING="debug_pathfinding"_I;
for(std::string&cl:DATA.GetProperty("class_list").GetValues()){
std::cout<<cl<<std::endl;
utils::datafile::Read(DATA,CONFIG_PATH + "class_directory"_S + cl + ".txt");
}
utils::datafile::DEBUG_ACCESS_OPTIONS="debug_access_options"_I;
}
bool Crawler::OnUserCreate(){
InitializeLevels();
player=std::make_unique<Warrior>();
//Initialize Camera.
camera=Camera2D{WINDOW_SIZE};
camera.SetMode(olc::utils::Camera2D::Mode::LazyFollow);
camera.SetTarget(player->GetPos());
camera.SetWorldBoundary({0,0},WORLD_SIZE*24);
camera.EnableWorldBoundary(false);
for(auto&val:DATA["Images"].GetKeys()){
std::string key=val.first;
std::string imgFile=DATA["Images"][key].GetString();
std::cout<<"Loading image "+imgFile+"..."<<std::endl;
GFX[imgFile];
if(GFX[imgFile].Load("GFX_Prefix"_S+imgFile)!=rcode::OK){
std::cout<<" WARNING! Failed to load "+imgFile+"!";
throw;
}
}
GFX.SetInitialized();
std::cout<<GFX.size()<<" images have been loaded."<<std::endl;
Monster::InitializeStrategies();
//Animations
sig::Animation::InitializeAnimations();
MonsterData::InitializeMonsterData();
sig::Animation::SetupPlayerAnimations();
view=TileTransformedView{GetScreenSize(),{1,1}};
LoadLevel(LEVEL_NAMES["starting_map"_S]);
InitializeClasses();
ChangePlayerClass(WARRIOR);
Warrior::ability4=Ranger::ability1; //Class ability swapping demonstration.
return true;
}
bool Crawler::OnUserUpdate(float fElapsedTime){
fElapsedTime=std::clamp(fElapsedTime,0.f,1/30.f); //HACK fix. We can't have a negative time. Although using a more precise system clock should make this never occur. Also make sure if the game is too slow we advance by only 1/30th of a second.
levelTime+=fElapsedTime;
HandleUserInput(fElapsedTime);
UpdateEffects(fElapsedTime);
player->Update(fElapsedTime);
for(Monster&m:MONSTER_LIST){
m.Update(fElapsedTime);
}
UpdateBullets(fElapsedTime);
UpdateCamera(fElapsedTime);
RenderWorld(fElapsedTime);
RenderHud();
return true;
}
bool Crawler::LeftHeld(){
return GetKey(LEFT).bHeld||GetKey(A).bHeld;
}
bool Crawler::RightHeld(){
return GetKey(RIGHT).bHeld||GetKey(D).bHeld;
}
bool Crawler::UpHeld(){
return GetKey(UP).bHeld||GetKey(W).bHeld;
}
bool Crawler::DownHeld(){
return GetKey(DOWN).bHeld||GetKey(S).bHeld;
}
bool Crawler::LeftReleased(){
return GetKey(LEFT).bReleased||GetKey(A).bReleased;
}
bool Crawler::RightReleased(){
return GetKey(RIGHT).bReleased||GetKey(D).bReleased;
}
bool Crawler::UpReleased(){
return GetKey(UP).bReleased||GetKey(W).bReleased;
}
bool Crawler::DownReleased(){
return GetKey(DOWN).bReleased||GetKey(S).bReleased;
}
void Crawler::HandleUserInput(float fElapsedTime){
bool setIdleAnimation=true;
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())/24;
int truncatedPlayerY=int(player->GetY())/24;
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;
}
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;
}
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;
}
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;
}
}
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(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);
}
}
}
}
void Crawler::UpdateCamera(float fElapsedTime){
lastWorldShakeAdjust=std::max(0.f,lastWorldShakeAdjust-fElapsedTime);
if(worldShakeTime-fElapsedTime>0){
worldShakeVel={1000,-1000};
if(lastWorldShakeAdjust==0){
lastWorldShakeAdjust=0.04;
worldShakeVel.x*=-1;
worldShakeVel.y*=-1;
}
worldShake=player->GetPos()+worldShakeVel*fElapsedTime;
} else {
camera.SetTarget(player->GetPos());
}
worldShakeTime=std::max(0.f,worldShakeTime-fElapsedTime);
camera.Update(fElapsedTime);
view.SetWorldOffset(camera.GetViewPosition());
}
void Crawler::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;});
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 Crawler::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();
vf2d moveStep=b->vel*fElapsedTime;
if((b->vel*fElapsedTime).mag()>1){
moveStep=(b->vel*fElapsedTime).norm();
}
while(totalDistance>0){
totalDistance=std::max(0.f,totalDistance-1);
b->pos+=moveStep;
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;
continue;
}
}
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;
continue;
}
}
}
}
}
} 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;
}
}
outsideBulletLoop:
std::erase_if(BULLET_LIST,[](std::unique_ptr<Bullet>&b){return b->dead;});
}
void Crawler::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 Crawler::PopulateRenderLists(){
monstersBeforeLower.clear();
monstersAfterLower.clear();
monstersBeforeUpper.clear();
monstersAfterUpper.clear();
bulletsLower.clear();
bulletsUpper.clear();
backgroundEffectsLower.clear();
backgroundEffectsUpper.clear();
foregroundEffectsLower.clear();
foregroundEffectsUpper.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(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);
}
}
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 Crawler::RenderTile(vi2d pos,TilesheetData tileSheet,int tileSheetIndex,vi2d tileSheetPos){
if(tileSheet.tileset->animationData.count(tileSheetIndex)){
int animationDuration_ms=tileSheet.tileset->animationData[tileSheetIndex].size()*"animation_tile_precision"_I;
int animatedIndex=tileSheet.tileset->animationData[tileSheetIndex][int(fmod(levelTime*1000,animationDuration_ms)/"animation_tile_precision"_I)];
int tileSheetWidth=tileSheet.tileset->tileset->Sprite()->width/24;
int tileSheetX=animatedIndex%tileSheetWidth;
int tileSheetY=animatedIndex/tileSheetWidth;
view.DrawPartialDecal(pos*24,{24,24},tileSheet.tileset->tileset->Decal(),vi2d{tileSheetX,tileSheetY}*24,{24,24});
}else{
view.DrawPartialDecal(pos*24,{24,24},tileSheet.tileset->tileset->Decal(),tileSheetPos*24,{24,24});
}
}
void Crawler::RenderTile(TileRenderData&tileSheet,Pixel col){
if(tileSheet.tileSheet.tileset->animationData.count(tileSheet.tileID%1000000)){
int animationDuration_ms=tileSheet.tileSheet.tileset->animationData[tileSheet.tileID%1000000].size()*"animation_tile_precision"_I;
int animatedIndex=tileSheet.tileSheet.tileset->animationData[tileSheet.tileID%1000000][int(fmod(levelTime*1000,animationDuration_ms)/"animation_tile_precision"_I)];
int tileSheetWidth=tileSheet.tileSheet.tileset->tileset->Sprite()->width/24;
int tileSheetX=animatedIndex%tileSheetWidth;
int tileSheetY=animatedIndex/tileSheetWidth;
view.DrawPartialDecal(tileSheet.pos,{24,24},tileSheet.tileSheet.tileset->tileset->Decal(),vi2d{tileSheetX,tileSheetY},{24,24},col);
}else{
view.DrawPartialDecal(tileSheet.pos,{24,24},tileSheet.tileSheet.tileset->tileset->Decal(),tileSheet.tileSheetPos,{24,24},col);
}
}
void Crawler::RenderWorld(float fElapsedTime){
Clear(BLANK);
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*abs(pow(player->teleportAnimationTimer-0.175,3));
pos=player->teleportStartPosition.lerp(player->teleportTarget,(0.35-player->teleportAnimationTimer)/0.35);
}
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,player->GetBuffs(BuffType::ATTACK_UP).size()>0?Pixel{255,uint8_t(255*abs(sin(1.4*player->GetBuffs(BuffType::ATTACK_UP)[0].duration))),uint8_t(255*abs(sin(1.4*player->GetBuffs(BuffType::ATTACK_UP)[0].duration)))}:WHITE);
};
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
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-WORLD_SIZE.y*24;
float multiplierX=0.9;
multiplierX*=(1-abs(sin(reflectionStepTime))*"water_reflection_scale_factor"_F);
multiplierX*=(1-abs(cos(1.5*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);
}
for (int x = view.GetTopLeftTile().x/24-1; x <= view.GetBottomRightTile().x/24; x++){
for (int y = view.GetTopLeftTile().y/24-1; y <= view.GetBottomRightTile().y/24; y++){
if(x>=0&&x<WORLD_SIZE.x&&y>=0&&y<WORLD_SIZE.y){
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)/24;
int playerYTruncated=int(player->GetPos().y)/24;
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/24;
int tileSheetHeight=tileSheet.tileset->tileset->Sprite()->height/24;
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});
}
if("debug_collision_boxes"_I){
if(tileSheet.tileset->collision.find(tileSheetIndex)!=tileSheet.tileset->collision.end()){
geom2d::rect<int>collision=tileSheet.tileset->collision[tileSheetIndex].collision;
view.FillRectDecal(vi2d{x,y}*24+collision.pos,collision.size,{0,0,0,128});
view.DrawRectDecal(vi2d{x,y}*24+collision.pos,collision.size,GREY);
}
}
}
}
}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/24;
int tileSheetHeight=tileSheet.tileset->tileset->Sprite()->height/24;
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});
}
if("debug_collision_boxes"_I){
if(tileSheet.tileset->collision.find(tileSheetIndex)!=tileSheet.tileset->collision.end()){
geom2d::rect<int>collision=tileSheet.tileset->collision[tileSheetIndex].collision;
view.FillRectDecal(vi2d{x,y}*24+collision.pos,collision.size,{0,0,0,128});
view.DrawRectDecal(vi2d{x,y}*24+collision.pos,collision.size,GREY);
}
}
}
}
}break;
case RenderMode::EMPTY_TILES:{
if(visibleTiles.count({x,y})){
view.FillRectDecal(vi2d{x,y}*24,{24,24},{100,180,100});
}
}break;
}
}
}
}
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());
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->Draw();
}
for(Monster*m:monstersBeforeLower){
m->Draw();
}
if(!player->upperLevel){
RenderPlayer(player->GetPos(),{1,1});
}
if(player->GetState()==State::BLOCK){
view.DrawDecal(player->GetPos()-vf2d{12,12},GFX["block.png"].Decal());
}
for(Monster*m:monstersAfterLower){
m->Draw();
}
for(Effect*e:foregroundEffectsLower){
e->Draw();
}
for(Bullet*b:bulletsLower){
b->Draw();
}
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=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
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()){
RenderTile(tile,{255,255,255,uint8_t(255-group.fadeFactor/TileGroup::FADE_TIME*TileGroup::FADE_AMT)});
}
}
}
#pragma endregion
#pragma region Bridge Layer Rendering
if(bridgeLayer!=nullptr){
for (int x = view.GetTopLeftTile().x/24-1; x <= view.GetBottomRightTile().x/24; x++){
for (int y = view.GetTopLeftTile().y/24-1; y <= view.GetBottomRightTile().y/24; y++){
if(x>=0&&x<WORLD_SIZE.x&&y>=0&&y<WORLD_SIZE.y){
int tileID=bridgeLayer->tiles[y][x]-1;
if(tileID!=-1){
TilesheetData tileSheet=GetTileSheet(currentLevel,tileID);
int tileSheetWidth=tileSheet.tileset->tileset->Sprite()->width/24;
int tileSheetHeight=tileSheet.tileset->tileset->Sprite()->height/24;
int tileSheetIndex=tileID-(tileSheet.firstgid-1);
int tileSheetX=tileSheetIndex%tileSheetWidth;
int tileSheetY=tileSheetIndex/tileSheetWidth;
view.DrawPartialDecal(vi2d{x,y}*24,{24,24},tileSheet.tileset->tileset->Decal(),vi2d{tileSheetX,tileSheetY}*24,{24,24},{255,255,255,uint8_t(255-bridgeFadeFactor/TileGroup::FADE_TIME*TileGroup::FADE_AMT)});
#ifdef DEBUG_COLLISIONS
if(tileSheet.tileset.collision.find(tileSheetIndex)!=tileSheet.tileset.collision.end()){
geom2d::rect<int>collision=tileSheet.tileset.collision[tileSheetIndex].collision;
view.FillRectDecal(vi2d{x,y}*24+collision.pos,collision.size,{0,0,0,128});
view.DrawRectDecal(vi2d{x,y}*24+collision.pos,collision.size,GREY);
}
#endif
}
}
}
}
}
#pragma endregion
for(Effect*e:backgroundEffectsUpper){
e->Draw();
}
for(Monster*m:monstersBeforeUpper){
m->Draw();
}
if(player->upperLevel){
RenderPlayer(player->GetPos(),{1,1});
}
for(Monster*m:monstersAfterUpper){
m->Draw();
}
for(Effect*e:foregroundEffectsUpper){
e->Draw();
}
for(Bullet*b:bulletsUpper){
b->Draw();
}
if(player->OnUpperLevel()){
RenderPrecastTargetingIndicator();
}
#pragma region Upper Foreground Rendering
for(TileGroup&group:upperForegroundTileGroups){
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()){
RenderTile(tile,{255,255,255,uint8_t(255-group.fadeFactor/TileGroup::FADE_TIME*TileGroup::FADE_AMT)});
}
}
#pragma endregion
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){
it=DAMAGENUMBER_LIST.erase(it);
if(it==DAMAGENUMBER_LIST.end()){
break;
}
} else {
if(dn->lifeTime<DamageNumber::MOVE_UP_TIME){
dn->pos.y-=20*fElapsedTime;
}
}
}
std::string text=std::to_string(dn->damage);
view.DrawStringPropDecal(dn->pos-GetTextSizeProp(text)/2,text,DARK_RED);
}
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*24,{24,24},DARK_GREEN);
}
}
}
Player*Crawler::GetPlayer(){
return player.get();
}
void Crawler::RenderHud(){
const std::function<std::string(std::string)>capitalize=[](std::string name)->std::string{
std::string newName="";
for(int i=0;i<name.length();i++){
newName.append(1,name[i]>='a'?name[i]-32:name[i]);
newName.append(1,' ');
}
return newName;
};
std::vector<Ability>cooldowns{
player->GetRightClickAbility(),
player->GetAbility1(),
player->GetAbility2(),
player->GetAbility3(),
player->GetAbility4(),
};
std::vector<Ability>activeCooldowns{};
std::copy_if(cooldowns.begin(),cooldowns.end(),std::back_inserter(activeCooldowns),[](Ability&a){
return a.cooldown>0;
});
std::sort(activeCooldowns.begin(),activeCooldowns.end(),[&](Ability&a1,Ability&a2){
return a1.cooldown<a2.cooldown;
});
int offset=6*activeCooldowns.size();
for(Ability&a:activeCooldowns){
if(a.cooldown>0.1){
FillRectDecal(vf2d{10,ScreenHeight()-22.f}-vf2d{0,float(offset)},{64,6},BLACK);
FillRectDecal(vf2d{11,ScreenHeight()-21.f}-vf2d{0,float(offset)},{62,4},DARK_GREY);
GradientFillRectDecal(vf2d{10,ScreenHeight()-22.f}-vf2d{0,float(offset)},{(a.cooldown/a.COOLDOWN_TIME)*64,6},a.barColor1,a.barColor1,a.barColor2,a.barColor2);
DrawRotatedShadowStringPropDecal(vf2d{8,ScreenHeight()-20.f}+vf2d{1,1}-vf2d{0,float(offset)},capitalize(a.name),-PI/64,{0,0},WHITE,BLACK,{0.4,0.4},0.5);
std::stringstream cooldownTimeDisplay;
cooldownTimeDisplay<<std::fixed<<std::setprecision(1)<<a.cooldown;
DrawShadowStringPropDecal(vf2d{74,ScreenHeight()-22.f+1}-vf2d{float(GetTextSizeProp(cooldownTimeDisplay.str()).x*0.5),float(offset)},cooldownTimeDisplay.str(),WHITE,BLACK,{0.5,0.5});
}
offset-=6;
}
if(GetPlayer()->GetCastInfo().castTimer>0){
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=GetPlayer()->GetCastInfo().castTimer;
float totalTime=GetPlayer()->GetCastInfo().castTotalTime;
std::string castText=GetPlayer()->GetCastInfo().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},2.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});
DrawShadowStringPropDecal({24,23},text_mana,{192,192,255},BLACK,{1.5,1.5});
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);
}
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.4,versionStr,WHITE,BLACK,{0.4,0.4},0.4);
if("debug_player_info"_I){
DrawShadowStringDecal({0,128},player->GetPos().str());
DrawShadowStringDecal({0,136},"Spd: "+std::to_string(player->GetMoveSpdMult()));
}
}
void Crawler::AddEffect(std::unique_ptr<Effect>foreground,std::unique_ptr<Effect> background){
AddEffect(std::move(background),true);
AddEffect(std::move(foreground));
}
void Crawler::AddEffect(std::unique_ptr<Effect> foreground,bool back){
if(back){
backgroundEffectsToBeInserted.push_back(std::move(foreground));
} else {
foregroundEffectsToBeInserted.push_back(std::move(foreground));
}
}
vf2d Crawler::GetWorldMousePos(){
return GetMousePos()+view.GetWorldOffset();
}
void Crawler::SetupWorldShake(float duration){
worldShakeVel={750,-750};
worldShakeTime=duration;
worldShake=vf2d{player->GetPos()};
camera.SetTarget(worldShake);
}
void Crawler::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].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;
std::cout<<"assets/maps/"+baseSourceDir<<" Animation Data Size: "<<MAP_TILESETS["assets/maps/"+baseSourceDir].animationData.size()<<std::endl;
r->Load("assets/maps/"+tileset.GetData().ImageData.data["source"]);
}
}
}
void Crawler::LoadLevel(MapName map){
SPAWNER_LIST.clear();
foregroundTileGroups.clear();
currentLevel=map;
WORLD_SIZE={MAP_DATA[map].MapData.width,MAP_DATA[map].MapData.height};
levelTime=0;
#pragma region Monster Spawn Data Setup
for(auto key:MAP_DATA[map].SpawnerData){
SpawnerTag&spawnData=MAP_DATA[map].SpawnerData[key.first];
std::vector<std::pair<int,vf2d>>monster_list;
vf2d spawnerRadius=vf2d{spawnData.ObjectData.GetFloat("width"),spawnData.ObjectData.GetFloat("height")}/2;
for(XMLTag&monster:spawnData.monsters){
int monsterTypeID=monster.GetInteger("value")-1;
monster_list.push_back({monsterTypeID,{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});
}
#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<geom2d::rect<int>>{};
};
for(geom2d::rect<int>&zone:GetUpperZones()){
int zoneX=zone.pos.x/24; //snap to grid
int zoneY=zone.pos.y/24;
int zoneW=zone.right().start.x/24-zoneX;
int zoneH=zone.bottom().start.y/24-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<WORLD_SIZE.x;x++){
for(int y=0;y<WORLD_SIZE.y;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/24;
int tileSheetHeight=tileSheet.tileset->tileset->Sprite()->height/24;
int tileSheetIndex=tileID-(tileSheet.firstgid-1);
int realTileSheetIndex=(tileID%1000000)-(tileSheet.firstgid-1);
int tileSheetX=realTileSheetIndex%tileSheetWidth;
int tileSheetY=realTileSheetIndex/tileSheetWidth;
#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<WORLD_SIZE.x-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<WORLD_SIZE.y-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/24;
int tileSheetHeight=tileSheet.tileset->tileset->Sprite()->height/24;
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}*24,vi2d{tileSheetX,tileSheetY}*24,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<WORLD_SIZE.x-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<WORLD_SIZE.y-1&&foregroundTilesIncluded.find(targetPos)==foregroundTilesIncluded.end()){tileGroupChecks.push(targetPos);foregroundTilesIncluded.insert(targetPos);}
}
tileGroupChecks.pop();
}
groups.push_back(group);
}
};
TileRenderData tile={tileSheet,vi2d{x,y}*24,vi2d{tileSheetX,tileSheetY}*24,tileID,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 Bridge Layer Setup
int counter=0;
bridgeLayerIndex=-1;
for(LayerTag&layer:MAP_DATA[map].LayerData){
if(IsBridgeLayer(layer)){
bridgeLayerIndex=counter;
}
counter++;
}
#pragma endregion
player->upperLevel=false; //Assume player starts on lower level.
player->SetPos(MAP_DATA[map].MapData.playerSpawnLocation);
vf2d cameraStartPos=player->GetPos()+vf2d(-24*6,0);
camera.SetMode(Camera2D::Mode::Simple);
camera.SetTarget(cameraStartPos);
camera.Update(game->GetElapsedTime());
camera.SetMode(Camera2D::Mode::LazyFollow);
camera.SetTarget(player->GetPos());
pathfinder.Initialize();
}
vi2d Crawler::GetWorldSize(){
return WORLD_SIZE;
}
bool Crawler::IsUpperForegroundTile(int tileID){
return tileID>=1000000;
}
bool Crawler::IsForegroundTile(TilesheetData sheet,int tileID){
return sheet.tileset->foregroundTiles.find(tileID)!=sheet.tileset->foregroundTiles.end();
}
TilesheetData Crawler::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[i-1].data["source"].find_last_of('/');
std::string baseSourceDir=tileData[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 Crawler::HasTileCollision(MapName map,vf2d pos,bool upperLevel){
geom2d::rect<int>collisionRect=GetTileCollision(map,pos,upperLevel);
vi2d collisionRectSnapPos=vi2d{pos/24}*24;
collisionRect.pos+=collisionRectSnapPos;
return geom2d::overlaps(collisionRect,pos);
}
bool Crawler::IsBridgeLayer(LayerTag&layer){
return layer.tag.data.find("class")!=layer.tag.data.end()&&layer.tag.data["class"]=="Bridge";
}
geom2d::rect<int>Crawler::GetTileCollision(MapName map,vf2d pos,bool upperLevel){
if(pos.x<0||pos.y<0||pos.x>=WORLD_SIZE.x*24||pos.y>=WORLD_SIZE.y*24)return NO_COLLISION;
#pragma region Lower Bridge Collision Check
if(!upperLevel){ //We are looking for lower bridge collisions.
for(geom2d::rect<int>&zone:MAP_DATA[map].ZoneData["LowerBridgeCollision"]){
if(geom2d::contains(zone,pos)){
return {{0,0},{24,24}};
}
}
}
#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)/24][int(pos.x)/24]-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;
}
}
int counter=0;
geom2d::rect<int>foundRect=NO_COLLISION;
for(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)/24][int(pos.x)/24]-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<int>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;
}
MapName Crawler::GetCurrentLevel(){
return currentLevel;
}
std::map<std::string,std::vector<geom2d::rect<int>>>&Crawler::GetZoneData(MapName map){
return MAP_DATA[map].ZoneData;
}
void Crawler::ChangePlayerClass(Class cl){
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;
}
sig::Animation::SetupPlayerAnimations();
GetPlayer()->UpdateIdleAnimation(DOWN);
}
void Crawler::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 Crawler::GetString(std::string key){
return DATA.GetProperty(key).GetString();
}
datafilestringdata Crawler::GetStringList(std::string key){
return {DATA,key};
}
int Crawler::GetInt(std::string key){
return DATA.GetProperty(key).GetInt();
}
datafileintdata Crawler::GetIntList(std::string key){
return {DATA,key};
}
float Crawler::GetFloat(std::string key){
return DATA.GetProperty(key).GetReal();
}
datafilefloatdata Crawler::GetFloatList(std::string key){
return {DATA,key};
}
double Crawler::GetDouble(std::string key){
return DATA.GetProperty(key).GetReal();
}
datafiledoubledata Crawler::GetDoubleList(std::string key){
return {DATA,key};
}
int main()
{
Crawler demo;
if (demo.Construct(WINDOW_SIZE.x, WINDOW_SIZE.y, 4, 4, false))
demo.Start();
return 0;
}
datafilestringdata operator ""_s(const char*key,std::size_t len){
Crawler::OutputDebugInfo(key,len);
return {DATA,std::string(key,len)};
}
datafileintdata operator ""_i(const char*key,std::size_t len){
Crawler::OutputDebugInfo(key,len);
return {DATA,std::string(key,len)};
}
datafilefloatdata operator ""_f(const char*key,std::size_t len){
Crawler::OutputDebugInfo(key,len);
return {DATA,std::string(key,len)};
}
datafiledoubledata operator ""_d(const char*key,std::size_t len){
Crawler::OutputDebugInfo(key,len);
return {DATA,std::string(key,len)};
}
Pixel operator ""_Pixel(const char*key,std::size_t len){
Crawler::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){
Crawler::OutputDebugInfo(key,len);
return DATA.GetProperty(std::string(key,len)).GetString();
}
int operator ""_I(const char*key,std::size_t len){
Crawler::OutputDebugInfo(key,len);
return DATA.GetProperty(std::string(key,len)).GetInt();
}
float operator ""_F(const char*key,std::size_t len){
Crawler::OutputDebugInfo(key,len);
return DATA.GetProperty(std::string(key,len)).GetReal();
}
float operator ""_FRange(const char*key,std::size_t len){
Crawler::OutputDebugInfo(key,len);
return util::random(DATA.GetProperty(std::string(key,len)).GetReal(1)-DATA.GetProperty(std::string(key,len)).GetReal(0))+DATA.GetProperty(std::string(key,len)).GetReal(0);
}
double operator ""_D(const char*key,std::size_t len){
Crawler::OutputDebugInfo(key,len);
return DATA.GetProperty(std::string(key,len)).GetReal();
}
datafile operator ""_A(const char*key,std::size_t len){
Crawler::OutputDebugInfo(key,len);
return DATA.GetProperty(std::string(key,len));
}
void Crawler::OutputDebugInfo(const char*key,std::size_t len){
if(utils::datafile::DEBUG_ACCESS_OPTIONS){
std::string k=std::string(key);
if(!k.starts_with("debug_")){
std::cout<<"Reading "<<k<<std::endl;
}
}
}
bool Crawler::IsReflectiveTile(TilesheetData tileSheet,int tileID){
return tileSheet.tileset->reflectiveData.find(tileID)!=tileSheet.tileset->reflectiveData.end();
}
bool Crawler::OnUserDestroy(){
GFX.Reset();
return true;
}
void Crawler::InitializeLevels(){
#define INITLEVEL(map) \
InitializeLevel("map_path"_S + "Levels."#map ## _S,map); \
LEVEL_NAMES[#map]=map;
INITLEVEL(WORLD_MAP);
INITLEVEL(CAMPAIGN_1_1);
INITLEVEL(BOSS_1);
INITLEVEL(CAMPAIGN_1_2);
LEVEL_NAMES.SetInitialized();
}