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.
2233 lines
84 KiB
2233 lines
84 KiB
#pragma region License
|
|
/*
|
|
License (OLC-3)
|
|
~~~~~~~~~~~~~~~
|
|
|
|
Copyright 2018 - 2023 OneLoneCoder.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 "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 "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_EMITTER_LIST
|
|
|
|
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;
|
|
safemap<std::string,MapName>LEVEL_NAMES;
|
|
utils::datafile DATA;
|
|
Crawler*game;
|
|
InputGroup Crawler::KEY_LEFT;
|
|
InputGroup Crawler::KEY_RIGHT;
|
|
InputGroup Crawler::KEY_UP;
|
|
InputGroup Crawler::KEY_DOWN;
|
|
InputGroup Crawler::KEY_ATTACK;
|
|
InputGroup Crawler::KEY_CONFIRM;
|
|
InputGroup Crawler::KEY_MENU;
|
|
|
|
float Crawler::SIZE_CHANGE_SPEED=1;
|
|
|
|
Crawler::Crawler()
|
|
{
|
|
sAppName = "Crawler Concept";
|
|
game=this;
|
|
|
|
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);
|
|
|
|
for(auto&[key,value]:DATA.GetProperty("ItemConfiguration").GetKeys()){
|
|
std::string config = DATA["ItemConfiguration"][key].GetString();
|
|
utils::datafile::Read(DATA,CONFIG_PATH + "item_directory"_S + 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;
|
|
utils::datafile::INITIAL_SETUP_COMPLETE=true;
|
|
}
|
|
|
|
bool Crawler::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().MapSize*GetCurrentMap().TileSize);
|
|
camera.EnableWorldBoundary(false);
|
|
|
|
ItemInfo::InitializeItems();
|
|
|
|
player=std::make_unique<Warrior>();
|
|
|
|
InitializeGraphics();
|
|
InitializeClasses();
|
|
|
|
Monster::InitializeStrategies();
|
|
//Animations
|
|
sig::Animation::InitializeAnimations();
|
|
MonsterData::InitializeMonsterData();
|
|
|
|
sig::Animation::SetupPlayerAnimations();
|
|
view=TileTransformedView{GetScreenSize(),{1,1}};
|
|
|
|
Menu::InitializeMenus();
|
|
|
|
Inventory::AddItem("Small Health Potion",16);
|
|
Inventory::AddItem("Large Health Potion",3);
|
|
Inventory::AddItem("Medium Mana Potion",1);
|
|
Inventory::AddItem("Dummy Item 1",78);
|
|
Inventory::AddItem("Dummy Item 2",3);
|
|
Inventory::AddItem("Dummy Item 3",5);
|
|
Inventory::AddItem("Dummy Item 4",8);
|
|
Inventory::AddItem("Dummy Item 5",6);
|
|
Inventory::AddItem("Dummy Item 6",3);
|
|
Inventory::AddItem("Dummy Item 7",5);
|
|
Inventory::AddItem("Dummy Item 8",8);
|
|
Inventory::AddItem("Dummy Item 9",3);
|
|
Inventory::AddItem("Dummy Item 10",4);
|
|
Inventory::AddItem("Dummy Item 11",8);
|
|
Inventory::AddItem("Dummy Item 12",3);
|
|
Inventory::AddItem("Dummy Item 13",6);
|
|
Inventory::AddItem("Bandages",10);
|
|
Inventory::AddItem("Blue Slime Remains",22);
|
|
Inventory::AddItem("Copper Armor");
|
|
Inventory::AddItem("Copper Pants");
|
|
Inventory::AddItem("Shell Helmet");
|
|
Inventory::AddItem("Shell Armor");
|
|
Inventory::AddItem("Shell Gloves");
|
|
Inventory::AddItem("Shell Shoes");
|
|
Inventory::AddItem("Bone Pants");
|
|
Inventory::AddItem("Bone Gloves");
|
|
|
|
LoadLevel(LEVEL_NAMES["starting_map"_S]);
|
|
ChangePlayerClass(WARRIOR);
|
|
|
|
GameState::Initialize();
|
|
|
|
Unlock::Initialize();
|
|
ItemDrop::Initialize();
|
|
|
|
ValidateGameStatus(); //Checks to make sure everything has been initialized properly.
|
|
|
|
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;
|
|
if(!GamePaused()){
|
|
GameState::STATE->OnUserUpdate(this);
|
|
}
|
|
RenderWorld(GetElapsedTime());
|
|
GameState::STATE->Draw(this);
|
|
RenderMenu();
|
|
RenderFadeout();
|
|
RenderVersionInfo();
|
|
return !gameEnd;
|
|
}
|
|
|
|
bool Crawler::LeftHeld(){
|
|
return KEY_LEFT.Held();
|
|
}
|
|
bool Crawler::RightHeld(){
|
|
return KEY_RIGHT.Held();
|
|
}
|
|
bool Crawler::UpHeld(){
|
|
return KEY_UP.Held();
|
|
}
|
|
bool Crawler::DownHeld(){
|
|
return KEY_DOWN.Held();
|
|
}
|
|
bool Crawler::LeftReleased(){
|
|
return KEY_LEFT.Released();
|
|
}
|
|
bool Crawler::RightReleased(){
|
|
return KEY_RIGHT.Released();
|
|
}
|
|
bool Crawler::UpReleased(){
|
|
return KEY_UP.Released();
|
|
}
|
|
bool Crawler::DownReleased(){
|
|
return KEY_DOWN.Released();
|
|
}
|
|
|
|
void Crawler::HandleUserInput(float fElapsedTime){
|
|
if(!Menu::stack.empty())return; //A window being opened means there's no user input allowed.
|
|
|
|
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())/game->GetCurrentMap().tilewidth;
|
|
int truncatedPlayerY=int(player->GetY())/game->GetCurrentMap().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;
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(GetKey(I).bPressed){
|
|
Menu::OpenMenu(INVENTORY_CONSUMABLES);
|
|
}
|
|
if(GetKey(O).bPressed){
|
|
ItemDrop::SpawnItem(&ITEM_DATA.at("Green Slime Remains"),player->GetPos(),player->OnUpperLevel());
|
|
}
|
|
}
|
|
|
|
void Crawler::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 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;});
|
|
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 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();
|
|
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 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();
|
|
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 Crawler::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->GetCurrentMap().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->GetCurrentMap().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 Crawler::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 Crawler::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-0.175f,3)));
|
|
pos=player->teleportStartPosition.lerp(player->teleportTarget,(0.35f-player->teleportAnimationTimer)/0.35f);
|
|
}
|
|
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);
|
|
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
|
|
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-GetCurrentMap().height*GetCurrentMap().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(GetCurrentMap().optimized){
|
|
view.FillRectDecal(-WINDOW_SIZE,vf2d{float(GetCurrentMap().width),float(GetCurrentMap().height)}*float(GetCurrentMap().tilewidth)+vf2d{WINDOW_SIZE}*2,{100,180,100});
|
|
view.DrawDecal({0,0},MAP_DATA[GetCurrentLevel()].optimizedTile->Decal());
|
|
}else{
|
|
for (int x = view.GetTopLeftTile().x/GetCurrentMap().tilewidth-1; x <= view.GetBottomRightTile().x/GetCurrentMap().tilewidth; x++){
|
|
for (int y = view.GetTopLeftTile().y/GetCurrentMap().tilewidth-1; y <= view.GetBottomRightTile().y/GetCurrentMap().tilewidth; y++){
|
|
if(x>=0&&x<GetCurrentMap().width&&y>=0&&y<GetCurrentMap().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)/GetCurrentMap().tilewidth;
|
|
int playerYTruncated=int(player->GetPos().y)/GetCurrentMap().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});
|
|
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}*GetCurrentMap().tilewidth+collision.pos,collision.size,{0,0,0,128});
|
|
view.DrawRectDecal(vi2d{x,y}*GetCurrentMap().tilewidth+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/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});
|
|
}
|
|
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}*GetCurrentMap().tilewidth+collision.pos,collision.size,{0,0,0,128});
|
|
view.DrawRectDecal(vi2d{x,y}*GetCurrentMap().tilewidth+collision.pos,collision.size,GREY);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}break;
|
|
case RenderMode::EMPTY_TILES:{
|
|
if(visibleTiles.count({x,y})){
|
|
view.FillRectDecal(vi2d{x,y}*GetCurrentMap().tilewidth,{float(GetCurrentMap().tilewidth),float(GetCurrentMap().tilewidth)},{100,180,100});
|
|
}
|
|
}break;
|
|
}
|
|
}else{
|
|
view.FillRectDecal(vi2d{x,y}*GetCurrentMap().tilewidth,{float(GetCurrentMap().tilewidth),float(GetCurrentMap().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;});
|
|
|
|
#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<int>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 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<int>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:foregroundEffectsLower){
|
|
e->Draw();
|
|
}
|
|
|
|
tilePreparationList.clear();
|
|
tileForegroundList.clear();
|
|
|
|
#pragma region Bridge Layer Rendering
|
|
if(bridgeLayer!=nullptr){
|
|
for (int x = view.GetTopLeftTile().x/game->GetCurrentMap().tilewidth-1; x <= view.GetBottomRightTile().x/game->GetCurrentMap().tilewidth; x++){
|
|
for (int y = view.GetTopLeftTile().y/game->GetCurrentMap().tilewidth-1; y <= view.GetBottomRightTile().y/game->GetCurrentMap().tilewidth; y++){
|
|
if(x>=0&&x<GetCurrentMap().width&&y>=0&&y<GetCurrentMap().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->GetCurrentMap().tilewidth,{float(tileSheet.tileset->tilewidth),float(tileSheet.tileset->tileheight)},tileSheet.tileset->tileset->Decal(),vi2d{tileSheetX,tileSheetY}*game->GetCurrentMap().tilewidth,{float(tileSheet.tileset->tilewidth),float(tileSheet.tileset->tileheight)},{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}*game->GetCurrentMap().tilewidth+collision.pos,collision.size,{0,0,0,128});
|
|
view.DrawRectDecal(vi2d{x,y}*game->GetCurrentMap().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;});
|
|
|
|
#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<int>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<int>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;
|
|
}
|
|
}
|
|
|
|
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->GetCurrentMap().tilewidth),{float(game->GetCurrentMap().tilewidth),float(game->GetCurrentMap().tilewidth)},DARK_GREEN);
|
|
}
|
|
}
|
|
}
|
|
|
|
Player*Crawler::GetPlayer(){
|
|
return player.get();
|
|
}
|
|
|
|
void Crawler::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},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});
|
|
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);
|
|
}
|
|
DisplayBossEncounterInfo();
|
|
if("debug_player_info"_I){
|
|
DrawShadowStringDecal({0,128},player->GetPos().str());
|
|
DrawShadowStringDecal({0,136},"Spd: "+std::to_string(player->GetMoveSpdMult()));
|
|
DrawShadowStringDecal({0,92},"Loadout Slot 1 Qty: "+std::to_string(GetLoadoutItem(0).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));
|
|
}}
|
|
|
|
void Crawler::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.COOLDOWN_TIME)*360,PixelLerp(a.barColor1,a.barColor2,(a.cooldown/a.COOLDOWN_TIME)));
|
|
}else{
|
|
DrawSquarePie(pos+vf2d{12,12},10,360-(a.cooldown/a.COOLDOWN_TIME)*360,PixelLerp(a.barColor1,a.barColor2,(a.cooldown/a.COOLDOWN_TIME)));
|
|
}
|
|
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);
|
|
|
|
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){
|
|
uint32_t itemAmt=GetLoadoutItem(loadoutSlot).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},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,64},{0.5f,0.75f});
|
|
}
|
|
};
|
|
|
|
vf2d iconPos={8.f,float(ScreenHeight()-32)};
|
|
for(Ability&a:cooldowns){
|
|
if(a.name!="???"){
|
|
DrawCooldown(iconPos,a);
|
|
iconPos+={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 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={13,-13};
|
|
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].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;
|
|
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"]);
|
|
}
|
|
}
|
|
|
|
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(VisualNovel::storyLevelData.count(cp.map)){ //Visual novel story data for story levels.
|
|
cp.levelDataExists=true;
|
|
break;
|
|
}
|
|
if(LEVEL_NAMES.count(cp.map)&&&MapHelper::MapFromString(cp.map)==&MAP_DATA[map]){
|
|
MAP_DATA[map].name=cp.name;
|
|
for(int spawn:MAP_DATA[map].spawns){
|
|
cp.spawns.push_back(spawn);
|
|
}
|
|
cp.levelDataExists=true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Crawler::LoadLevel(MapName map){
|
|
SPAWNER_LIST.clear();
|
|
foregroundTileGroups.clear();
|
|
currentLevel=map;
|
|
levelTime=0;
|
|
bossName="";
|
|
encounterDuration=0;
|
|
totalDamageDealt=0;
|
|
encounterStarted=false;
|
|
totalBossEncounterMobs=0;
|
|
Inventory::Clear("Monster Loot");
|
|
Inventory::Clear("Stage Loot");
|
|
|
|
#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<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,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->GetCurrentMap().tilewidth; //snap to grid
|
|
int zoneY=zone.zone.pos.y/game->GetCurrentMap().tilewidth;
|
|
int zoneW=zone.zone.right().start.x/game->GetCurrentMap().tilewidth-zoneX;
|
|
int zoneH=zone.zone.bottom().start.y/game->GetCurrentMap().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<GetCurrentMap().width;x++){
|
|
for(int y=0;y<GetCurrentMap().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;
|
|
#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<GetCurrentMap().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<GetCurrentMap().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->GetCurrentMap().tilewidth,vi2d{tileSheetX,tileSheetY}*game->GetCurrentMap().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<GetCurrentMap().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<GetCurrentMap().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->GetCurrentMap().tilewidth,vi2d{tileSheetX,tileSheetY}*game->GetCurrentMap().tilewidth,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
|
|
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->SetPos(MAP_DATA[map].MapData.playerSpawnLocation);
|
|
|
|
vf2d cameraStartPos=player->GetPos()+vf2d(-24*6,0);
|
|
camera.MoveCamera(cameraStartPos);
|
|
|
|
pathfinder.Initialize();
|
|
}
|
|
|
|
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[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 Crawler::HasTileCollision(MapName map,vf2d pos,bool upperLevel){
|
|
geom2d::rect<int>collisionRect=GetTileCollision(map,pos,upperLevel);
|
|
vi2d collisionRectSnapPos=vi2d{pos/float(game->GetCurrentMap().tilewidth)}*game->GetCurrentMap().tilewidth;
|
|
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>=GetCurrentMap().width*game->GetCurrentMap().tilewidth||pos.y>=GetCurrentMap().height*game->GetCurrentMap().tilewidth)return NO_COLLISION;
|
|
#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},{game->GetCurrentMap().tilewidth,game->GetCurrentMap().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)/game->GetCurrentMap().tilewidth][int(pos.x)/game->GetCurrentMap().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<int>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)/game->GetCurrentMap().tilewidth][int(pos.x)/game->GetCurrentMap().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<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<ZoneData>>&Crawler::GetZoneData(MapName map){
|
|
return MAP_DATA[map].ZoneData;
|
|
}
|
|
|
|
void Crawler::ChangePlayerClass(Class cl){
|
|
Ability itemAbility1=player->useItem1;
|
|
Ability itemAbility2=player->useItem2;
|
|
Ability itemAbility3=player->useItem3;
|
|
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->SetBaseStat(ItemAttribute::health,DATA.GetProperty(player->GetClassName()+".BaseHealth").GetInt());
|
|
player->hp=player->GetBaseStat(ItemAttribute::health);
|
|
player->SetBaseStat(ItemAttribute::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());
|
|
sig::Animation::SetupPlayerAnimations();
|
|
GetPlayer()->UpdateIdleAnimation(DOWN);
|
|
GetPlayer()->SetItem1UseFunc(itemAbility1);
|
|
GetPlayer()->SetItem2UseFunc(itemAbility2);
|
|
GetPlayer()->SetItem3UseFunc(itemAbility3);
|
|
camera.SetTarget(player->GetPos());
|
|
}
|
|
|
|
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 float(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))
|
|
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("Crawler\\")!=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){
|
|
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 float(DATA.GetProperty(std::string(key,len)).GetReal());
|
|
}
|
|
|
|
float operator ""_FRange(const char*key,std::size_t len){
|
|
Crawler::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));
|
|
}
|
|
|
|
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();
|
|
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;
|
|
}
|
|
delete[]pathfinder.nodes;
|
|
Menu::CleanupAllMenus();
|
|
for(auto&[key,value]:MonsterData::imgs){
|
|
delete value;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Crawler::InitializeLevels(){
|
|
#define INITLEVEL(map) \
|
|
LEVEL_NAMES[#map]=map; \
|
|
InitializeLevel("map_path"_S + "Levels."#map ## _S,map);
|
|
|
|
INITLEVEL(WORLD_MAP);
|
|
INITLEVEL(CAMPAIGN_1_1);
|
|
INITLEVEL(BOSS_1);
|
|
INITLEVEL(CAMPAIGN_1_2);
|
|
|
|
Test::RunMapTests();
|
|
|
|
LEVEL_NAMES.SetInitialized();
|
|
}
|
|
|
|
void Crawler::SpawnMonster(vf2d pos,MonsterData*data,bool upperLevel,bool isBossSpawn){
|
|
monstersToBeSpawned.push_back(Monster(pos,*data,upperLevel,isBossSpawn));
|
|
if(isBossSpawn){
|
|
totalBossEncounterMobs++;
|
|
}
|
|
}
|
|
|
|
void Crawler::DrawPie(vf2d center,float radius,float degreesCut,Pixel col){
|
|
DrawPolygonDecal(nullptr,circleCooldownPoints,circleCooldownPoints,std::max(1,int(degreesCut/4)),center,radius,col);
|
|
}
|
|
|
|
void Crawler::DrawSquarePie(vf2d center,float radius,float degreesCut,Pixel col){
|
|
DrawPolygonDecal(nullptr,squareCircleCooldownPoints,squareCircleCooldownPoints,std::max(1,int(degreesCut/4)),center,radius,col);
|
|
}
|
|
|
|
void Crawler::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 Crawler::SetBossNameDisplay(std::string name,float time){
|
|
bossName=name;
|
|
bossDisplayTimer=time;
|
|
}
|
|
|
|
bool Crawler::InBossEncounter(){
|
|
return bossName!="";
|
|
}
|
|
|
|
void Crawler::StartBossEncounter(){
|
|
if(!encounterStarted){
|
|
encounterStarted=true;
|
|
totalDamageDealt=0;
|
|
encounterDuration=0.f;
|
|
}
|
|
}
|
|
|
|
void Crawler::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,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 Crawler::BossDamageDealt(int damage){
|
|
totalDamageDealt+=damage;
|
|
}
|
|
|
|
void Crawler::ReduceBossEncounterMobCount(){
|
|
totalBossEncounterMobs--;
|
|
if(totalBossEncounterMobs<0){
|
|
ERR("WARNING! Boss Encounter mob count is less than zero, THIS SHOULD NOT BE HAPPENING!");
|
|
}
|
|
}
|
|
|
|
void Crawler::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 Crawler::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;
|
|
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);
|
|
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;
|
|
}
|
|
|
|
MapTag Crawler::GetCurrentMap(){
|
|
return MAP_DATA[GetCurrentLevel()].MapData;
|
|
}
|
|
|
|
void Crawler::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);
|
|
}
|
|
}
|
|
|
|
void Crawler::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},0.4f);
|
|
}
|
|
|
|
int Crawler::GetCurrentChapter(){
|
|
return chapter;
|
|
}
|
|
|
|
void Crawler::SetChapter(int chapter){
|
|
this->chapter=chapter;
|
|
}
|
|
|
|
Item&Crawler::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 Crawler::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){
|
|
GetLoadoutItem(slot)=Inventory::CopyItem(itemName);
|
|
GetLoadoutItem(slot).amt=std::min((uint32_t)"Player.Item Loadout Limit"_I,GetLoadoutItem(slot).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).CastTime(),0,0},true};
|
|
|
|
switch(slot){
|
|
case 0:{
|
|
itemAbility.action=[&](Player*p,vf2d pos={}){
|
|
bool itemUsed=game->UseLoadoutItem(0);
|
|
|
|
return itemUsed;
|
|
};
|
|
game->GetPlayer()->SetItem1UseFunc(itemAbility);
|
|
}break;
|
|
case 1:{
|
|
itemAbility.action=[&](Player*p,vf2d pos={}){
|
|
return game->UseLoadoutItem(1);
|
|
};
|
|
game->GetPlayer()->SetItem2UseFunc(itemAbility);
|
|
}break;
|
|
case 2:{
|
|
itemAbility.action=[&](Player*p,vf2d pos={}){
|
|
return game->UseLoadoutItem(2);
|
|
};
|
|
game->GetPlayer()->SetItem3UseFunc(itemAbility);
|
|
}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 Crawler::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).Amt()>0){
|
|
Inventory::UseItem(loadout[slot].Name());
|
|
GetLoadoutItem(slot).OnUseAction();
|
|
GetLoadoutItem(slot).amt--;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Crawler::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].it=nullptr;
|
|
}
|
|
|
|
void Crawler::RenderFadeout(){
|
|
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){
|
|
fadeInDuration=std::max(0.f,fadeInDuration-GetElapsedTime());
|
|
alpha=uint8_t(util::lerp(255,0,1-(fadeInDuration/fadeOutTotalTime)));
|
|
}
|
|
FillRectDecal({0,0},GetScreenSize(),{0,0,0,alpha});
|
|
}
|
|
|
|
bool Crawler::GamePaused(){
|
|
return fadeOutDuration>0;
|
|
}
|
|
|
|
void Crawler::EndGame(){
|
|
gameEnd=true;
|
|
} |