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.
1085 lines
40 KiB
1085 lines
40 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_EMITTER_LIST
|
|
|
|
//#define DEBUG_COLLISIONS //Shows collision hitboxes.
|
|
//#define DEBUG_POS //Shows player position.
|
|
|
|
//192x192
|
|
const vi2d WINDOW_SIZE={24*15,24*10};
|
|
std::map<AnimationState,Animate2D::FrameSequence>ANIMATION_DATA;
|
|
std::vector<Monster>MONSTER_LIST;
|
|
std::vector<MonsterSpawner>SPAWNER_LIST;
|
|
std::vector<DamageNumber>DAMAGENUMBER_LIST;
|
|
std::vector<std::unique_ptr<Bullet>>BULLET_LIST;
|
|
Crawler*game;
|
|
|
|
Key Crawler::KEY_ABILITY1=Q;
|
|
Key Crawler::KEY_ABILITY2=E;
|
|
Key Crawler::KEY_ABILITY3=R;
|
|
Key Crawler::KEY_ABILITY4=F;
|
|
|
|
Crawler::Crawler()
|
|
{
|
|
sAppName = "Crawler Concept";
|
|
game=this;
|
|
}
|
|
|
|
bool Crawler::OnUserCreate(){
|
|
|
|
InitializeLevel("assets/Campaigns/1_1.tmx",CAMPAIGN_1_1);
|
|
|
|
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);
|
|
|
|
//Graphics
|
|
GFX_Warrior_Sheet.Load("assets/nico-warrior.png");
|
|
GFX_Slime_Sheet.Load("assets/slime.png");
|
|
GFX_Circle.Load("assets/circle.png");
|
|
GFX_Effect_GroundSlam_Back.Load("assets/ground-slam-attack-back.png");
|
|
GFX_Effect_GroundSlam_Front.Load("assets/ground-slam-attack-front.png");
|
|
GFX_Heart.Load("assets/heart.png");
|
|
GFX_BLOCK_BUBBLE.Load("assets/block.png");
|
|
GFX_Ranger_Sheet.Load("assets/nico-ranger.png");
|
|
GFX_Wizard_Sheet.Load("assets/nico-wizard.png");
|
|
GFX_Battlecry_Effect.Load("assets/battlecry_effect.png");
|
|
GFX_Mana.Load("assets/mana.png");
|
|
GFX_SonicSlash.Load("assets/sonicslash.png");
|
|
GFX_BulletCircle.Load("assets/circle.png");
|
|
GFX_BulletCircleOutline.Load("assets/circle_outline.png");
|
|
GFX_EnergyBolt.Load("assets/energy_bolt.png");
|
|
GFX_EnergyParticle.Load("assets/energy_particle.png");
|
|
GFX_Splash_Effect.Load("assets/splash_effect.png");
|
|
GFX_LightningBolt.Load("assets/lightning_bolt.png");
|
|
GFX_LightningBoltParticle1.Load("assets/lightning_bolt_part1.png");
|
|
GFX_LightningBoltParticle2.Load("assets/lightning_bolt_part2.png");
|
|
GFX_LightningBoltParticle3.Load("assets/lightning_bolt_part3.png");
|
|
GFX_LightningBoltParticle4.Load("assets/lightning_bolt_part4.png");
|
|
GFX_ChainLightning.Load("assets/chain_lightning.png");
|
|
GFX_LightningSplash.Load("assets/lightning_splash_effect.png");
|
|
GFX_Meteor.Load("assets/meteor.png");
|
|
GFX_Arrow.Load("assets/arrow.png");
|
|
|
|
//Animations
|
|
sig::Animation::InitializeAnimations();
|
|
|
|
sig::Animation::SetupPlayerAnimations();
|
|
view=TileTransformedView{GetScreenSize(),{1,1}};
|
|
|
|
LoadLevel(CAMPAIGN_1_1);
|
|
InitializeClassAbilities();
|
|
ChangePlayerClass(WARRIOR);
|
|
//Warrior::ability4=Wizard::ability3; //Class ability swapping demonstration.
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Crawler::OnUserUpdate(float fElapsedTime){
|
|
fElapsedTime=std::clamp(fElapsedTime,0.f,1/60.f);
|
|
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*100*player->GetMoveSpdMult());
|
|
player->movementVelocity.x=100;
|
|
if(staircaseDirection=="RIGHT"){
|
|
player->SetY(player->GetY()-60*fElapsedTime*player->GetMoveSpdMult());
|
|
player->movementVelocity.y=-60;
|
|
} else
|
|
if(staircaseDirection=="LEFT"){
|
|
player->SetY(player->GetY()+60*fElapsedTime*player->GetMoveSpdMult());
|
|
player->movementVelocity.y=60;
|
|
}
|
|
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*100*player->GetMoveSpdMult());
|
|
player->movementVelocity.x=-100;
|
|
if(staircaseDirection=="RIGHT"){
|
|
player->SetY(player->GetY()+60*fElapsedTime*player->GetMoveSpdMult());
|
|
player->movementVelocity.y=60;
|
|
} else
|
|
if(staircaseDirection=="LEFT"){
|
|
player->SetY(player->GetY()-60*fElapsedTime*player->GetMoveSpdMult());
|
|
player->movementVelocity.y=-60;
|
|
}
|
|
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*100*player->GetMoveSpdMult());
|
|
player->movementVelocity.y=-100*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*100*player->GetMoveSpdMult());
|
|
player->movementVelocity.y=100*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();
|
|
if(!ptr->Update(fElapsedTime)){
|
|
it=EMITTER_LIST.erase(it);
|
|
if(it==EMITTER_LIST.end()){
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for(std::vector<std::unique_ptr<Effect>>::iterator it=backgroundEffects.begin();it!=backgroundEffects.end();++it){
|
|
Effect*e=(*it).get();
|
|
if(!e->Update(fElapsedTime)){
|
|
it=backgroundEffects.erase(it);
|
|
if(it==backgroundEffects.end()){//In case we added effects to the vector and the vector has shifted in memory, we prematurely end.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for(std::vector<std::unique_ptr<Effect>>::iterator it=foregroundEffects.begin();it!=foregroundEffects.end();++it){
|
|
Effect*e=(*it).get();
|
|
if(!e->Update(fElapsedTime)){
|
|
it=foregroundEffects.erase(it);
|
|
if(it==foregroundEffects.end()){//In case we added effects to the vector and the vector has shifted in memory, we prematurely end.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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->pos+=b->vel*fElapsedTime;
|
|
b->animation.UpdateState(b->internal_animState,fElapsedTime);
|
|
if(!b->deactivated){
|
|
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())){
|
|
if(!b->hitsMultiple){
|
|
if(b->MonsterHit(m)){
|
|
it=BULLET_LIST.erase(it);
|
|
if(it==BULLET_LIST.end()){
|
|
goto outsidePlayerBulletLoop;
|
|
}
|
|
}
|
|
goto continuePlayerBulletLoop;
|
|
}
|
|
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())){
|
|
if(b->PlayerHit(GetPlayer())){
|
|
it=BULLET_LIST.erase(it);
|
|
if(it==BULLET_LIST.end()){
|
|
break;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(b->pos.x+b->radius<view.GetWorldTL().x||b->pos.x-b->radius>view.GetWorldBR().x||b->pos.y+b->radius<view.GetWorldTL().y||b->pos.y-b->radius>view.GetWorldBR().y){
|
|
it=BULLET_LIST.erase(it);
|
|
if(it==BULLET_LIST.end()){
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
b->lifetime-=fElapsedTime;
|
|
if(b->lifetime<=0){
|
|
it=BULLET_LIST.erase(it);
|
|
if(it==BULLET_LIST.end()){
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
continuePlayerBulletLoop:
|
|
continue;
|
|
}
|
|
outsidePlayerBulletLoop:
|
|
int a;
|
|
}
|
|
void Crawler::HurtEnemies(vf2d pos,float radius,int damage,bool upperLevel){
|
|
for(Monster&m:MONSTER_LIST){
|
|
if(geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m.GetPos(),12*m.GetSizeMult()))){
|
|
m.Hurt(damage,upperLevel);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Crawler::PopulateRenderLists(std::vector<Monster*>&monstersBeforeLower,std::vector<Monster*>&monstersBeforeUpper,std::vector<Monster*>&monstersAfterLower,std::vector<Monster*>&monstersAfterUpper,std::vector<Bullet*>&bulletsLower,std::vector<Bullet*>&bulletsUpper,std::vector<Effect*>&backgroundEffectsLower,std::vector<Effect*>&backgroundEffectsUpper,std::vector<Effect*>&foregroundEffectsLower,std::vector<Effect*>&foregroundEffectsUpper){
|
|
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::RenderWorld(float fElapsedTime){
|
|
Clear({100,180,100});
|
|
LayerTag*bridgeLayer=nullptr;
|
|
bool bridgeLayerFade=false;
|
|
#pragma region Basic Tile Layer Rendering
|
|
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){
|
|
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(tileSheet,tileSheetIndex)){
|
|
view.DrawPartialDecal(vi2d{x,y}*24,{24,24},tileSheet.tileset.tileset->Decal(),vi2d{tileSheetX,tileSheetY}*24,{24,24});
|
|
}
|
|
#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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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());
|
|
std::vector<Monster*>monstersBeforeLower,monstersAfterLower,monstersBeforeUpper,monstersAfterUpper;
|
|
std::vector<Bullet*>bulletsLower,bulletsUpper;
|
|
std::vector<Effect*>backgroundEffectsLower,backgroundEffectsUpper,foregroundEffectsLower,foregroundEffectsUpper;
|
|
Player*pl=GetPlayer();
|
|
|
|
PopulateRenderLists(monstersBeforeLower,monstersBeforeUpper,monstersAfterLower,monstersAfterUpper,bulletsLower,bulletsUpper,backgroundEffectsLower,backgroundEffectsUpper,foregroundEffectsLower,foregroundEffectsUpper);
|
|
|
|
if(player->GetZ()>0){
|
|
vf2d shadowScale=vf2d{8/3.f,1}/std::max(1.f,player->GetZ()/4);
|
|
view.DrawDecal(player->GetPos()-vf2d{3,3}*shadowScale/2+vf2d{0,6},GFX_Circle.Decal(),shadowScale,BLACK);
|
|
}
|
|
for(Effect*e:backgroundEffectsLower){
|
|
e->Draw();
|
|
}
|
|
for(Monster*m:monstersBeforeLower){
|
|
m->Draw();
|
|
}
|
|
vf2d playerScale=vf2d(player->GetSizeMult(),player->GetSizeMult());
|
|
vf2d playerPosition=player->GetPos();
|
|
auto RenderPlayer=[&](){
|
|
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));
|
|
playerPosition=player->teleportStartPosition.lerp(player->teleportTarget,(0.35-player->teleportAnimationTimer)/0.35);
|
|
view.DrawPartialRotatedDecal(playerPosition+vf2d{0,-player->GetZ()},player->GetFrame().GetSourceImage()->Decal(),player->GetSpinAngle(),{12,12},player->GetFrame().GetSourceRect().pos,player->GetFrame().GetSourceRect().size,playerScale,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);
|
|
} else {
|
|
view.DrawPartialRotatedDecal(playerPosition+vf2d{0,-player->GetZ()},player->GetFrame().GetSourceImage()->Decal(),player->GetSpinAngle(),{12,12},player->GetFrame().GetSourceRect().pos,player->GetFrame().GetSourceRect().size,playerScale,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);
|
|
}
|
|
};
|
|
//define end
|
|
if(!player->upperLevel){
|
|
RenderPlayer();
|
|
}
|
|
if(player->GetState()==State::BLOCK){
|
|
view.DrawDecal(player->GetPos()-vf2d{12,12},GFX_BLOCK_BUBBLE.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{game->GFX_Circle.Sprite()->width*scale.x/2,game->GFX_Circle.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{game->GFX_Circle.Sprite()->width*scale.x/2,game->GFX_Circle.Sprite()->height*scale.y/2};
|
|
view.DrawDecal(centerPoint,GFX_Circle.Decal(),scale,{255,0,0,96});
|
|
} else {
|
|
view.DrawDecal(centerPoint,GFX_Circle.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()){
|
|
view.DrawPartialDecal(tile.pos,{24,24},tile.tileset,tile.tileSheetPos,{24,24},{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();
|
|
}
|
|
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()){
|
|
view.DrawPartialDecal(tile.pos,{24,24},tile.tileset,tile.tileSheetPos,{24,24},{255,255,255,uint8_t(255-group.fadeFactor/TileGroup::FADE_TIME*TileGroup::FADE_AMT)});
|
|
}
|
|
}
|
|
#pragma endregion
|
|
for(std::vector<DamageNumber>::iterator it=DAMAGENUMBER_LIST.begin();it!=DAMAGENUMBER_LIST.end();++it){
|
|
DamageNumber&dn=*it;
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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.Decal());
|
|
DrawDecal({2,20},GFX_Mana.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);
|
|
|
|
#ifdef DEBUG_POS
|
|
DrawShadowStringDecal({0,128},player->GetPos().str());
|
|
#endif
|
|
}
|
|
|
|
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=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;
|
|
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};
|
|
|
|
for(auto key:MAP_DATA[map].SpawnerData){
|
|
SpawnerTag&spawnData=MAP_DATA[map].SpawnerData[key.first];
|
|
std::vector<std::pair<MonsterName,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;
|
|
if(monsterTypeID>=0&&monsterTypeID<MonsterName::END){
|
|
monster_list.push_back({MonsterName(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});
|
|
}
|
|
|
|
std::set<vi2d>foregroundTilesAdded,upperForegroundTilesAdded;
|
|
for(int x=0;x<WORLD_SIZE.x;x++){
|
|
for(int y=0;y<WORLD_SIZE.y;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;
|
|
#pragma region TileGroupShenanigans
|
|
TileRenderData tile={tileSheet.tileset.tileset->Decal(),vi2d{x,y}*24,vi2d{tileSheetX,tileSheetY}*24};
|
|
if(IsForegroundTile(tileSheet,tileSheetIndex)&&foregroundTilesAdded.find({x,y})==foregroundTilesAdded.end()){
|
|
std::queue<vi2d>tileGroupChecks;
|
|
TileGroup group;
|
|
foregroundTilesAdded.insert({x,y});
|
|
group.InsertTile(tile);
|
|
if(x>0)tileGroupChecks.push({x-1,y});
|
|
if(x<WORLD_SIZE.x-1)tileGroupChecks.push({x+1,y});
|
|
if(y>0)tileGroupChecks.push({x,y-1});
|
|
if(y<WORLD_SIZE.y-1)tileGroupChecks.push({x,y+1});
|
|
while(!tileGroupChecks.empty()){
|
|
vi2d&pos=tileGroupChecks.front();
|
|
tileGroupChecks.pop();
|
|
int tileID=layer.tiles[pos.y][pos.x]-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;
|
|
TileRenderData tile={tileSheet.tileset.tileset->Decal(),pos*24,vi2d{tileSheetX,tileSheetY}*24};
|
|
if(IsForegroundTile(tileSheet,tileSheetIndex)&&foregroundTilesAdded.find(pos)==foregroundTilesAdded.end()){
|
|
foregroundTilesAdded.insert(pos);
|
|
group.InsertTile(tile);
|
|
if(pos.x>0)tileGroupChecks.push(pos+vi2d{-1,0});
|
|
if(pos.x<WORLD_SIZE.x-1)tileGroupChecks.push(pos+vi2d{1,0});
|
|
if(pos.y>0)tileGroupChecks.push(pos+vi2d{0,-1});
|
|
if(pos.y<WORLD_SIZE.y-1)tileGroupChecks.push(pos+vi2d{0,1});
|
|
}
|
|
}
|
|
foregroundTileGroups.push_back(group);
|
|
} else
|
|
if(IsUpperForegroundTile(tileSheet,tileSheetIndex)&&upperForegroundTilesAdded.find({x,y})==upperForegroundTilesAdded.end()){
|
|
std::queue<vi2d>tileGroupChecks;
|
|
TileGroup group;
|
|
upperForegroundTilesAdded.insert({x,y});
|
|
group.InsertTile(tile);
|
|
if(x>0)tileGroupChecks.push({x-1,y});
|
|
if(x<WORLD_SIZE.x-1)tileGroupChecks.push({x+1,y});
|
|
if(y>0)tileGroupChecks.push({x,y-1});
|
|
if(y<WORLD_SIZE.y-1)tileGroupChecks.push({x,y+1});
|
|
while(!tileGroupChecks.empty()){
|
|
vi2d&pos=tileGroupChecks.front();
|
|
tileGroupChecks.pop();
|
|
int tileID=layer.tiles[pos.y][pos.x]-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;
|
|
TileRenderData tile={tileSheet.tileset.tileset->Decal(),pos*24,vi2d{tileSheetX,tileSheetY}*24};
|
|
if(IsUpperForegroundTile(tileSheet,tileSheetIndex)&&upperForegroundTilesAdded.find(pos)==upperForegroundTilesAdded.end()){
|
|
upperForegroundTilesAdded.insert(pos);
|
|
group.InsertTile(tile);
|
|
if(pos.x>0)tileGroupChecks.push(pos+vi2d{-1,0});
|
|
if(pos.x<WORLD_SIZE.x-1)tileGroupChecks.push(pos+vi2d{1,0});
|
|
if(pos.y>0)tileGroupChecks.push(pos+vi2d{0,-1});
|
|
if(pos.y<WORLD_SIZE.y-1)tileGroupChecks.push(pos+vi2d{0,1});
|
|
}
|
|
}
|
|
upperForegroundTileGroups.push_back(group);
|
|
}
|
|
#pragma endregion
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int counter=0;
|
|
bridgeLayerIndex=-1;
|
|
for(LayerTag&layer:MAP_DATA[map].LayerData){
|
|
if(IsBridgeLayer(layer)){
|
|
bridgeLayerIndex=counter;
|
|
}
|
|
counter++;
|
|
}
|
|
|
|
player->upperLevel=false; //Assume player starts on lower level.
|
|
player->SetPos(MAP_DATA[map].MapData.playerSpawnLocation);
|
|
|
|
pathfinder.Initialize();
|
|
}
|
|
|
|
vi2d Crawler::GetWorldSize(){
|
|
return WORLD_SIZE;
|
|
}
|
|
|
|
bool Crawler::IsUpperForegroundTile(TilesheetData sheet,int tileID){
|
|
return sheet.tileset.upperForegroundTiles.find(tileID)!=sheet.tileset.upperForegroundTiles.end();
|
|
}
|
|
|
|
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<stoi(tileData[i].data["firstgid"])){
|
|
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},{32,32}};
|
|
}
|
|
}
|
|
}
|
|
#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).tileset.collision.find(tileID-GetTileSheet(map,tileID).firstgid+1)!=GetTileSheet(map,tileID).tileset.collision.end()){
|
|
return GetTileSheet(map,tileID).tileset.collision[tileID-GetTileSheet(map,tileID).firstgid+1].collision;
|
|
}
|
|
return NO_COLLISION;
|
|
}
|
|
}
|
|
int counter=0;
|
|
for(LayerTag&layer:MAP_DATA[map].LayerData){
|
|
auto HasNoClass=[&](){return layer.tag.data.find("class")==layer.tag.data.end();};
|
|
if(HasNoClass()&&counter!=bridgeLayerIndex){
|
|
int tileID=layer.tiles[int(pos.y)/24][int(pos.x)/24]-1;
|
|
if(tileID!=-1&&GetTileSheet(map,tileID).tileset.collision.find(tileID-GetTileSheet(map,tileID).firstgid+1)!=GetTileSheet(map,tileID).tileset.collision.end()){
|
|
return GetTileSheet(map,tileID).tileset.collision[tileID-GetTileSheet(map,tileID).firstgid+1].collision;
|
|
}
|
|
}
|
|
counter++;
|
|
}
|
|
return NO_COLLISION;
|
|
}
|
|
|
|
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::InitializeClassAbilities(){
|
|
Warrior::InitializeClassAbilities();
|
|
Thief::InitializeClassAbilities();
|
|
Ranger::InitializeClassAbilities();
|
|
Trapper::InitializeClassAbilities();
|
|
Wizard::InitializeClassAbilities();
|
|
Witch::InitializeClassAbilities();
|
|
}
|
|
|
|
int main()
|
|
{
|
|
Crawler demo;
|
|
if (demo.Construct(WINDOW_SIZE.x, WINDOW_SIZE.y, 4, 4))
|
|
demo.Start();
|
|
|
|
return 0;
|
|
} |