sigonasr2, Sig, Sigo 9 months ago
commit 09571da405
  1. 1
      .gitignore
  2. 39
      Adventures in Lestoria/Adventures in Lestoria.tiled-project
  3. 9
      Adventures in Lestoria/Adventures in Lestoria.vcxproj
  4. 9
      Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters
  5. 588
      Adventures in Lestoria/AdventuresInLestoria.cpp
  6. 10
      Adventures in Lestoria/AdventuresInLestoria.h
  7. 113
      Adventures in Lestoria/Audio.cpp
  8. 17
      Adventures in Lestoria/Audio.h
  9. 9
      Adventures in Lestoria/CharacterAbilityPreviewComponent.h
  10. 2
      Adventures in Lestoria/CharacterRotatingDisplay.h
  11. 2
      Adventures in Lestoria/DEFINES.h
  12. 1
      Adventures in Lestoria/GameState.h
  13. 6
      Adventures in Lestoria/ItemDrop.cpp
  14. 51
      Adventures in Lestoria/Key.cpp
  15. 5
      Adventures in Lestoria/Key.h
  16. 108
      Adventures in Lestoria/LoadingScreen.cpp
  17. 57
      Adventures in Lestoria/LoadingScreen.h
  18. 6
      Adventures in Lestoria/Monster.cpp
  19. 15
      Adventures in Lestoria/Player.cpp
  20. 3
      Adventures in Lestoria/Player.h
  21. 16
      Adventures in Lestoria/SoundEffect.cpp
  22. 3
      Adventures in Lestoria/SoundEffect.h
  23. 2
      Adventures in Lestoria/State_GameHub.cpp
  24. 1
      Adventures in Lestoria/State_GameHub.h
  25. 9
      Adventures in Lestoria/State_GameRun.cpp
  26. 1
      Adventures in Lestoria/State_GameRun.h
  27. 1
      Adventures in Lestoria/State_LevelComplete.cpp
  28. 1
      Adventures in Lestoria/State_LevelComplete.h
  29. 55
      Adventures in Lestoria/State_MainMenu.cpp
  30. 9
      Adventures in Lestoria/State_MainMenu.h
  31. 4
      Adventures in Lestoria/State_OverworldMap.cpp
  32. 1
      Adventures in Lestoria/State_OverworldMap.h
  33. 1
      Adventures in Lestoria/State_Story.cpp
  34. 1
      Adventures in Lestoria/State_Story.h
  35. 29
      Adventures in Lestoria/TMXParser.h
  36. 23
      Adventures in Lestoria/TODO.txt
  37. 4
      Adventures in Lestoria/Test.cpp
  38. 2
      Adventures in Lestoria/Version.h
  39. 10
      Adventures in Lestoria/VisualNovel.cpp
  40. 5
      Adventures in Lestoria/VisualNovel.h
  41. 1628
      Adventures in Lestoria/assets/Campaigns/Intro_Map.tmx
  42. 2
      Adventures in Lestoria/assets/Campaigns/World_Map.tmx
  43. BIN
      Adventures in Lestoria/assets/backgrounds/cave.png
  44. BIN
      Adventures in Lestoria/assets/backgrounds/cave_dark.png
  45. BIN
      Adventures in Lestoria/assets/backgrounds/rest.png
  46. BIN
      Adventures in Lestoria/assets/backgrounds/sea.png
  47. 27
      Adventures in Lestoria/assets/config/audio/events.txt
  48. 2
      Adventures in Lestoria/assets/config/configuration.txt
  49. 5
      Adventures in Lestoria/assets/config/levels.txt
  50. 5
      Adventures in Lestoria/assets/config/story/Chapter 1.txt
  51. 2
      Adventures in Lestoria/assets/maps/112x96_Forge_No_Shadow_12x12.tsx
  52. 2
      Adventures in Lestoria/assets/maps/Decorations_c1_No_Shadow24x24.tsx
  53. 2
      Adventures in Lestoria/assets/maps/Minifantasy_TinyOverworldAllTiles.tsx
  54. BIN
      Adventures in Lestoria/assets/themes/lmb.png
  55. BIN
      Adventures in Lestoria/assets/themes/mmb.png
  56. BIN
      Adventures in Lestoria/assets/themes/rmb.png
  57. 10
      Adventures in Lestoria/olcPGEX_TTF.h
  58. 1
      distribute.ps1
  59. 4005
      git-filter-repo

1
.gitignore vendored

@ -396,3 +396,4 @@ build/CMakeFiles/3.16.3/CompilerIdC/CMakeCCompilerId.c
build/CMakeFiles/3.16.3/CompilerIdCXX/CMakeCXXCompilerId.cpp
test.cpp
/Adventures in Lestoria/Adventures in Lestoria
/Adventures in Lestoria/packkey.cpp

@ -7,6 +7,8 @@
"folders": [
"."
],
"properties": [
],
"propertyTypes": [
{
"color": "#ff3af8eb",
@ -170,6 +172,26 @@
"layer"
]
},
{
"color": "#ff4f4f51",
"drawFill": true,
"id": 37,
"members": [
{
"name": "Scroll Direction",
"propertyType": "ScrollDirection",
"type": "string",
"value": "NORTH"
}
],
"name": "Focus Area",
"type": "class",
"useAs": [
"property",
"object",
"project"
]
},
{
"color": "#ffd9d929",
"drawFill": true,
@ -420,6 +442,23 @@
"tile"
]
},
{
"id": 38,
"name": "ScrollDirection",
"storageType": "string",
"type": "enum",
"values": [
"NORTH",
"NORTHEAST",
"EAST",
"SOUTHEAST",
"SOUTH",
"SOUTHWEST",
"WEST",
"NORTHWEST"
],
"valuesAsFlags": false
},
{
"color": "#ffe67300",
"drawFill": true,

@ -387,6 +387,10 @@
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="LoadingScreen.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="MenuDefinitions.h" />
<ClInclude Include="MenuType.h">
<SubType>
@ -643,6 +647,10 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="LoadingScreen.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="MainMenuWindow.cpp" />
<ClCompile Include="Map.cpp" />
<ClCompile Include="Menu.cpp" />
@ -670,6 +678,7 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="packkey.cpp" />
<ClCompile Include="PauseMenu.cpp">
<SubType>
</SubType>

@ -465,6 +465,9 @@
<ClInclude Include="IconType.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="LoadingScreen.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Player.cpp">
@ -812,6 +815,12 @@
<ClCompile Include="GameSettings.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="LoadingScreen.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="packkey.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="cpp.hint" />

@ -76,11 +76,13 @@ All rights reserved.
#include "discord.h"
#endif
#include "GameSettings.h"
#include "LoadingScreen.h"
INCLUDE_EMITTER_LIST
INCLUDE_ITEM_CATEGORIES
INCLUDE_BACKDROP_DATA
INCLUDE_MONSTER_DATA
INCLUDE_PACK_KEY
bool _DEBUG_MAP_LOAD_INFO = false;
//360x240
@ -226,8 +228,7 @@ AiL::AiL()
}
bool AiL::OnUserCreate(){
std::string packKey="129jvgndsaf7dsa8932hj";
gamepack.LoadPack("assets/"+"gamepack_file"_S,packKey);
gamepack.LoadPack("assets/"+"gamepack_file"_S,PACK_KEY);
GamePad::init();
@ -278,7 +279,6 @@ bool AiL::OnUserCreate(){
Inventory::AddItem("Minor Health Potion"s,3);
Inventory::AddItem("Bandages"s,10);
LoadLevel("starting_map"_S);
ChangePlayerClass(WARRIOR);
GameState::Initialize();
@ -320,7 +320,7 @@ bool AiL::OnUserCreate(){
gameInitialized=true;
if(!gamepack.Loaded()&&"GENERATE_GAMEPACK"_B){
gamepack.SavePack("assets/"+"gamepack_file"_S,packKey);
gamepack.SavePack("assets/"+"gamepack_file"_S,PACK_KEY);
std::cout<<"Game Pack has been generated!"<<std::endl<<"========================"<<std::endl<<std::endl;
}
@ -336,6 +336,7 @@ bool AiL::OnUserUpdate(float fElapsedTime){
if(!GamePaused()){
GameState::STATE->OnUserUpdate(this);
}
LoadingScreen::Update();
InputListener::Update();
Audio::Update();
RenderWorld(GetElapsedTime());
@ -343,6 +344,7 @@ bool AiL::OnUserUpdate(float fElapsedTime){
RenderMenu();
GameState::STATE->DrawOverlay(this);
RenderFadeout();
LoadingScreen::Draw();
RenderVersionInfo();
#ifndef __EMSCRIPTEN__
if(Discord){
@ -898,6 +900,7 @@ void AiL::RenderWorld(float fElapsedTime){
PopulateRenderLists();
auto RenderPlayer=[&](vf2d pos,vf2d scale){
if(player->IsInvisible())return;
vf2d playerScale=vf2d(player->GetSizeMult(),player->GetSizeMult());
int count=0;
for(vf2d&pos:player->ghostPositions){
@ -1153,7 +1156,9 @@ void AiL::RenderWorld(float fElapsedTime){
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);
if(GameState::STATE!=GameState::states[States::MAIN_MENU]){ //Don't fade out tile groups while we are on the main menu.
group.fadeFactor=std::min(group.fadeFactor+fElapsedTime,TileGroup::FADE_TIME);
}
} else {
group.playerBehind=false;
group.fadeFactor=std::max(group.fadeFactor-fElapsedTime,0.f);
@ -1319,7 +1324,9 @@ void AiL::RenderWorld(float fElapsedTime){
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);
if(GameState::STATE!=GameState::states[States::MAIN_MENU]){ //Don't fade out tile groups while we are on the main menu.
group.fadeFactor=std::min(group.fadeFactor+fElapsedTime,TileGroup::FADE_TIME);
}
} else {
group.playerBehind=false;
group.fadeFactor=std::max(group.fadeFactor-fElapsedTime,0.f);
@ -1582,6 +1589,16 @@ void AiL::RenderCooldowns(){
const auto DrawCooldown=[&](vf2d pos,Ability&a,int loadoutSlot=-1/*Set to 0-2 to get an item slot rendered instead*/){
bool circle=loadoutSlot==-1;
if(a.name!="???"){
vf2d keyDisplaySize=vf2d{GetTextSize(a.input->GetDisplayName())}*vf2d{0.5f,0.5f};
InputType controlType=KEY;
if(Input::UsingGamepad())controlType=CONTROLLER;
vf2d drawOffset={};
if(loadoutSlot!=-1){
drawOffset.y+=2.f;
}
a.input->DrawInput(game,pos+vf2d{14,0.f}+drawOffset,"",255,controlType,{0.75f,0.75f});
if(a.cooldown>0.1){
vf2d iconScale={1,1};
if(loadoutSlot!=-1)iconScale={0.7f,0.7f};
@ -1647,9 +1664,6 @@ void AiL::RenderCooldowns(){
vf2d manaCostSize=vf2d{GetTextSize(std::to_string(a.manaCost))}*vf2d{0.5f,0.75f};
DrawShadowStringDecal(pos+vf2d{20,4}-manaCostSize/2,std::to_string(a.manaCost),{192,192,255},manaCostShadowCol,{0.5f,0.75f});
}
vf2d keyDisplaySize=vf2d{GetTextSize(a.input->GetDisplayName())}*vf2d{0.5f,0.5f};
DrawShadowStringDecal(pos+vf2d{12,-2}-keyDisplaySize/2,a.input->GetDisplayName(),keyDisplayCol,BLACK,{0.5f,0.5f},std::numeric_limits<float>::max(),1);
vf2d shortNameSize=vf2d{GetTextSize(a.shortName)}*vf2d{0.5f,0.75f};
DrawShadowStringDecal(pos+vf2d{13,24}-shortNameSize/2,a.shortName,shortNameCol,{255,255,255,230},{0.5f,0.75f});
@ -1820,282 +1834,354 @@ void AiL::InitializeLevel(std::string mapFile,MapName map){
}
}
void AiL::LoadLevel(MapName map){
if(game->MAP_DATA.count(map)==0)ERR(std::format("WARNING! Could not load map {}! Does not exist! Refer to levels.txt for valid maps.",map));
if(game->MAP_DATA[map].GetMapType()=="Hub"&&GameState::STATE!=GameState::states[States::GAME_HUB])ERR("WARNING! A hub level should only be initiated in the GAME_HUB game state!");
#pragma region Reset Environmental Audio
for(EnvironmentalAudio&audio:MAP_DATA[GetCurrentLevel()].environmentalAudioData){
audio.Deactivate();
}
#pragma endregion
void AiL::LoadLevel(MapName map,MusicChange changeMusic){
LoadingScreen::loading=true;
_PrepareLevel(map,changeMusic);
}
SPAWNER_LIST.clear();
foregroundTileGroups.clear();
upperForegroundTileGroups.clear();
MONSTER_LIST.clear();
ZONE_LIST.clear();
ItemDrop::drops.clear();
GameEvent::events.clear();
worldColor=WHITE;
worldColorFunc=[&](vi2d pos){return game->worldColor;};
void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
LoadingScreen::Reset();
previousLevel=currentLevel;
currentLevel=map;
levelTime=0;
bossName="";
encounterDuration=0;
totalDamageDealt=0;
encounterStarted=false;
totalBossEncounterMobs=0;
Inventory::Clear("Monster Loot");
Inventory::Clear("Stage Loot");
GetPlayer()->hp=GetPlayer()->GetMaxHealth();
GetPlayer()->mana=GetPlayer()->GetMaxMana();
GetPlayer()->SetState(State::NORMAL);
GetPlayer()->GetCastInfo()={};
GetPlayer()->ResetAccumulatedXP();
ZONE_LIST=game->MAP_DATA[game->GetCurrentLevel()].ZoneData;
#pragma region Reset all data (Loading phase 1)
LoadingScreen::AddPhase([&](){
if(game->MAP_DATA.count(GetCurrentLevel())==0)ERR(std::format("WARNING! Could not load map {}! Does not exist! Refer to levels.txt for valid maps.",map));
if(game->MAP_DATA[GetCurrentLevel()].GetMapType()=="Hub"&&GameState::STATE!=GameState::states[States::GAME_HUB])ERR("WARNING! A hub level should only be initiated in the GAME_HUB game state!");
#pragma region Reset Environmental Audio
for(EnvironmentalAudio&audio:MAP_DATA[previousLevel].environmentalAudioData){
audio.Deactivate();
}
#pragma endregion
SPAWNER_LIST.clear();
foregroundTileGroups.clear();
upperForegroundTileGroups.clear();
MONSTER_LIST.clear();
ZONE_LIST.clear();
ItemDrop::drops.clear();
GameEvent::events.clear();
worldColor=WHITE;
worldColorFunc=[&](vi2d pos){return game->worldColor;};
levelTime=0;
bossName="";
encounterDuration=0;
totalDamageDealt=0;
encounterStarted=false;
totalBossEncounterMobs=0;
Inventory::Clear("Monster Loot");
Inventory::Clear("Stage Loot");
GetPlayer()->hp=GetPlayer()->GetMaxHealth();
GetPlayer()->mana=GetPlayer()->GetMaxMana();
GetPlayer()->SetState(State::NORMAL);
GetPlayer()->GetCastInfo()={};
GetPlayer()->ResetAccumulatedXP();
GetPlayer()->SetIframes(0.f);
GetPlayer()->SetInvisible(false);
ZONE_LIST=game->MAP_DATA[game->GetCurrentLevel()].ZoneData;
return true;
});
#pragma endregion
#pragma region Monster Spawn Data Setup
for(auto&[key,value]:MAP_DATA[map].SpawnerData){
SpawnerTag&spawnData=MAP_DATA[map].SpawnerData[key];
std::vector<std::pair<std::string,vf2d>>monster_list;
#pragma region Monster Spawn Data Setup (Loading phase 2)
LoadingScreen::AddPhase([&](){
for(auto&[key,value]:MAP_DATA[GetCurrentLevel()].SpawnerData){
SpawnerTag&spawnData=MAP_DATA[GetCurrentLevel()].SpawnerData[key];
std::vector<std::pair<std::string,vf2d>>monster_list;
vf2d spawnerRadius=vf2d{spawnData.ObjectData.GetFloat("width"),spawnData.ObjectData.GetFloat("height")}/2;
for(XMLTag&monster:spawnData.monsters){
std::string monsterName=monster.GetString("value");
monster_list.push_back({monsterName,{monster.GetInteger("x")-spawnData.ObjectData.GetFloat("x"),monster.GetInteger("y")-spawnData.ObjectData.GetFloat("y")}});
vf2d spawnerRadius=vf2d{spawnData.ObjectData.GetFloat("width"),spawnData.ObjectData.GetFloat("height")}/2;
for(XMLTag&monster:spawnData.monsters){
std::string monsterName=monster.GetString("value");
monster_list.push_back({monsterName,{monster.GetInteger("x")-spawnData.ObjectData.GetFloat("x"),monster.GetInteger("y")-spawnData.ObjectData.GetFloat("y")}});
}
SPAWNER_LIST.push_back(MonsterSpawner{{spawnData.ObjectData.GetFloat("x"),spawnData.ObjectData.GetFloat("y")},spawnerRadius*2,monster_list,spawnData.upperLevel,spawnData.bossNameDisplay});
}
SPAWNER_LIST.push_back(MonsterSpawner{{spawnData.ObjectData.GetFloat("x"),spawnData.ObjectData.GetFloat("y")},spawnerRadius*2,monster_list,spawnData.upperLevel,spawnData.bossNameDisplay});
}
return true;
});
#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;
#pragma region Identify Upper Foreground Tiles (Loading phase 3)
LoadingScreen::AddPhase([&](){
auto GetUpperZones=[&](){
for(auto&zoneSet:MAP_DATA[GetCurrentLevel()].ZoneData){
if(zoneSet.first=="UpperZone"){ //We are interested in all upper zones.
return zoneSet.second;
}
}
}
return std::vector<ZoneData>{};
};
for(ZoneData&zone:GetUpperZones()){
int zoneX=zone.zone.pos.x/game->GetCurrentMapData().tilewidth; //snap to grid
int zoneY=zone.zone.pos.y/game->GetCurrentMapData().tilewidth;
int zoneW=zone.zone.right().start.x/game->GetCurrentMapData().tilewidth-zoneX;
int zoneH=zone.zone.bottom().start.y/game->GetCurrentMapData().tilewidth-zoneY;
for(int x=zoneX;x<zoneX+zoneW;x++){
for(int y=zoneY;y<zoneY+zoneH;y++){
for(LayerTag&layer:MAP_DATA[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;
return std::vector<ZoneData>{};
};
for(ZoneData&zone:GetUpperZones()){
int zoneX=zone.zone.pos.x/game->GetCurrentMapData().tilewidth; //snap to grid
int zoneY=zone.zone.pos.y/game->GetCurrentMapData().tilewidth;
int zoneW=zone.zone.right().start.x/game->GetCurrentMapData().tilewidth-zoneX;
int zoneH=zone.zone.bottom().start.y/game->GetCurrentMapData().tilewidth-zoneY;
for(int x=zoneX;x<zoneX+zoneW;x++){
for(int y=zoneY;y<zoneY+zoneH;y++){
for(LayerTag&layer:MAP_DATA[GetCurrentLevel()].LayerData){
int tile=layer.tiles[y][x]-1;
TilesheetData tileSheet=GetTileSheet(currentLevel,tile);
int tileSheetIndex=tile-(tileSheet.firstgid-1);
if(IsForegroundTile(tileSheet,tileSheetIndex)){
layer.tiles[y][x]+=1000000;
}
}
}
}
}
}
return true;
});
#pragma endregion
#pragma region Foreground and Upper Foreground Tile Fade Group Setup
std::set<vi2d>foregroundTilesAdded,upperForegroundTilesAdded;
for(int x=0;x<GetCurrentMapData().width;x++){
for(int y=0;y<GetCurrentMapData().height;y++){
int layerID=0;
for(LayerTag&layer:MAP_DATA[currentLevel].LayerData){
if(Unlock::IsUnlocked(layer.unlockCondition)){
int tileID=layer.tiles[y][x]-1;
if(tileID!=-1){
TilesheetData tileSheet=GetTileSheet(currentLevel,tileID);
int tileSheetWidth=tileSheet.tileset->tileset->Sprite()->width/tileSheet.tileset->tilewidth;
int tileSheetHeight=tileSheet.tileset->tileset->Sprite()->height/tileSheet.tileset->tileheight;
int tileSheetIndex=tileID-(tileSheet.firstgid-1);
int realTileSheetIndex=(tileID%1000000)-(tileSheet.firstgid-1);
int tileSheetX=realTileSheetIndex%tileSheetWidth;
int tileSheetY=realTileSheetIndex/tileSheetWidth;
int checkTileIndex=tileID;
int checkTileID=tileSheetIndex;
#pragma region TileGroupShenanigans
auto SetupTileGroups=[&](std::function<bool(TilesheetData,int)>IsForeground,TileRenderData tile,std::set<vi2d>&foregroundTilesIncluded,std::vector<TileGroup>&groups){
if(foregroundTilesIncluded.find({x,y})==foregroundTilesIncluded.end()&&IsForeground(tileSheet,tileSheetIndex)){
std::queue<vi2d>tileGroupChecks;
TileGroup group;
foregroundTilesIncluded.insert({x,y});
group.InsertTile(tile);
if(x>0&&foregroundTilesIncluded.find(vi2d{x,y}+vi2d{-1,0})==foregroundTilesIncluded.end())tileGroupChecks.push({x-1,y});
if(x<GetCurrentMapData().width-1&&foregroundTilesIncluded.find(vi2d{x,y}+vi2d{1,0})==foregroundTilesIncluded.end())tileGroupChecks.push({x+1,y});
if(y>0&&foregroundTilesIncluded.find(vi2d{x,y}+vi2d{0,-1})==foregroundTilesIncluded.end())tileGroupChecks.push({x,y-1});
if(y<GetCurrentMapData().height-1&&foregroundTilesIncluded.find(vi2d{x,y}+vi2d{0,1})==foregroundTilesIncluded.end())tileGroupChecks.push({x,y+1});
auto IterateThroughOtherLayers=[&](vi2d pos,bool loopAll=false){
int layer2ID=0;
bool hadForeground=false;
for(LayerTag&layer2:MAP_DATA[currentLevel].LayerData){
if(!loopAll&&&layer==&layer2){layer2ID++;continue;};
int tileID=layer2.tiles[pos.y][pos.x]-1;
TilesheetData tileSheet=GetTileSheet(currentLevel,tileID%1000000);
int tileSheetWidth=tileSheet.tileset->tileset->Sprite()->width/tileSheet.tileset->tilewidth;
int tileSheetHeight=tileSheet.tileset->tileset->Sprite()->height/tileSheet.tileset->tileheight;
int tileSheetIndex=tileID-(tileSheet.firstgid-1);
int realTileSheetIndex=(tileID%1000000)-(tileSheet.firstgid-1);
int tileSheetX=realTileSheetIndex%tileSheetWidth;
int tileSheetY=realTileSheetIndex/tileSheetWidth;
TileRenderData tile={tileSheet,vi2d{pos.x,pos.y}*game->GetCurrentMapData().tilewidth,vi2d{tileSheetX,tileSheetY}*game->GetCurrentMapData().tilewidth,realTileSheetIndex,layer2ID};
if(IsForeground(tileSheet,tileSheetIndex)){
foregroundTilesIncluded.insert({pos.x,pos.y});
group.InsertTile(tile);
hadForeground=true;
#pragma region Foreground and Upper Foreground Tile Fade Group Setup (Loading phase 4)
LoadingScreen::AddPhase([&](){
std::set<vi2d>foregroundTilesAdded,upperForegroundTilesAdded;
for(int x=0;x<GetCurrentMapData().width;x++){
for(int y=0;y<GetCurrentMapData().height;y++){
int layerID=0;
for(LayerTag&layer:MAP_DATA[currentLevel].LayerData){
if(Unlock::IsUnlocked(layer.unlockCondition)){
int tileID=layer.tiles[y][x]-1;
if(tileID!=-1){
TilesheetData tileSheet=GetTileSheet(currentLevel,tileID);
int tileSheetWidth=tileSheet.tileset->tileset->Sprite()->width/tileSheet.tileset->tilewidth;
int tileSheetHeight=tileSheet.tileset->tileset->Sprite()->height/tileSheet.tileset->tileheight;
int tileSheetIndex=tileID-(tileSheet.firstgid-1);
int realTileSheetIndex=(tileID%1000000)-(tileSheet.firstgid-1);
int tileSheetX=realTileSheetIndex%tileSheetWidth;
int tileSheetY=realTileSheetIndex/tileSheetWidth;
int checkTileIndex=tileID;
int checkTileID=tileSheetIndex;
#pragma region TileGroupShenanigans
auto SetupTileGroups=[&](std::function<bool(TilesheetData,int)>IsForeground,TileRenderData tile,std::set<vi2d>&foregroundTilesIncluded,std::vector<TileGroup>&groups){
if(foregroundTilesIncluded.find({x,y})==foregroundTilesIncluded.end()&&IsForeground(tileSheet,tileSheetIndex)){
std::queue<vi2d>tileGroupChecks;
TileGroup group;
foregroundTilesIncluded.insert({x,y});
group.InsertTile(tile);
if(x>0&&foregroundTilesIncluded.find(vi2d{x,y}+vi2d{-1,0})==foregroundTilesIncluded.end())tileGroupChecks.push({x-1,y});
if(x<GetCurrentMapData().width-1&&foregroundTilesIncluded.find(vi2d{x,y}+vi2d{1,0})==foregroundTilesIncluded.end())tileGroupChecks.push({x+1,y});
if(y>0&&foregroundTilesIncluded.find(vi2d{x,y}+vi2d{0,-1})==foregroundTilesIncluded.end())tileGroupChecks.push({x,y-1});
if(y<GetCurrentMapData().height-1&&foregroundTilesIncluded.find(vi2d{x,y}+vi2d{0,1})==foregroundTilesIncluded.end())tileGroupChecks.push({x,y+1});
auto IterateThroughOtherLayers=[&](vi2d pos,bool loopAll=false){
int layer2ID=0;
bool hadForeground=false;
for(LayerTag&layer2:MAP_DATA[currentLevel].LayerData){
if(!loopAll&&&layer==&layer2){layer2ID++;continue;};
int tileID=layer2.tiles[pos.y][pos.x]-1;
TilesheetData tileSheet=GetTileSheet(currentLevel,tileID%1000000);
int tileSheetWidth=tileSheet.tileset->tileset->Sprite()->width/tileSheet.tileset->tilewidth;
int tileSheetHeight=tileSheet.tileset->tileset->Sprite()->height/tileSheet.tileset->tileheight;
int tileSheetIndex=tileID-(tileSheet.firstgid-1);
int realTileSheetIndex=(tileID%1000000)-(tileSheet.firstgid-1);
int tileSheetX=realTileSheetIndex%tileSheetWidth;
int tileSheetY=realTileSheetIndex/tileSheetWidth;
TileRenderData tile={tileSheet,vi2d{pos.x,pos.y}*game->GetCurrentMapData().tilewidth,vi2d{tileSheetX,tileSheetY}*game->GetCurrentMapData().tilewidth,realTileSheetIndex,layer2ID};
if(IsForeground(tileSheet,tileSheetIndex)){
foregroundTilesIncluded.insert({pos.x,pos.y});
group.InsertTile(tile);
hadForeground=true;
}
layer2ID++;
}
layer2ID++;
}
return hadForeground;
};
IterateThroughOtherLayers({x,y});
while(!tileGroupChecks.empty()){
vi2d&pos=tileGroupChecks.front();
if(IterateThroughOtherLayers(pos,true)){
foregroundTilesIncluded.insert({pos.x,pos.y}); //Regardless of if we found a foreground tile or not, we need to add this to not get stuck in an infinite loop.
vi2d targetPos=pos+vi2d{-1,0};
if(pos.x>0&&foregroundTilesIncluded.find(targetPos)==foregroundTilesIncluded.end()){tileGroupChecks.push(targetPos);foregroundTilesIncluded.insert(targetPos);}
targetPos=pos+vi2d{1,0};
if(pos.x<GetCurrentMapData().width-1&&foregroundTilesIncluded.find(targetPos)==foregroundTilesIncluded.end()){tileGroupChecks.push(targetPos);foregroundTilesIncluded.insert(targetPos);}
targetPos=pos+vi2d{0,-1};
if(pos.y>0&&foregroundTilesIncluded.find(targetPos)==foregroundTilesIncluded.end()){tileGroupChecks.push(targetPos);foregroundTilesIncluded.insert(targetPos);}
targetPos=pos+vi2d{0,1};
if(pos.y<GetCurrentMapData().height-1&&foregroundTilesIncluded.find(targetPos)==foregroundTilesIncluded.end()){tileGroupChecks.push(targetPos);foregroundTilesIncluded.insert(targetPos);}
return hadForeground;
};
IterateThroughOtherLayers({x,y});
while(!tileGroupChecks.empty()){
vi2d&pos=tileGroupChecks.front();
if(IterateThroughOtherLayers(pos,true)){
foregroundTilesIncluded.insert({pos.x,pos.y}); //Regardless of if we found a foreground tile or not, we need to add this to not get stuck in an infinite loop.
vi2d targetPos=pos+vi2d{-1,0};
if(pos.x>0&&foregroundTilesIncluded.find(targetPos)==foregroundTilesIncluded.end()){tileGroupChecks.push(targetPos);foregroundTilesIncluded.insert(targetPos);}
targetPos=pos+vi2d{1,0};
if(pos.x<GetCurrentMapData().width-1&&foregroundTilesIncluded.find(targetPos)==foregroundTilesIncluded.end()){tileGroupChecks.push(targetPos);foregroundTilesIncluded.insert(targetPos);}
targetPos=pos+vi2d{0,-1};
if(pos.y>0&&foregroundTilesIncluded.find(targetPos)==foregroundTilesIncluded.end()){tileGroupChecks.push(targetPos);foregroundTilesIncluded.insert(targetPos);}
targetPos=pos+vi2d{0,1};
if(pos.y<GetCurrentMapData().height-1&&foregroundTilesIncluded.find(targetPos)==foregroundTilesIncluded.end()){tileGroupChecks.push(targetPos);foregroundTilesIncluded.insert(targetPos);}
}
tileGroupChecks.pop();
}
tileGroupChecks.pop();
groups.push_back(group);
}
groups.push_back(group);
}
};
TileRenderData tile={tileSheet,vi2d{x,y}*game->GetCurrentMapData().tilewidth,vi2d{tileSheetX,tileSheetY}*game->GetCurrentMapData().tilewidth,realTileSheetIndex,layerID};
SetupTileGroups([&](TilesheetData sheet,int tileID){return IsForegroundTile(sheet,tileID);},tile,foregroundTilesAdded,foregroundTileGroups);
SetupTileGroups([&](TilesheetData sheet,int tileID){return IsUpperForegroundTile(tileID);},tile,upperForegroundTilesAdded,upperForegroundTileGroups);
#pragma endregion
};
TileRenderData tile={tileSheet,vi2d{x,y}*game->GetCurrentMapData().tilewidth,vi2d{tileSheetX,tileSheetY}*game->GetCurrentMapData().tilewidth,realTileSheetIndex,layerID};
SetupTileGroups([&](TilesheetData sheet,int tileID){return IsForegroundTile(sheet,tileID);},tile,foregroundTilesAdded,foregroundTileGroups);
SetupTileGroups([&](TilesheetData sheet,int tileID){return IsUpperForegroundTile(tileID);},tile,upperForegroundTilesAdded,upperForegroundTileGroups);
#pragma endregion
}
}
layerID++;
}
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;});
}
for(TileGroup&group:foregroundTileGroups){
std::sort(group.GetTiles().begin(),group.GetTiles().end(),[](TileRenderData&t1,TileRenderData&t2){return t1.layerID<t2.layerID;});
}
for(TileGroup&group:upperForegroundTileGroups){
std::sort(group.GetTiles().begin(),group.GetTiles().end(),[](TileRenderData&t1,TileRenderData&t2){return t1.layerID<t2.layerID;});
}
return true;
});
#pragma endregion
#pragma region Foreground and Upper Foreground Tile Fade Group Individual Object Grouping Splitting
auto SplitUp=[&](std::vector<TileGroup>&group){
std::multimap<vi2d,TileRenderData>data;
using TileDataGroup=std::multimap<vi2d,TileRenderData>; //See below.
std::vector<TileDataGroup>splitUpData; //This stores every tile group with tiles as a multi map.
std::set<vi2d>iteratedTiles;
for(TileGroup&group:group){
for(TileRenderData&tile:group.GetTiles()){
data.insert({tile.pos,tile});
#pragma region Foreground and Upper Foreground Tile Fade Group Individual Object Grouping Splitting (Loading phase 5)
LoadingScreen::AddPhase([&](){
auto SplitUp=[&](std::vector<TileGroup>&group){
std::multimap<vi2d,TileRenderData>data;
using TileDataGroup=std::multimap<vi2d,TileRenderData>; //See below.
std::vector<TileDataGroup>splitUpData; //This stores every tile group with tiles as a multi map.
std::set<vi2d>iteratedTiles;
for(TileGroup&group:group){
for(TileRenderData&tile:group.GetTiles()){
data.insert({tile.pos,tile});
}
}
}
auto IsAdjacent=[](int tile1,int tile2,int tileSheetWidth){
return abs(tile1-tile2)==1||abs(tile1-tile2)>=tileSheetWidth-1&&abs(tile1-tile2)<=tileSheetWidth+1;
};
for(auto&[key,tile]:data){
if(iteratedTiles.count(key))continue;
vi2d loc=key;
auto loc_tiles=data.equal_range(loc);
for(auto&it=loc_tiles.first;it!=loc_tiles.second;++it){ //For each tile that exists at this position...
TileRenderData&tile=(*it).second;
bool groupFound=false;
for(TileDataGroup&group:splitUpData){ //See if this position can fit into any existing tile groups
for(int y=-game->GetCurrentMapData().tileheight;y<=game->GetCurrentMapData().tileheight;y+=game->GetCurrentMapData().tileheight){
for(int x=-game->GetCurrentMapData().tilewidth;x<=game->GetCurrentMapData().tilewidth;x+=game->GetCurrentMapData().tilewidth){
if(x!=0||y!=0){ //Check every adjacent location for a possible adjacent tile.
vi2d checkOffset=loc+vi2d{x,y};
auto find_tiles=group.equal_range(checkOffset);//Each tile group consists of tiles that may be adjacent to us. Find all tiles that are adjacent to us in this tile group.
for(auto&it=find_tiles.first;it!=find_tiles.second;++it){
//These are all tiles that were found adjacent to the location we are checking for. See if they match a potential group.
TileRenderData&foundTile=(*it).second;
if(tile.tileSheet.tileset==foundTile.tileSheet.tileset){ //Let's first see if they are even in the same tileset.
//Let's get how many tiles wide this tile sheet is.
int tileWidth=tile.tileSheet.tileset->tilewidth;
int tileSheetWidth=tile.tileSheet.tileset->tileset->Sprite()->width/tileWidth;
if(IsAdjacent(tile.tileID,foundTile.tileID,tileSheetWidth)){
group.insert({loc,tile});//We add this tile to the group! It is adjacent!
groupFound=true;
goto groupIterationDone;
auto IsAdjacent=[](int tile1,int tile2,int tileSheetWidth){
return abs(tile1-tile2)==1||abs(tile1-tile2)>=tileSheetWidth-1&&abs(tile1-tile2)<=tileSheetWidth+1;
};
for(auto&[key,tile]:data){
if(iteratedTiles.count(key))continue;
vi2d loc=key;
auto loc_tiles=data.equal_range(loc);
for(auto&it=loc_tiles.first;it!=loc_tiles.second;++it){ //For each tile that exists at this position...
TileRenderData&tile=(*it).second;
bool groupFound=false;
for(TileDataGroup&group:splitUpData){ //See if this position can fit into any existing tile groups
for(int y=-game->GetCurrentMapData().tileheight;y<=game->GetCurrentMapData().tileheight;y+=game->GetCurrentMapData().tileheight){
for(int x=-game->GetCurrentMapData().tilewidth;x<=game->GetCurrentMapData().tilewidth;x+=game->GetCurrentMapData().tilewidth){
if(x!=0||y!=0){ //Check every adjacent location for a possible adjacent tile.
vi2d checkOffset=loc+vi2d{x,y};
auto find_tiles=group.equal_range(checkOffset);//Each tile group consists of tiles that may be adjacent to us. Find all tiles that are adjacent to us in this tile group.
for(auto&it=find_tiles.first;it!=find_tiles.second;++it){
//These are all tiles that were found adjacent to the location we are checking for. See if they match a potential group.
TileRenderData&foundTile=(*it).second;
if(tile.tileSheet.tileset==foundTile.tileSheet.tileset){ //Let's first see if they are even in the same tileset.
//Let's get how many tiles wide this tile sheet is.
int tileWidth=tile.tileSheet.tileset->tilewidth;
int tileSheetWidth=tile.tileSheet.tileset->tileset->Sprite()->width/tileWidth;
if(IsAdjacent(tile.tileID,foundTile.tileID,tileSheetWidth)){
group.insert({loc,tile});//We add this tile to the group! It is adjacent!
groupFound=true;
goto groupIterationDone;
}
}
}
}
}
}
}
groupIterationDone:
if(!groupFound){
splitUpData.push_back({});
splitUpData.back().insert({loc,tile}); //Since we could not find a group to fit in, we had to start a brand new group.
}
}
groupIterationDone:
if(!groupFound){
splitUpData.push_back({});
splitUpData.back().insert({loc,tile}); //Since we could not find a group to fit in, we had to start a brand new group.
}
iteratedTiles.insert(loc);
}
iteratedTiles.insert(loc);
}
group.clear();
for(auto&split:splitUpData){
TileGroup newGroup;
for(auto&[key,value]:split){
newGroup.InsertTile(value);
group.clear();
for(auto&split:splitUpData){
TileGroup newGroup;
for(auto&[key,value]:split){
newGroup.InsertTile(value);
}
group.push_back(newGroup);
}
group.push_back(newGroup);
}
};
SplitUp(foregroundTileGroups);
SplitUp(upperForegroundTileGroups);
};
SplitUp(foregroundTileGroups);
SplitUp(upperForegroundTileGroups);
return true;
});
#pragma endregion
#pragma region Bridge Layer Setup
bridgeLayerIndex=-1;
for(int counter=0;LayerTag&layer:MAP_DATA[map].LayerData){
if(IsBridgeLayer(layer)){
bridgeLayerIndex=counter;
#pragma region Bridge Layer Setup (Loading Phase 6)
LoadingScreen::AddPhase([&](){
bridgeLayerIndex=-1;
for(int counter=0;LayerTag&layer:MAP_DATA[GetCurrentLevel()].LayerData){
if(IsBridgeLayer(layer)){
bridgeLayerIndex=counter;
}
counter++;
}
counter++;
}
return true;
});
#pragma endregion
for(NPCData data:game->MAP_DATA[game->GetCurrentLevel()].npcs){
if(Unlock::IsUnlocked(data.unlockCondition)){
MONSTER_LIST.push_back(Monster{data.spawnPos,MONSTER_DATA[data.name]});
MONSTER_LIST.back().iframe_timer=INFINITE;
MONSTER_LIST.back().npcData=data;
}
}
#pragma region Setup NPCs (Loading Phase 7)
LoadingScreen::AddPhase([&](){
for(NPCData data:game->MAP_DATA[game->GetCurrentLevel()].npcs){
if(Unlock::IsUnlocked(data.unlockCondition)){
MONSTER_LIST.push_back(Monster{data.spawnPos,MONSTER_DATA[data.name]});
MONSTER_LIST.back().iframe_timer=INFINITE;
MONSTER_LIST.back().npcData=data;
}
}
return true;
});
#pragma endregion
player->GetAbility1().cooldown=0.f;
player->GetAbility2().cooldown=0.f;
player->GetAbility3().cooldown=0.f;
player->GetAbility4().cooldown=0.f;
player->GetRightClickAbility().cooldown=0.f;
player->useItem1.cooldown=0.f;
player->useItem2.cooldown=0.f;
player->useItem3.cooldown=0.f;
#pragma region Setup Player and Camera (Loading Phase 8)
LoadingScreen::AddPhase([&](){
player->GetAbility1().cooldown=0.f;
player->GetAbility2().cooldown=0.f;
player->GetAbility3().cooldown=0.f;
player->GetAbility4().cooldown=0.f;
player->GetRightClickAbility().cooldown=0.f;
player->useItem1.cooldown=0.f;
player->useItem2.cooldown=0.f;
player->useItem3.cooldown=0.f;
player->upperLevel=false; //Assume player starts on lower level.
player->ForceSetPos(MAP_DATA[map].MapData.playerSpawnLocation); //Normal set pos does one axis and then the other, so this will make sure that we actually end up at the right spot and ignore collision rules.
player->upperLevel=false; //Assume player starts on lower level.
player->ForceSetPos(MAP_DATA[GetCurrentLevel()].MapData.playerSpawnLocation); //Normal set pos does one axis and then the other, so this will make sure that we actually end up at the right spot and ignore collision rules.
vf2d cameraStartPos=player->GetPos()+vf2d(-24*6,0);
camera.MoveCamera(cameraStartPos);
vf2d cameraStartPos=player->GetPos()+vf2d(-24*6,0);
camera.MoveCamera(cameraStartPos);
return true;
});
#pragma endregion
pathfinder.Initialize();
Audio::SetAudioEvent("Default Volume");
game->audioEngine.fullyLoaded=true; //We assume there's no audio to load, so we just set the audio as fully loaded by default.
if(MAP_DATA[map].bgmSongName.length()>0){
Audio::PlayBGM(MAP_DATA[map].bgmSongName);
DisableFadeIn(true);
#pragma region Setup Pathfinding (Loading Phase 9)
LoadingScreen::AddPhase([&](){
pathfinder.Initialize();
return true;
});
#pragma endregion
if(changeMusic==PLAY_LEVEL_MUSIC){
#pragma region Audio Preparation (Loading Phase 10)
LoadingScreen::AddPhase([&](){
Audio::SetAudioEvent("Default Volume");
game->audioEngine.fullyLoaded=true; //We assume there's no audio to load, so we just set the audio as fully loaded by default.
if(MAP_DATA[GetCurrentLevel()].bgmSongName.length()>0){
Audio::PrepareBGM(MAP_DATA[GetCurrentLevel()].bgmSongName);
DisableFadeIn(true);
}
return true;
});
#pragma endregion
//Until the audio has stopped (by waiting for a set amount of time), we will respect the audio engine's wishes and not proceed.
LoadingScreen::DeferLoad([&](){return audioEngine.playBGMWaitTime==0.f;}); //This is the wait time for the audio engine to finish.
#pragma region Audio Channel Loading (Count based on Audio::GetPrepareBGMLoopIterations)
for(int i=0;i<Audio::GetPrepareBGMLoopIterations(MAP_DATA[GetCurrentLevel()].bgmSongName);i++){
LoadingScreen::AddPhase([&](){
Audio::UpdateLoop();
return true;
});
}
#pragma endregion
LoadingScreen::AddPhase([&](){
Audio::BGM&track=audioEngine.bgm[audioEngine.GetTrackName()];
for(int trackID:track.GetChannelIDs()){
audioEngine.Engine().Play(trackID,true);
}
return true;
});
}
}
@ -2204,10 +2290,6 @@ const MapName&AiL::GetCurrentLevel()const{
return currentLevel;
}
std::map<std::string,std::vector<ZoneData>>&AiL::GetZoneData(MapName map){
return MAP_DATA[map].ZoneData;
}
void AiL::ChangePlayerClass(Class cl){
Ability itemAbility1=player->useItem1;
Ability itemAbility2=player->useItem2;
@ -3100,7 +3182,7 @@ void AiL::RenderFadeout(){
}
bool AiL::GamePaused(){
return fadeOutDuration>0||disableFadeIn||paused;
return fadeOutDuration>0||disableFadeIn||paused||LoadingScreen::loading;
}
void AiL::PauseGame(){
@ -3338,12 +3420,22 @@ const bool AiL::GameInitialized()const {
rcode AiL::LoadResource(Renderable&renderable,std::string_view imgPath,bool filter,bool clamp){
rcode returnCode;
if(gamepack.Loaded()){
returnCode=renderable.Load(std::string(imgPath),&gamepack);
returnCode=renderable.Load(std::string(imgPath),&gamepack,filter,clamp);
}else{
returnCode=renderable.Load(std::string(imgPath));
returnCode=renderable.Load(std::string(imgPath),nullptr,filter,clamp);
if("GENERATE_GAMEPACK"_B){
gamepack.AddFile(std::string(imgPath));
}
}
return returnCode;
}
void AiL::UpdateMonsters(){
for(Monster&m:MONSTER_LIST){
m.Update(game->GetElapsedTime());
}
for(Monster&m:game->monstersToBeSpawned){
MONSTER_LIST.push_back(m);
}
game->monstersToBeSpawned.clear();
}

@ -72,6 +72,10 @@ class AiL : public olc::PixelGameEngine
std::unique_ptr<Player>player;
SplashScreen splash;
public:
enum MusicChange{
NO_MUSIC_CHANGE,
PLAY_LEVEL_MUSIC,
};
Pathfinding pathfinder;
static InputGroup KEY_BACK;
static InputGroup KEY_CONFIRM;
@ -127,6 +131,7 @@ private:
float lastWorldShakeAdjust=0;
vf2d worldShakeVel={};
const float WORLD_SHAKE_ADJUST_MAX_TIME=0.4f;
MapName previousLevel="CAMPAIGN_1_1";
MapName currentLevel="CAMPAIGN_1_1";
std::vector<TileGroup>foregroundTileGroups;
std::vector<TileGroup>upperForegroundTileGroups;
@ -169,6 +174,7 @@ private:
ResourcePack gamepack;
void ValidateGameStatus();
void _PrepareLevel(MapName map,MusicChange changeMusic);
#ifndef __EMSCRIPTEN__
::discord::Result SetupDiscord();
#endif
@ -186,7 +192,7 @@ public:
geom2d::rect<float>NO_COLLISION={{0.f,0.f,},{0.f,0.f}};
TileTransformedView view;
void InitializeLevel(std::string mapFile,MapName map);
void LoadLevel(MapName map);
void LoadLevel(MapName map,MusicChange changeMusic=PLAY_LEVEL_MUSIC);
void HandleUserInput(float fElapsedTime);
void UpdateCamera(float fElapsedTime);
void UpdateEffects(float fElapsedTime);
@ -225,7 +231,6 @@ public:
bool HasTileCollision(MapName map,vf2d pos,bool upperLevel=false);
const MapName&GetCurrentLevel()const;
bool IsBridgeLayer(LayerTag&layer);
std::map<std::string,std::vector<ZoneData>>&GetZoneData(MapName map);
void PopulateRenderLists();
void ChangePlayerClass(Class cl);
std::string GetString(std::string key);
@ -290,6 +295,7 @@ public:
void ResumeGame();
const bool GameInitialized()const;
rcode LoadResource(Renderable&renderable,std::string_view imgPath,bool filter=false,bool clamp=true);
void UpdateMonsters();
struct TileGroupData{
vi2d tilePos;

@ -39,6 +39,7 @@ All rights reserved.
#include "AdventuresInLestoria.h"
#include "DEFINES.h"
#include "util.h"
#include "LoadingScreen.h"
INCLUDE_game
INCLUDE_DATA
@ -121,12 +122,21 @@ const size_t Audio::LoadAndPlay(const std::string_view sound,const bool loop){
Engine().Play(soundID,loop);
return soundID;
};
void Audio::PlayBGM(const std::string_view sound,const bool loop){
void Audio::PrepareBGM(const std::string_view sound,const bool loop){
BGM&track=Self().bgm[std::string(sound)];
Self().fullyLoaded=false;
StopBGM(); //Stop any currently playing track.
Self().playParams={std::string(sound),loop};
Self().playBGMWaitTime=0.7f;
#pragma region Internal Loading Loop Setup
Self().trackLoadStarted=false;
Self().trackLoadComplete=false;
Self().channelPlayingStarted=false;
Self().channelPlayingComplete=false;
int currentLoopIndex=0;
#pragma endregion
Self().immediatelyLoadAudio=false;
};
void Audio::StopBGM(){
@ -145,24 +155,39 @@ const bool Audio::BGMIsPlaying(){
return Self().currentBGM.length()>0;
}
const Volume&Audio::BGM::GetVolume(const Event&eventName,const ChannelID&id)const{
return eventVolumes.GetVolumes(eventName).at(id);
const Volume&Audio::BGM::GetVolume(const Event&eventName,const int&index)const{
return eventVolumes.GetVolumes(eventName).at(index);
}
void Audio::BGM::Load(){
if(Self().BGMIsPlaying()){
if(Self().GetTrackName()==songFileName)return; //We are already playing the current track.
BGM&bgm=Self().bgm[Self().GetTrackName()];
if(!Self().trackLoadStarted){
Self().trackLoadStarted=true;
if(Self().BGMIsPlaying()){
bgm.Unload();
if(Self().GetTrackName()==songFileName){ //We are already playing the current track.
Self().trackLoadComplete=Self().channelPlayingComplete=Self().fullyLoaded=true;
return;
}else{
BGM&bgm=Self().bgm[Self().GetTrackName()];
if(Self().BGMIsPlaying()){
bgm.Unload();
}
}
}
}
Self().currentBGM=songFileName;
BGM&newBgm=Self().bgm[songFileName];
if(newBgm.channels.size()>0)ERR(std::format("WARNING! The size of the channels list is greater than zero! Size: {}",newBgm.channels.size()));
for(const ChannelName&channel:newBgm.GetChannels()){
Self().currentBGM=songFileName;
Self().currentLoopIndex=0;
BGM&newBgm=Self().bgm[songFileName];
if(newBgm.channels.size()>0)ERR(std::format("WARNING! The size of the channels list is greater than zero! Size: {}",newBgm.channels.size()));
}else{
BGM&newBgm=Self().bgm[songFileName];
const ChannelName&channel=newBgm.GetChannels()[Self().currentLoopIndex];
ChannelID soundID=Engine().LoadSound("bgm_directory"_S+channel);
newBgm.channels.push_back(soundID);
#pragma region Handle threaded loop indexing
Self().currentLoopIndex++;
if(Self().currentLoopIndex>=newBgm.GetChannels().size()){
Self().trackLoadComplete=true;
}
#pragma endregion
}
}
@ -258,27 +283,60 @@ const SongName&Audio::GetTrackName(){
return Self().currentBGM;
}
void Audio::Update(){
if(Self().playBGMWaitTime>0.f){
Self().playBGMWaitTime=std::max(Self().playBGMWaitTime-game->GetElapsedTime(),0.f);
if(Self().playBGMWaitTime==0.f){
BGM&track=Self().bgm[Self().playParams.sound];
void Audio::UpdateLoop(){
if(Self().playBGMWaitTime==0.f){
BGM&track=Self().bgm[Self().playParams.sound];
if(!Self().trackLoadComplete){
track.Load();
Self().prevVolumes.clear();
Self().targetVolumes.clear();
Self().fadeToTargetVolumeTime=0.f;
for(int channelListIndex=0;int trackID:track.GetChannelIDs()){
float channelVol=track.GetVolume(Self().currentAudioEvent,channelListIndex);
}else
if(!Self().channelPlayingComplete){
if(!Self().channelPlayingStarted){
Self().prevVolumes.clear();
Self().targetVolumes.clear();
Self().fadeToTargetVolumeTime=0.f;
Self().currentLoopIndex=0;
Self().channelPlayingStarted=true;
}else{
int trackID=track.GetChannelIDs()[Self().currentLoopIndex];
float channelVol=track.GetVolume(Self().currentAudioEvent,Self().currentLoopIndex);
Self().prevVolumes.push_back(channelVol);
Self().targetVolumes.push_back(channelVol);
Engine().SetVolume(trackID,channelVol*GetBGMVolume());
Engine().Play(trackID,Self().playParams.loop);
channelListIndex++;
#pragma region Handle threaded loop indexing
Self().currentLoopIndex++;
if(Self().currentLoopIndex>=track.GetChannelIDs().size()){
Self().channelPlayingComplete=true;
Self().fullyLoaded=true;
}
#pragma endregion
}
Self().fullyLoaded=true;
}else
if(!Self().fullyLoaded){
ERR("Invalid loading state or too many calls to initialize audio loop! The audio is still not fully loaded!");
}
}
}
void Audio::PlayBGM(const std::string_view sound,const bool loop){
PrepareBGM(sound,loop);
Self().immediatelyLoadAudio=true;
}
void Audio::Update(){
if(Self().fadeToTargetVolumeTime==0.f&&Self().playBGMWaitTime>0.f){
Self().playBGMWaitTime=std::max(Self().playBGMWaitTime-game->GetElapsedTime(),0.f);
if(Self().playBGMWaitTime==0.f&&Self().immediatelyLoadAudio){
while(!Self().BGMFullyLoaded()){
UpdateLoop(); //We immediately load the file. In a loading screen setting we would defer UpdateLoop() such that we have extra time to update the screen, UpdateLoop() is divided into many parts of the music loading process.
}
//Start playing the tracks.
Audio::BGM&track=Self().bgm[Self().GetTrackName()];
for(int trackID:track.GetChannelIDs()){
Self().Engine().Play(trackID,true);
}
}
}
if(Self().fadeToTargetVolumeTime>0.f){
Self().fadeToTargetVolumeTime=std::max(0.f,Self().fadeToTargetVolumeTime-game->GetElapsedTime());
for(int counter=0;float&vol:Self().prevVolumes){
@ -318,4 +376,9 @@ float&Audio::GetSFXVolume(){
float Audio::GetMuteMult(){
if(muted)return 0.f;
return 1.f;
}
int Audio::GetPrepareBGMLoopIterations(std::string_view sound){
BGM&newBgm=Self().bgm[std::string(sound)];
return newBgm.GetChannels().size()*2+2; //The channels list gets populated by calling newBgm.Load(), which then provides the list of channels that need to be loaded and played. This is why we multiply by 2. Each of the loading phases also consist of an initialization phase, so we add 2 as well.
}

@ -56,10 +56,13 @@ public:
static MiniAudio&Engine();
static void Initialize();
static void Update();
static void UpdateLoop();
static void Play(const std::string_view sound);
[[nodiscard]]
static const size_t LoadAndPlay(const std::string_view sound,const bool loop=true);
//Play a BGM given a name found in bgm.txt configuration file.
//Prepares a BGM for loading. This means we call UpdateLoop() repeatedly until the loading of the music is complete. Names are found in bgm.txt configuration file.
static void PrepareBGM(const std::string_view sound,const bool loop=true);
//Play immediately a BGM given a name found in bgm.txt configuration file.
static void PlayBGM(const std::string_view sound,const bool loop=true);
static void StopBGM();
static const Event&GetAudioEvent();
@ -72,7 +75,17 @@ public:
static float&GetBGMVolume();
static float&GetSFXVolume();
static float GetMuteMult();
//This will get a prepared BGM loop iteration count which is useful for loading stages.
static int GetPrepareBGMLoopIterations(std::string_view sound);
private:
bool trackLoadStarted=false;
bool trackLoadComplete=false;
bool channelPlayingStarted=false;
bool channelPlayingComplete=false;
int currentLoopIndex=0;
//Set to false by PrepareBGM(). If PlayBGM() is called instead, it will set the state of this variable to true, such that the loading is performed in Audio::Update()!
bool immediatelyLoadAudio=false;
struct BGMPlayParams{
std::string sound;
bool loop;
@ -92,7 +105,7 @@ private:
const size_t GetChannelCount()const;
const std::vector<ChannelName>&GetChannels()const;
const SongName&GetName()const;
const Volume&GetVolume(const Event&eventName,const ChannelID&id)const;
const Volume&GetVolume(const Event&eventName,const int&index)const;
void SetName(std::string_view name);
void SetFileName(std::string_view name);
void AddChannel(const ChannelName&name);

@ -69,11 +69,10 @@ protected:
vi2d descriptionPos=iconPos+vi2d{int(rect.size.y)-2,-1};
window.DrawShadowStringPropDecal(descriptionPos,ability->description,WHITE,BLACK,{0.8f,1.f},int(rect.size.x-(descriptionPos.x-rect.pos.x))-4);
InputType controlType=KEY;
if(Input::UsingGamepad())controlType=CONTROLLER;
if(textWidth>boxWidth){
window.DrawShadowStringPropDecal(textPos,ability->input->GetDisplayName(),WHITE,BLACK,{boxWidth/textWidth*0.5f,0.5});
}else{
window.DrawShadowStringPropDecal(textPos,ability->input->GetDisplayName(),WHITE,BLACK,{0.5,0.5});
}
ability->input->DrawInput(&window,textPos+vf2d{boxWidth/2,7},"",255,controlType,{0.5f,0.5f});
}
};

@ -50,7 +50,7 @@ protected:
float perspectiveFactor=6;
public:
inline CharacterRotatingDisplay(geom2d::rect<float>rect,Decal*icon)
:MenuComponent(rect,"",DO_NOTHING),icon(icon){}
:MenuComponent(rect,"",DO_NOTHING,ButtonAttr::UNSELECTABLE),icon(icon){}
inline void SetIcon(Decal*icon){
this->icon=icon;
}

@ -59,6 +59,8 @@ using BackdropName=std::string;
#define INCLUDE_WINDOW_SIZE extern vi2d WINDOW_SIZE;
#define INCLUDE_ITEM_CONVERSIONS extern safemap<std::string,IT>ITEM_CONVERSIONS;
#define INCLUDE_PACK_KEY extern std::string PACK_KEY;
#define INCLUDE_BACKDROP_DATA extern std::map<BackdropName,Renderable>BACKDROP_DATA;

@ -73,4 +73,5 @@ public:
virtual void GetAnyMousePress(int32_t mouseButton);
virtual void GetAnyMouseRelease(int32_t mouseButton);
static void ChangeState(States::State newState,float fadeOutDuration=0);
virtual void OnLevelLoad()=0;
};

@ -118,13 +118,13 @@ void ItemDrop::UpdateDrops(float fElapsedTime){
#pragma region Handle Upper/Lower Level Zone Intersecting
if(drop.speed.mag()>0){
std::map<std::string,std::vector<ZoneData>>&zoneData=game->GetZoneData(game->GetCurrentLevel());
for(ZoneData&upperLevelZone:zoneData["UpperZone"]){
const std::map<std::string,std::vector<ZoneData>>&zoneData=game->GetZones(game->GetCurrentLevel());
for(const ZoneData&upperLevelZone:zoneData.at("UpperZone")){
if(geom2d::overlaps(upperLevelZone.zone,drop.pos)){
drop.upperLevel=true;
}
}
for(ZoneData&lowerLevelZone:zoneData["LowerZone"]){
for(const ZoneData&lowerLevelZone:zoneData.at("LowerZone")){
if(geom2d::overlaps(lowerLevelZone.zone,drop.pos)){
drop.upperLevel=false;
}

@ -302,7 +302,7 @@ std::string InputGroup::GetDisplayName(){
return combinationDisplay;
}
void InputGroup::DrawInput(const std::variant<AiL*const,TileTransformedView*const>renderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha,InputType type)const{
void InputGroup::DrawInput(const std::variant<AiL*const,TileTransformedView*const,ViewPort*const>renderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha,InputType type,vf2d textScale)const{
std::optional<Input>primaryKey;
switch(type){
case CONTROLLER:primaryKey=GetPrimaryKey(CONTROLLER);break;
@ -317,16 +317,16 @@ void InputGroup::DrawInput(const std::variant<AiL*const,TileTransformedView*cons
for(const Input&input:keyOrder){
if(input.GetType()==MOUSE){
if(input.HasExtendedIcons()){
buttonImgSize.x+=input.GetIcon(GameSettings::GetIconType()).Sprite()->width+"Interface.InputHelperSpacing"_F;
buttonImgSize.x+=input.GetIcon(GameSettings::GetIconType()).Sprite()->width*textScale.x+"Interface.InputHelperSpacing"_F;
buttonImgSize.y=std::max(buttonImgSize.y,float(input.GetIcon(GameSettings::GetIconType()).Sprite()->height));
buttonImgs.push_back(input.GetIcon(GameSettings::GetIconType()).Decal());
}else
if(input.HasIcon()){
buttonImgSize.x+=input.GetIcon().Sprite()->width+"Interface.InputHelperSpacing"_F;
buttonImgSize.x+=input.GetIcon().Sprite()->width*textScale.x+"Interface.InputHelperSpacing"_F;
buttonImgSize.y=std::max(buttonImgSize.y,float(input.GetIcon().Sprite()->height));
buttonImgs.push_back(input.GetIcon().Decal());
}else{
buttonImgSize.x+=game->GetTextSizeProp(input.GetDisplayName()).x+"Interface.InputHelperSpacing"_F;
buttonImgSize.x+=game->GetTextSizeProp(input.GetDisplayName()).x*textScale.x+"Interface.InputHelperSpacing"_F;
buttonImgSize.y=std::max(buttonImgSize.y,float(game->GetTextSizeProp(input.GetDisplayName()).y)+"Interface.InputHelperSpacing"_F);
buttonImgs.push_back(input.GetDisplayName());
}
@ -336,16 +336,16 @@ void InputGroup::DrawInput(const std::variant<AiL*const,TileTransformedView*cons
if(primaryKey.has_value()){
if(primaryKey.value().HasExtendedIcons()){
buttonImgSize.x+=primaryKey.value().GetIcon(GameSettings::GetIconType()).Sprite()->width+"Interface.InputHelperSpacing"_F;
buttonImgSize.x+=primaryKey.value().GetIcon(GameSettings::GetIconType()).Sprite()->width*textScale.x+"Interface.InputHelperSpacing"_F;
buttonImgSize.y=std::max(buttonImgSize.y,float(primaryKey.value().GetIcon(GameSettings::GetIconType()).Sprite()->height));
buttonImgs.push_back(primaryKey.value().GetIcon(GameSettings::GetIconType()).Decal());
}else
if(primaryKey.value().HasIcon()){
buttonImgSize.x+=primaryKey.value().GetIcon().Sprite()->width+"Interface.InputHelperSpacing"_F;
buttonImgSize.x+=primaryKey.value().GetIcon().Sprite()->width*textScale.x+"Interface.InputHelperSpacing"_F;
buttonImgSize.y=std::max(buttonImgSize.y,float(primaryKey.value().GetIcon().Sprite()->height));
buttonImgs.push_back(primaryKey.value().GetIcon().Decal());
}else{
buttonImgSize.x+=game->GetTextSizeProp(primaryKey.value().GetDisplayName()).x+"Interface.InputHelperSpacing"_F;
buttonImgSize.x+=game->GetTextSizeProp(primaryKey.value().GetDisplayName()).x*textScale.x+"Interface.InputHelperSpacing"_F;
buttonImgSize.y=std::max(buttonImgSize.y,float(game->GetTextSizeProp(primaryKey.value().GetDisplayName()).y)+"Interface.InputHelperSpacing"_F);
buttonImgs.push_back(primaryKey.value().GetDisplayName());
}
@ -359,19 +359,22 @@ void InputGroup::DrawInput(const std::variant<AiL*const,TileTransformedView*cons
#pragma region Render Macro
#define Render(rendererType) \
std::get<rendererType*const>(renderer)->DrawDecal(pos+offset-vf2d{0.f,2.f},img,{1.f,1.f},{255,255,255,alpha});
std::get<rendererType*const>(renderer)->DrawDecal(pos+offset-vf2d{0.f,2.f},img,textScale,{255,255,255,alpha});
#pragma endregion
if(std::holds_alternative<AiL*const>(renderer)){
Render(AiL);
}else
if(std::holds_alternative<TileTransformedView*const>(renderer)){
Render(TileTransformedView);
}
offset.x+=img->sprite->width+"Interface.InputHelperSpacing"_I;
}else
if(std::holds_alternative<ViewPort*const>(renderer)){
Render(ViewPort);
}else ERR("Could not find proper renderer for rendering Inputs!");
offset.x+=img->sprite->width*textScale.x+"Interface.InputHelperSpacing"_I;
}else
if(std::holds_alternative<std::string>(button)){
std::string label=std::get<std::string>(button);
vf2d textSize=game->GetTextSizeProp(label);
vf2d textSize=game->GetTextSizeProp(label)*textScale;
Pixel buttonBackCol="Interface.InputButtonBackCol"_Pixel;
Pixel buttonTextCol="Interface.InputButtonTextCol"_Pixel;
buttonBackCol.a=alpha;
@ -382,7 +385,7 @@ void InputGroup::DrawInput(const std::variant<AiL*const,TileTransformedView*cons
std::get<rendererType*const>(renderer)->FillRectDecal(pos+offset+vf2d{-2.f,0.f},vf2d{textSize.x+4,textSize.y},buttonBackCol); \
std::get<rendererType*const>(renderer)->FillRectDecal(pos+offset+vf2d{-1.f,-1.f},vf2d{textSize.x+2,textSize.y},buttonBackCol); \
std::get<rendererType*const>(renderer)->FillRectDecal(pos+offset+vf2d{-1.f,0.f},vf2d{textSize.x+2,textSize.y+1.f},buttonBackCol); \
std::get<rendererType*const>(renderer)->DrawStringPropDecal(pos+offset+vf2d{0.f,0.f},label,buttonTextCol);
std::get<rendererType*const>(renderer)->DrawStringPropDecal(pos+offset+vf2d{0.f,0.f},label,buttonTextCol,textScale);
#pragma endregion
if(std::holds_alternative<AiL*const>(renderer)){
@ -390,15 +393,33 @@ void InputGroup::DrawInput(const std::variant<AiL*const,TileTransformedView*cons
}else
if(std::holds_alternative<TileTransformedView*const>(renderer)){
Render(TileTransformedView);
}
}else
if(std::holds_alternative<ViewPort*const>(renderer)){
Render(ViewPort);
}else ERR("Could not find proper renderer for rendering Inputs!");
offset.x+=textSize.x+"Interface.InputHelperSpacing"_I;
}else [[unlikely]]ERR("WARNING! Hit a state where no data is inside of the button! THIS SHOULD NOT BE HAPPENING!");
}
#pragma region Render Display Text
#pragma region Render Macro
#define Render(rendererType) \
std::get<rendererType*const>(renderer)->DrawShadowStringPropDecal(pos+offset,displayText,{255,255,255,alpha},{0,0,0,alpha},textScale);
#pragma endregion
game->view.DrawShadowStringPropDecal(pos+offset,displayText,{255,255,255,alpha},{0,0,0,alpha});
if(std::holds_alternative<AiL*const>(renderer)){
Render(AiL);
}else
if(std::holds_alternative<TileTransformedView*const>(renderer)){
Render(TileTransformedView);
}else
if(std::holds_alternative<ViewPort*const>(renderer)){
Render(ViewPort);
}else ERR("Could not find proper renderer for rendering Inputs!");
#pragma endregion
}
void InputGroup::DrawInput(const std::variant<AiL*const,TileTransformedView*const>renderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha)const{
void InputGroup::DrawInput(const std::variant<AiL*const,TileTransformedView*const,ViewPort*const>renderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha)const{
InputType primaryType;
if(Input::UsingGamepad())primaryType=CONTROLLER;
else if(Menu::UsingMouseNavigation())primaryType=MOUSE;

@ -44,6 +44,7 @@ All rights reserved.
#include "olcPGEX_TransformedView.h"
#include <variant>
#include "IconType.h"
#include "olcPGEX_ViewPort.h"
class AiL;
@ -119,8 +120,8 @@ public:
const float AnalogDAS(const float threshold=0.2f);
std::string GetDisplayName();
//Draws an input display with accompanying text centered at given position.
void DrawInput(const std::variant<AiL*const,TileTransformedView*const>renderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha)const;
void DrawInput(const std::variant<AiL*const,TileTransformedView*const>renderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha,const InputType type)const;
void DrawInput(const std::variant<AiL*const,TileTransformedView*const,ViewPort*const>renderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha)const;
void DrawInput(const std::variant<AiL*const,TileTransformedView*const,ViewPort*const>renderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha,const InputType type,vf2d textScale={1.f,1.f})const;
const std::optional<Input>GetPrimaryKey(InputType type)const;
};

@ -0,0 +1,108 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "AdventuresInLestoria.h"
#include "LoadingScreen.h"
#include "util.h"
INCLUDE_game
INCLUDE_WINDOW_SIZE
INCLUDE_ANIMATION_DATA
bool LoadingScreen::loading=false;
int LoadingScreen::currentProgress=0;
int LoadingScreen::totalProgress=0;
std::queue<std::function<bool()>>LoadingScreen::loadingPhases;
bool LoadingScreen::showGhost=false;
bool LoadingScreen::showLarge=false;
void LoadingScreen::DeferLoad(std::function<bool()>waitCondition){
AddPhase(waitCondition);
}
void LoadingScreen::Update(){
if(loading){
if(loadingPhases.size()>0){
std::function<bool()>&loadFunc=loadingPhases.front();
if(loadFunc()){
currentProgress++;
loadingPhases.pop();
}
}else{
loading=false;
GameState::STATE->OnLevelLoad();
}
}
}
void LoadingScreen::Draw(){
if(loading){
game->FillRectDecal({0,0},WINDOW_SIZE,{VERY_DARK_GREEN.r,VERY_DARK_GREEN.g,VERY_DARK_GREEN.b,230});
game->FillRectDecal({22.f,WINDOW_SIZE.y-46.f},{(float(currentProgress)/totalProgress)*(WINDOW_SIZE.x-48.f)+4.f,28.f},VERY_DARK_GREEN/2);
game->FillRectDecal({24.f,WINDOW_SIZE.y-48.f},{(float(currentProgress)/totalProgress)*(WINDOW_SIZE.x-48.f),24.f},DARK_YELLOW);
game->DrawShadowStringPropDecal({24.f,WINDOW_SIZE.y-60.f},"Loading...",{170,210,0},BLACK,{1.f,1.5f});
vf2d playerScale=vf2d(game->GetPlayer()->GetSizeMult(),game->GetPlayer()->GetSizeMult());
float scale=1.f;
if(showLarge){
scale=2.f;
}
const std::vector<Buff>attackBuffs=game->GetPlayer()->GetStatBuffs({"Attack","Attack %"});
Pixel blendCol=attackBuffs.size()>0?Pixel{255,uint8_t(255*abs(sin(1.4*attackBuffs[0].duration))),uint8_t(255*abs(sin(1.4*attackBuffs[0].duration)))}:WHITE;
if(showGhost){
blendCol=BLACK;
}
game->GetPlayer()->GetWalkEAnimation();
Animate2D::FrameSequence&playerWalkE=ANIMATION_DATA[game->GetPlayer()->GetWalkEAnimation()];
game->DrawPartialRotatedDecal({(float(currentProgress)/totalProgress)*(WINDOW_SIZE.x-48.f),WINDOW_SIZE.y-36.f},playerWalkE.GetFrame(game->GetRuntime()).GetSourceImage()->Decal(),game->GetPlayer()->GetSpinAngle(),{12,12},playerWalkE.GetFrame(game->GetRuntime()).GetSourceRect().pos,playerWalkE.GetFrame(game->GetRuntime()).GetSourceRect().size,playerScale*scale,blendCol);
}
}
void LoadingScreen::Reset(){
currentProgress=0;
totalProgress=0;
showGhost=util::random()%6==0;
showLarge=util::random()%6==0;
while(loadingPhases.size()>0)ERR("WARNING! Previous loading phase was not properly cleared! This probably means some part of a previous load did not execute!");
}
void LoadingScreen::AddPhase(std::function<bool()>loadFunc){
loadingPhases.push(loadFunc);
totalProgress++;
}

@ -0,0 +1,57 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#pragma once
#include <queue>
#include "olcUTIL_Animate2D.h"
class LoadingScreen{
public:
static bool loading;
static int currentProgress;
static int totalProgress;
static float waitTime;
static bool showGhost;
static bool showLarge;
static std::queue<std::function<bool()>>loadingPhases;
static void Update();
static void Draw();
static void DeferLoad(std::function<bool()>waitCondition);
static void Reset();
static void AddPhase(std::function<bool()>loadFunc);
};

@ -455,13 +455,13 @@ bool Monster::SetPos(vf2d pos){
return resultX||resultY;
}
void Monster::Moved(){
std::map<std::string,std::vector<ZoneData>>&zoneData=game->GetZoneData(game->GetCurrentLevel());
for(ZoneData&upperLevelZone:zoneData["UpperZone"]){
const std::map<std::string,std::vector<ZoneData>>&zoneData=game->GetZones(game->GetCurrentLevel());
for(const ZoneData&upperLevelZone:zoneData.at("UpperZone")){
if(geom2d::overlaps(upperLevelZone.zone,pos)){
upperLevel=true;
}
}
for(ZoneData&lowerLevelZone:zoneData["LowerZone"]){
for(const ZoneData&lowerLevelZone:zoneData.at("LowerZone")){
if(geom2d::overlaps(lowerLevelZone.zone,pos)){
upperLevel=false;
}

@ -783,13 +783,13 @@ void Player::Moved(){
spawner.SetTriggered(true);
}
}
std::map<std::string,std::vector<ZoneData>>&zoneData=game->GetZoneData(game->GetCurrentLevel());
for(ZoneData&upperLevelZone:zoneData["UpperZone"]){
const std::map<std::string,std::vector<ZoneData>>&zoneData=game->GetZones(game->GetCurrentLevel());
for(const ZoneData&upperLevelZone:zoneData.at("UpperZone")){
if(geom2d::overlaps(upperLevelZone.zone,pos)){
upperLevel=true;
}
}
for(ZoneData&lowerLevelZone:zoneData["LowerZone"]){
for(const ZoneData&lowerLevelZone:zoneData.at("LowerZone")){
if(geom2d::overlaps(lowerLevelZone.zone,pos)){
upperLevel=false;
}
@ -1349,4 +1349,13 @@ void Player::UpdateHealthAndMana(){
hp=std::min(hp,int(GetStat("Health")));
mana=std::min(mana,GetMaxMana());
}
}
void Player::SetInvisible(const bool invisibleState){
invisibility=invisibleState;
}
const bool Player::IsInvisible()const{
return invisibility;
}

@ -245,6 +245,8 @@ public:
void SetXP(const uint32_t xp);
void SetTotalXPEarned(const uint32_t totalXP);
void SetLevel(uint8_t newLevel);
void SetInvisible(const bool invisibleState);
const bool IsInvisible()const;
private:
int hp="Warrior.BaseHealth"_I;
int mana="Player.BaseMana"_I;
@ -293,6 +295,7 @@ private:
uint32_t money="Player.Starting Money"_I;
EntityStats stats;
ItemAttribute&Get(std::string_view attr);
bool invisibility=false;
//Returns true if the move was valid and successful.
//If playerInvoked is true, this means the player was the one that instantiated this input, and it's not an extra movement done via collision.
//Set playerInvoked to false when you don't want a movement loop due to collisions.

@ -49,23 +49,31 @@ INCLUDE_game
std::multimap<EventName,SoundEffect>SoundEffect::SOUND_EFFECTS;
const vf2d SoundEffect::CENTERED={-8419.f,-3289.f};
SoundEffect::SoundEffect(const std::string_view filename,const float&vol,const float&minPitch,const float&maxPitch)
:filename(filename),vol(vol),minPitch(minPitch),maxPitch(maxPitch){
SoundEffect::SoundEffect(const std::string_view filename,const float&vol,const float&minPitch,const float&maxPitch,const bool combatSound)
:filename(filename),vol(vol),minPitch(minPitch),maxPitch(maxPitch),combatSound(combatSound){
if(vol<0.f||vol>1.f)ERR(std::format("WARNING! Volume must be between 0.0f ~ 1.0f! Provided value {}",vol));
}
void SoundEffect::Initialize(){
for(auto&[key,size]:DATA["Events"]["SFX"]){
int counter=0;
bool combatSound=false;
if(DATA["Events"]["SFX"][key].HasProperty("CombatSound")){
combatSound=DATA["Events"]["SFX"][key]["CombatSound"].GetBool();
}
while(DATA["Events"]["SFX"][key].HasProperty(std::format("File[{}]",counter))){
utils::datafile&data=DATA["Events"]["SFX"][key][std::format("File[{}]",counter)];
float minPitch=0.9f;
float maxPitch=1.1f;
if(data.GetValueCount()>=3){minPitch=data.GetInt(2)/100.f;}
if(data.GetValueCount()>=4){maxPitch=data.GetInt(3)/100.f;}
SOUND_EFFECTS.insert({key,SoundEffect{data.GetString(0),data.GetInt(1)/100.f,minPitch,maxPitch}});
SOUND_EFFECTS.insert({key,SoundEffect{data.GetString(0),data.GetInt(1)/100.f,minPitch,maxPitch,combatSound}});
counter++;
}
auto itr=SOUND_EFFECTS.equal_range(key);
for(auto it=itr.first;it!=itr.second;++it){
it->second.combatSound=combatSound;
}
}
}
@ -84,6 +92,8 @@ void SoundEffect::PlaySFX(const std::string_view eventName,const vf2d&pos){
}
const SoundEffect&sfx=(*it).second;
if(GameState::STATE==GameState::states[States::MAIN_MENU])return; //Do not play combat sounds on the main menu.
float pitchDiff=sfx.maxPitch-sfx.minPitch;
float pitch=util::random(pitchDiff)+sfx.minPitch;

@ -45,7 +45,7 @@ using EventName=std::string;
class SoundEffect{
public:
SoundEffect(const std::string_view filename,const float&vol,const float&minPitch=0.9f,const float&maxPitch=1.1f);
SoundEffect(const std::string_view filename,const float&vol,const float&minPitch=0.9f,const float&maxPitch=1.1f,const bool combatSound=false);
static void PlaySFX(const std::string_view eventName,const vf2d&pos);
static void Initialize();
static const vf2d CENTERED;
@ -53,6 +53,7 @@ private:
static std::multimap<EventName,SoundEffect>SOUND_EFFECTS;
std::string filename;
float vol;
bool combatSound=false;
float minPitch=0.9f;
float maxPitch=1.1f;
};

@ -59,6 +59,8 @@ void State_GameHub::OnStateChange(GameState*prevState){
game->GetPlayer()->SetState(State::NORMAL);
game->LoadLevel("HUB");
}
void State_GameHub::OnLevelLoad(){
game->UpdateDiscordStatus("Hub Area",game->GetPlayer()->GetClassName());
}
void State_GameHub::OnUserUpdate(AiL*game){

@ -45,4 +45,5 @@ class State_GameHub:public State_GameRun{
virtual void OnStateChange(GameState*prevState)override final;
virtual void OnUserUpdate(AiL*game)override final;
virtual void Draw(AiL*game)override final;
virtual void OnLevelLoad()override final;
};

@ -73,6 +73,7 @@ void State_GameRun::OnStateChange(GameState*prevState){
game->LoadLevel(State_OverworldMap::GetCurrentConnectionPoint().map);
}
void State_GameRun::OnLevelLoad(){}
void State_GameRun::OnUserUpdate(AiL*game){
game->bossDisplayTimer=std::max(0.f,game->bossDisplayTimer-game->GetElapsedTime());
if(game->encounterStarted&&game->totalBossEncounterMobs>0){
@ -84,13 +85,7 @@ void State_GameRun::OnUserUpdate(AiL*game){
game->UpdateEffects(game->GetElapsedTime());
GameEvent::UpdateEvents();
game->GetPlayer()->Update(game->GetElapsedTime());
for(Monster&m:MONSTER_LIST){
m.Update(game->GetElapsedTime());
}
for(Monster&m:game->monstersToBeSpawned){
MONSTER_LIST.push_back(m);
}
game->monstersToBeSpawned.clear();
game->UpdateMonsters();
ItemDrop::UpdateDrops(game->GetElapsedTime());
game->UpdateBullets(game->GetElapsedTime());

@ -46,6 +46,7 @@ protected:
virtual void OnStateChange(GameState*prevState)override;
virtual void OnUserUpdate(AiL*game)override;
virtual void Draw(AiL*game)override;
virtual void OnLevelLoad()override;
void FontTest();
void FontSpriteTest();
};

@ -76,6 +76,7 @@ void State_LevelComplete::OnStateChange(GameState*prevState){
game->GetPlayer()->SetState(State::NORMAL);
Menu::OpenMenu(LEVEL_COMPLETE);
};
void State_LevelComplete::OnLevelLoad(){}
void State_LevelComplete::OnUserUpdate(AiL*game){
if(levelUpTimer>0.f){
levelUpTimer=std::max(0.f,levelUpTimer-game->GetElapsedTime());

@ -48,4 +48,5 @@ class State_LevelComplete:public GameState{
virtual void OnUserUpdate(AiL*game)override final;
virtual void Draw(AiL*game)override final;
virtual void DrawOverlay(AiL*game)override final;
virtual void OnLevelLoad()override final;
};

@ -40,6 +40,8 @@ All rights reserved.
#include "Menu.h"
#include "TitleScreen.h"
#include "Key.h"
#include "ItemDrop.h"
#include "util.h"
INCLUDE_game
@ -47,13 +49,64 @@ void State_MainMenu::OnStateChange(GameState*prevState){
Audio::PlayBGM("title_screen");
TitleScreen::Reset();
game->UpdateDiscordStatus("Main Menu","");
game->LoadLevel("starting_map"_S,AiL::NO_MUSIC_CHANGE);
};
void State_MainMenu::OnLevelLoad(){
game->GetPlayer()->SetIframes(999999.f);
game->GetPlayer()->SetInvisible(true);
SelectAndMoveToNewFocusArea();
}
void State_MainMenu::OnUserUpdate(AiL*game){
game->GetPlayer()->ForceSetPos(game->GetPlayer()->GetPos()+cameraMoveDir*8*game->GetElapsedTime());
lastMoveTime+=game->GetElapsedTime();
if(lastMoveTime>8.f)SelectAndMoveToNewFocusArea();
TitleScreen::Update();
if(AiL::KEY_CONFIRM.Released()){
TitleScreen::Skip();
}
game->UpdateEffects(game->GetElapsedTime());
GameEvent::UpdateEvents();
game->UpdateMonsters();
ItemDrop::UpdateDrops(game->GetElapsedTime());
game->UpdateBullets(game->GetElapsedTime());
game->UpdateCamera(game->GetElapsedTime());
};
void State_MainMenu::Draw(AiL*game){
TitleScreen::Draw();
};
};
const ZoneData&State_MainMenu::ChooseRandomFocusArea(){
//std::vector<ZoneData>
if(game->GetZones().count("Focus Area")>0){
const std::vector<ZoneData>&zones=game->GetZones().at("Focus Area");
newSelectedFocusAreaIndex=util::random()%zones.size();
return zones[newSelectedFocusAreaIndex];
}else ERR("WARNING! No focus areas included in the intro map!");
return game->GetZones().at("Focus Area")[0];
}
void State_MainMenu::SelectAndMoveToNewFocusArea(){
const ZoneData&newFocusArea=ChooseRandomFocusArea();
if(lastSelectedFocusAreaIndex==newSelectedFocusAreaIndex)return;
game->camera.MoveCamera(newFocusArea.zone.pos);
game->GetPlayer()->ForceSetPos(newFocusArea.zone.pos);
cameraMoveDir={};
for(const XMLTag&tag:newFocusArea.properties){
if(tag.data.at("name")=="Scroll Direction"){
std::string_view dir=tag.data.at("value");
if(dir=="NORTH"sv)cameraMoveDir={0.f,-1.f};else
if(dir=="NORTHEAST"sv)cameraMoveDir={1.f,-1.f};else
if(dir=="EAST"sv)cameraMoveDir={1.f,0.f};else
if(dir=="SOUTHEAST"sv)cameraMoveDir={1.f,1.f};else
if(dir=="SOUTH"sv)cameraMoveDir={0.f,1.f};else
if(dir=="SOUTHWEST"sv)cameraMoveDir={-1.f,1.f};else
if(dir=="WEST"sv)cameraMoveDir={-1.f,0.f};else
if(dir=="NORTHWEST"sv)cameraMoveDir={-1.f,-1.f};
}
}
lastMoveTime=0.f;
lastSelectedFocusAreaIndex=newSelectedFocusAreaIndex;
}

@ -36,9 +36,18 @@ All rights reserved.
*/
#pragma endregion
#include "GameState.h"
#include "TMXParser.h"
class State_MainMenu:public GameState{
virtual void OnStateChange(GameState*prevState)override final;
virtual void OnUserUpdate(AiL*game)override final;
virtual void Draw(AiL*game)override final;
virtual void OnLevelLoad()override final;
const ZoneData&ChooseRandomFocusArea();
vf2d cameraMoveDir;
float lastMoveTime=0.f;
size_t newSelectedFocusAreaIndex=0;
size_t lastSelectedFocusAreaIndex=0;
void SelectAndMoveToNewFocusArea();
};

@ -63,6 +63,8 @@ void State_OverworldMap::OnStateChange(GameState*prevState){
Component<MenuComponent>(MenuType::PAUSE,"Return to Camp Button")->SetGrayedOut(false);
SaveFile::SaveGame();
game->LoadLevel("WORLD_MAP");
};
void State_OverworldMap::OnLevelLoad(){
if(Menu::IsMenuOpen()){
Menu::CloseAllMenus();
}
@ -82,7 +84,7 @@ void State_OverworldMap::OnStateChange(GameState*prevState){
}
Menu::OpenMenu(OVERWORLD_LEVEL_SELECT,false);
game->UpdateDiscordStatus("Overworld Map",game->GetPlayer()->GetClassName());
};
}
void State_OverworldMap::OnUserUpdate(AiL*game){
if(Menu::stack.size()>1)return;

@ -60,6 +60,7 @@ public:
virtual void OnUserUpdate(AiL*game)override final;
virtual void Draw(AiL*game)override final;
virtual void DrawOverlay(AiL*game)override final;
virtual void OnLevelLoad()override final;
static void StartLevel();
static void UpdateCurrentConnectionPoint(const ConnectionPoint&connection);
};

@ -42,6 +42,7 @@ All rights reserved.
void State_Story::OnStateChange(GameState*prevState){
Menu::CloseAllMenus();
};
void State_Story::OnLevelLoad(){}
void State_Story::OnUserUpdate(AiL*game){
VisualNovel::novel.Update();
};

@ -42,4 +42,5 @@ class State_Story:public GameState{
virtual void OnStateChange(GameState*prevState)override final;
virtual void OnUserUpdate(AiL*game)override final;
virtual void Draw(AiL*game)override final;
virtual void OnLevelLoad()override final;
};

@ -98,6 +98,7 @@ struct SpawnerTag{
struct ZoneData{
geom2d::rect<int>zone;
bool isUpper=false;
std::vector<XMLTag>properties;
};
struct NPCData{
@ -478,7 +479,11 @@ class TMXParser{
}
} else
if (newTag.tag=="object"&&newTag.data["type"]=="PlayerSpawnLocation") {
parsedMapInfo.MapData.playerSpawnLocation={newTag.GetInteger("x")-newTag.GetInteger("width")/2,newTag.GetInteger("y")-newTag.GetInteger("height")/2};
float width=1.f;
float height=1.f;
if(newTag.data.count("width")>0)width=newTag.GetFloat("width");
if(newTag.data.count("height")>0)height=newTag.GetFloat("height");
parsedMapInfo.MapData.playerSpawnLocation={int(newTag.GetFloat("x")-width/2),int(newTag.GetFloat("y")-height/2)};
} else
if (newTag.tag=="object"&&newTag.data["type"]=="NPC") {
if(inNPCTag)parsedMapInfo.npcs.push_back(NPCData{npcTag});
@ -513,19 +518,19 @@ class TMXParser{
if(newTag.tag=="property"&&currentStagePlate!=nullptr){
currentStagePlate->properties[newTag.data["name"]]={newTag.data["name"],newTag.data["value"]};
}else
if(newTag.tag=="property"&&prevZoneData!=nullptr){
//This is a property for a zone that doesn't fit into the other categories, we add it to the previous zone data encountered.
prevZoneData->properties.push_back(newTag);
}else
if (newTag.tag=="object"&&newTag.data.find("type")!=newTag.data.end()){
//This is an object with a type that doesn't fit into other categories, we can add it to ZoneData.
if(parsedMapInfo.ZoneData.find(newTag.data["type"])!=parsedMapInfo.ZoneData.end()){
std::vector<ZoneData>&zones=parsedMapInfo.ZoneData.at(newTag.data["type"]);
zones.emplace_back(geom2d::rect<int>{{newTag.GetInteger("x"),newTag.GetInteger("y")},{newTag.GetInteger("width"),newTag.GetInteger("height")}});
prevZoneData=&zones.back();
} else {
if(newTag.data["width"].length()>0&&newTag.data["height"].length()>0){ //This ensures the zone is valid to begin with.
std::vector<ZoneData>&zones=parsedMapInfo.ZoneData[newTag.data["type"]];
zones.emplace_back(geom2d::rect<int>{{newTag.GetInteger("x"),newTag.GetInteger("y")},{newTag.GetInteger("width"),newTag.GetInteger("height")}});
prevZoneData=&zones.back();
}
}
std::vector<ZoneData>&zones=parsedMapInfo.ZoneData[newTag.data["type"]];
float width=1.f;
float height=1.f;
if(newTag.data.count("width")>0)width=newTag.GetFloat("width");
if(newTag.data.count("height")>0)height=newTag.GetFloat("height");
zones.emplace_back(geom2d::rect<int>{{newTag.GetInteger("x"),newTag.GetInteger("y")},{int(width),int(height)}});
prevZoneData=&zones.back();
}else{
#ifdef _DEBUG
if(_DEBUG_MAP_LOAD_INFO)std::cout<<"Unsupported tag format! Ignoring."<<"\n";

@ -1,27 +1,26 @@
January 1st
===========
February 28th -> Begin Internal Game Playtesting
March 6th -> Discord/Friend Playtesting
March 30th -> Public Demo Release
- Foreground tile depth correction for tiles w/hitboxes
- Add Death screen (Zoom in on fatal blow, slow time down... Display some game over text... Allow retry or return to world map.)
- Track items used during a stage, on death, restore the loadout item quantities used.
- Icon displays / Proper key displays above skill keys
- Mosaic transition on level load
Add Bonus XP when completing a stage
January 31st
============
- Loading Screen
- Title Screen setpieces
- Hide mouse cursor during controller play. Reveal it again during mouse play.
- Auto aim causes retreat-type moves to aim away from the auto target, and prefer the direction the player's moving in.
- Condense stage track (loading times)
- Credits/Licensing
- Credits/Licensing
- Basic tutorial on the first stage, Only allow the player to select "Change Loadout", explain how to setup items, can only start once a loadout item is set.
- show inputs that can be used by the player to navigate, ability usage, and defensive. When player takes enough damage show how to use recovery items.

@ -54,8 +54,8 @@ void Test::is(std::string conditionStr,bool testResult){
void Test::RunMapTests(){
is("There are two LowerBridgeCollision zones in Campaign I-I",
game->GetZoneData("CAMPAIGN_1_1").count("LowerBridgeCollision")
&&game->GetZoneData("CAMPAIGN_1_1").at("LowerBridgeCollision").size()>=2);
game->GetZones("CAMPAIGN_1_1").count("LowerBridgeCollision")
&&game->GetZones("CAMPAIGN_1_1").at("LowerBridgeCollision").size()>=2);
for(auto&[key,value]:game->MAP_DATA){
is("A Map type has been selected for map "+key,
value.GetMapType()!=""&&value.GetMapType()!="Unspecified");

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 0
#define VERSION_MINOR 3
#define VERSION_PATCH 0
#define VERSION_BUILD 7515
#define VERSION_BUILD 7609
#define stringify(a) stringify_(a)
#define stringify_(a) #a

@ -193,6 +193,9 @@ void VisualNovel::Update(){
locationDisplayTime=std::max(0.f,locationDisplayTime-game->GetElapsedTime());
transitionTime=std::max(0.f,transitionTime-game->GetElapsedTime());
textScrollTime=std::max(0.f,textScrollTime-game->GetElapsedTime());
if(backgroundScrollAmt<90.f){
backgroundScrollAmt=std::min(90.f,backgroundScrollAmt+backgroundScrollSpd*game->GetElapsedTime());
}
}
void VisualNovel::ExecuteNextCommand(){
if(commandIndex<commands.size()){
@ -211,9 +214,9 @@ void VisualNovel::Draw(){
alpha=util::lerp(0,1,1-(transitionTime/maxTransitionTime));
}
if(prevBackgroundFilename!=""){
game->DrawDecal({0,0},GFX["story_background_image_location"_S+prevBackgroundFilename].Decal());
game->DrawDecal({0,-prevBackgroundScrollAmt},GFX["story_background_image_location"_S+prevBackgroundFilename].Decal());
}
game->DrawDecal({0,0},GFX["story_background_image_location"_S+backgroundFilename].Decal(),{1,1},{255,255,255,uint8_t(255*alpha)});
game->DrawDecal({0,-backgroundScrollAmt},GFX["story_background_image_location"_S+backgroundFilename].Decal(),{1,1},{255,255,255,uint8_t(255*alpha)});
}else{
game->FillRectDecal({0,0},game->GetScreenSize());
}
@ -289,6 +292,8 @@ void BackgroundCommand::Execute(VisualNovel&vn){
vn.prevBackgroundFilename=vn.backgroundFilename;
vn.backgroundFilename=backgroundFilename;
vn.transitionTime=2.0f;
vn.prevBackgroundScrollAmt=vn.backgroundScrollAmt;
vn.backgroundScrollAmt=0.f;
vn.ExecuteNextCommand();
}
BackgroundCommand::BackgroundCommand(std::string backgroundFilename)
@ -323,6 +328,7 @@ SpeakerCommand::SpeakerCommand(std::string displayedName,std::string speaker)
CommandType::CommandType SpeakerCommand::GetType(){return CommandType::SPEAKER;}
void DialogCommand::Execute(VisualNovel&vn){
if(dialog.size()<=0)return;
vn.textScrollTime=VisualNovel::maxTextScrollTime;
bool mustDisplay=vn.activeText.length()==0;
Font*displayFont=&VisualNovel::font;

@ -73,7 +73,7 @@ public:
};
class DialogCommand final:public Command{
std::string dialog;
std::string dialog="";
public:
void Execute(VisualNovel&vn)override;
DialogCommand(std::string dialog);
@ -143,12 +143,15 @@ class VisualNovel{
std::string prevBackgroundFilename;
float transitionTime=0;
static constexpr float maxTransitionTime=2.0f;
const float backgroundScrollSpd=2.0f;
std::vector<Command*>commands;
int commandIndex=0;
std::string locationDisplayText="";
float locationDisplayTime=0;
std::string prevTheme="";
float textScrollTime=0;
float backgroundScrollAmt=0;
float prevBackgroundScrollAmt=0;
static constexpr float maxTextScrollTime=1.0f;
public:
static Font font,narratorFont,locationFont;

File diff suppressed because it is too large Load Diff

@ -571,7 +571,7 @@
<property name="Connection 4 - West" type="object" value="7"/>
<property name="Map" propertytype="Level" value="CAMPAIGN_1_2"/>
<property name="Type" propertytype="StageType" value="DUNGEON"/>
<property name="Unlock Condition" propertytype="Level" value="CAMPAIGN_1_1"/>
<property name="Unlock Condition" propertytype="Level" value="STORY_1_1"/>
</properties>
</object>
<object id="5" name="Story I" type="StagePlate" x="344" y="476" width="20" height="24">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

@ -9,6 +9,7 @@ Events
{
Bear Slam Attack
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = bear_slam.ogg, 70%
}
@ -112,70 +113,83 @@ Events
}
Monster Hurt
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = monster_hurt.ogg, 40%
}
Player Hit
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = player_hit1.ogg, 40%
File[1] = player_hit2.ogg, 100%
}
Ranger Auto Attack
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = ranger_auto1.ogg, 50%
File[1] = ranger_auto2.ogg, 50%
}
Ranger Retreat
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = ranger_backstep.ogg, 90%
}
Ranger Multishot
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = ranger_multishot.ogg, 100%
}
Ranger Rapid Fire
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = ranger_rapid_fire.ogg, 100%
}
Ranger Charged Shot
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = ranger_charged_shot.ogg, 70%
}
Slime Dead
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = slime_dead.ogg, 60%
File[1] = slime_dead2.ogg, 60%
}
Monster Dead
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = slime_dead2.ogg, 60%
}
Slime King Land
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = slime_king_landing.ogg, 100%
}
Slime King Shoot
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = slime_king_shoot.ogg, 60%
}
Slime Shoot
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = slime_shoot.ogg, 100%
File[1] = slime_shoot2.ogg, 80%
}
Slime Walk
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = slime_walk.ogg, 10%
File[1] = slime_walk2.ogg, 10%
@ -203,37 +217,44 @@ Events
}
Ursule Dead
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = ursule_dead.ogg, 100%
}
Ursule Phase Transition
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = ursule_phase_transition.ogg, 100%
}
Warrior Auto Attack
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = warrior_auto1.ogg, 60%
}
Warrior Battlecry
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = warrior_battlecry.ogg, 100%
}
Warrior Block Hit
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = warrior_blockhit1.ogg, 100%
File[1] = warrior_blockhit2.ogg, 100%
}
Warrior Ground Slam
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = warrior_groundslam.ogg, 100%
}
Warrior Sonic Slash
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = warrior_sonicslash.ogg, 70%
}
@ -255,31 +276,37 @@ Events
}
Wizard Fire Bolt Hit
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = wizard_firebolt_hit.ogg, 100%
}
Wizard Lightning Bolt Shoot
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = wizard_lightningbolt.ogg, 60%
}
Wizard Lightning Bolt Hit
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = wizard_firebolt_hit.ogg, 100%
}
Wizard Meteor
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = wizard_meteor.ogg, 100%
}
Wizard Meteor Flames
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = wizard_meteor_lingering.ogg, 100%
}
Wizard Teleport
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = wizard_teleport.ogg, 100%
}

@ -20,7 +20,7 @@ save_server = https://projectdivar.com:4505/AiL
map_config = levels.txt
# Starting map when loading the game.
starting_map = WORLD_MAP
starting_map = INTRO_MAP
# Player Properties Loading Config
player_config = Player.txt

@ -2,6 +2,11 @@ map_path = assets/Campaigns/
Levels
{
INTRO_MAP
{
Map File = Intro_Map.tmx
}
WORLD_MAP
{
Map File = World_Map.tmx

@ -14,6 +14,7 @@ Merchant crossing is a Crossway that connects Bleakport, the Lestorian Forest, E
The crossing is famous for merchants to rest on their travels and trade goods with each other.
{BACKGROUND commercial_assets/Burning Trading Post.png}
While you are getting closer you realise something isnt right. The Smoke in the sky is way thicker then what you would expect from just a few campfires...
You reach the crossing where a #FF0000horrible scenery#FFFFFF awaits you.
@ -110,7 +111,7 @@ Oh what shall I do? I can't return to the Kingdom without it!
[You]
First you need more rest. Once you are able to walk on your own again, we can think about a solution for your lost object.
{BACKGROUND rest.png}
{BACKGROUND commercial_assets/Forest Clearing Campsite.png}
[]
You set up a tent and a campfire a little outside of the main camp where the tragedy happened and helped Sherman over.
@ -121,9 +122,9 @@ Without the corpses surrounding you, you relax a little bit.
You keep an eye on Sherman as he recovers.
{BACKGROUND sea.png}
The next day you wake to find Sherman no longer lying down but is instead sitting on the grass.
{BACKGROUND commercial_assets/Forest Clearing Campsite_Day.png}
[Sherman]
Good Morning! You told me you are an Adventurer right?

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.10" tiledversion="1.10.1" name="112x96_Forge_No_Shadow_12x12" tilewidth="12" tileheight="12" tilecount="336" columns="56">
<tileset version="1.10" tiledversion="1.10.2" name="112x96_Forge_No_Shadow_12x12" tilewidth="12" tileheight="12" tilecount="336" columns="56">
<image source="commercial_assets/112x96_Forge_No_Shadow_12x12.png" width="672" height="72"/>
<tile id="0" type="ForegroundTile">
<properties>

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.10" tiledversion="1.10.1" name="Decorations_c1_No_Shadow24x24" tilewidth="24" tileheight="24" tilecount="1620" columns="45">
<tileset version="1.10" tiledversion="1.10.2" name="Decorations_c1_No_Shadow24x24" tilewidth="24" tileheight="24" tilecount="1620" columns="45">
<image source="commercial_assets/Decorations_c1_No_Shadow24x24.png" width="1080" height="864"/>
<tile id="68" type="ForegroundTile"/>
<tile id="73" type="ForegroundTile"/>

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.10" tiledversion="1.10.1" name="Minifantasy_TinyOverworldAllTiles" class="Terrain" tilewidth="4" tileheight="4" tilecount="8137" columns="79">
<tileset version="1.10" tiledversion="1.10.2" name="Minifantasy_TinyOverworldAllTiles" class="Terrain" tilewidth="4" tileheight="4" tilecount="8137" columns="79">
<image source="commercial_assets/Minifantasy_TinyOverworldAllTiles.png" width="316" height="412"/>
</tileset>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

@ -159,10 +159,18 @@ namespace olc {
FT_Set_Transform(toUse->fontFace, &rotMat, &pen);
FT_Error error = FT_Load_Char(toUse->fontFace, chr, FT_LOAD_RENDER);
if(error){
std::cout<<"FT Error: "<<error<<std::endl;
continue;
}
FT_GlyphSlot slot = toUse->fontFace->glyph;
FT_Glyph glyph;
FT_Get_Glyph(slot, &glyph);
error = FT_Get_Glyph(slot, &glyph);
if(error){
std::cout<<"FT Error: "<<error<<std::endl;
continue;
}
FT_BBox bbox;
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_GRIDFIT, &bbox);

@ -10,6 +10,7 @@ cp -R "Adventures in Lestoria/assets/music" bin/assets
cp -R "Adventures in Lestoria/assets/npcs" bin/assets
cp -R "Adventures in Lestoria/assets/sounds" bin/assets
cp -R "Adventures in Lestoria/assets/gamepack.pak" bin/assets
cp -R "Adventures in Lestoria/assets/*.ttf" bin/assets
cp -R "x64/Release/*" bin
rm bin/*.pdb

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save