Downstream merge from demo branch. Fixes Steam crash when Steam is not installed.

pull/57/head
sigonasr2 10 months ago
commit df28a12c63
  1. 9
      Adventures in Lestoria/Adventures in Lestoria.vcxproj
  2. 12
      Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters
  3. 162
      Adventures in Lestoria/AdventuresInLestoria.cpp
  4. 6
      Adventures in Lestoria/AdventuresInLestoria.h
  5. 8
      Adventures in Lestoria/Bear.cpp
  6. 3
      Adventures in Lestoria/BulletTypes.h
  7. 2
      Adventures in Lestoria/DEFINES.h
  8. 2
      Adventures in Lestoria/Frog.cpp
  9. 34
      Adventures in Lestoria/FrogTongue.cpp
  10. 12
      Adventures in Lestoria/LightningBolt.cpp
  11. 2
      Adventures in Lestoria/Map.h
  12. 177
      Adventures in Lestoria/Minimap.cpp
  13. 55
      Adventures in Lestoria/Minimap.h
  14. 14
      Adventures in Lestoria/Monster.cpp
  15. 2
      Adventures in Lestoria/Pathfinding.cpp
  16. 26
      Adventures in Lestoria/Player.cpp
  17. 5
      Adventures in Lestoria/TODO.txt
  18. 2
      Adventures in Lestoria/Version.h
  19. 18
      Adventures in Lestoria/Warrior.cpp
  20. 2
      Adventures in Lestoria/assets/Campaigns/1_2.tmx
  21. 2
      Adventures in Lestoria/assets/Campaigns/1_3.tmx
  22. 2
      Adventures in Lestoria/assets/Campaigns/Boss_1_B.tmx
  23. 3
      Adventures in Lestoria/assets/Campaigns/World_Map.tmx
  24. 3
      Adventures in Lestoria/assets/config/configuration.txt
  25. 8
      Adventures in Lestoria/assets/config/minimap.txt
  26. 2
      Adventures in Lestoria/assets/maps/End_of_Map.tmx
  27. BIN
      Adventures in Lestoria/assets/screenshot10.png
  28. BIN
      Adventures in Lestoria/assets/screenshot11.png
  29. BIN
      Adventures in Lestoria/assets/screenshot12.png
  30. BIN
      Adventures in Lestoria/assets/screenshot8.png
  31. BIN
      Adventures in Lestoria/assets/screenshot9.png
  32. 16
      Adventures in Lestoria/olcPGEX_ViewPort.h
  33. 73
      Adventures in Lestoria/olcPixelGameEngine.h
  34. BIN
      x64/Release/Adventures in Lestoria.exe

@ -411,6 +411,10 @@
<SubType> <SubType>
</SubType> </SubType>
</ClInclude> </ClInclude>
<ClInclude Include="Minimap.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="olcPGEX_SplashScreen.h" /> <ClInclude Include="olcPGEX_SplashScreen.h" />
<ClInclude Include="PlayerMoneyLabel.h"> <ClInclude Include="PlayerMoneyLabel.h">
<SubType> <SubType>
@ -749,6 +753,10 @@
</SubType> </SubType>
</ClCompile> </ClCompile>
<ClCompile Include="Meteor.cpp" /> <ClCompile Include="Meteor.cpp" />
<ClCompile Include="Minimap.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="NPC.cpp"> <ClCompile Include="NPC.cpp">
<SubType> <SubType>
</SubType> </SubType>
@ -893,6 +901,7 @@
<Text Include="assets\config\items\ItemStats.txt" /> <Text Include="assets\config\items\ItemStats.txt" />
<Text Include="assets\config\items\Weapons.txt" /> <Text Include="assets\config\items\Weapons.txt" />
<Text Include="assets\config\levels.txt" /> <Text Include="assets\config\levels.txt" />
<Text Include="assets\config\minimap.txt" />
<Text Include="assets\config\Monsters.txt" /> <Text Include="assets\config\Monsters.txt" />
<Text Include="assets\config\MonsterStrategies.txt" /> <Text Include="assets\config\MonsterStrategies.txt" />
<Text Include="assets\config\NPCs.txt" /> <Text Include="assets\config\NPCs.txt" />

@ -627,6 +627,12 @@
<ClInclude Include="emscripten_compat.h"> <ClInclude Include="emscripten_compat.h">
<Filter>Header Files\steam</Filter> <Filter>Header Files\steam</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="SteamStatsReceivedHandler.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Minimap.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="Player.cpp"> <ClCompile Include="Player.cpp">
@ -1001,6 +1007,9 @@
<ClCompile Include="SteamStatsReceivedHandler.cpp"> <ClCompile Include="SteamStatsReceivedHandler.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="Minimap.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="cpp.hint" /> <None Include="cpp.hint" />
@ -1166,6 +1175,9 @@
<Text Include="assets\config\Achievements.txt"> <Text Include="assets\config\Achievements.txt">
<Filter>Configurations</Filter> <Filter>Configurations</Filter>
</Text> </Text>
<Text Include="assets\config\minimap.txt">
<Filter>Configurations</Filter>
</Text>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Image Include="assets\heart.ico"> <Image Include="assets\heart.ico">

@ -92,7 +92,7 @@ bool _DEBUG_MAP_LOAD_INFO = false;
//360x240 //360x240
vi2d WINDOW_SIZE={24*15,24*10}; vi2d WINDOW_SIZE={24*15,24*10};
safemap<std::string,Animate2D::FrameSequence>ANIMATION_DATA; safemap<std::string,Animate2D::FrameSequence>ANIMATION_DATA;
std::vector<Monster>MONSTER_LIST; std::vector<std::unique_ptr<Monster>>MONSTER_LIST;
std::vector<MonsterSpawner>SPAWNER_LIST; std::vector<MonsterSpawner>SPAWNER_LIST;
std::vector<std::shared_ptr<DamageNumber>>DAMAGENUMBER_LIST; std::vector<std::shared_ptr<DamageNumber>>DAMAGENUMBER_LIST;
std::vector<std::unique_ptr<Bullet>>BULLET_LIST; std::vector<std::unique_ptr<Bullet>>BULLET_LIST;
@ -236,6 +236,9 @@ AiL::AiL()
std::string ACHIEVEMENT_CONFIG = CONFIG_PATH + "achievement_config"_S; std::string ACHIEVEMENT_CONFIG = CONFIG_PATH + "achievement_config"_S;
utils::datafile::Read(DATA,ACHIEVEMENT_CONFIG); utils::datafile::Read(DATA,ACHIEVEMENT_CONFIG);
std::string MINIMAP_CONFIG = CONFIG_PATH + "minimap_config"_S;
utils::datafile::Read(DATA,MINIMAP_CONFIG);
utils::datafile::DEBUG_ACCESS_OPTIONS="debug_access_options"_I; utils::datafile::DEBUG_ACCESS_OPTIONS="debug_access_options"_I;
sAppName = "GAME_NAME"_S; sAppName = "GAME_NAME"_S;
@ -350,6 +353,8 @@ bool AiL::OnUserCreate(){
SetupDiscord(); SetupDiscord();
#endif #endif
minimap.Initialize();
gameInitialized=true; gameInitialized=true;
if(!gamepack.Loaded()&&"GENERATE_GAMEPACK"_B){ if(!gamepack.Loaded()&&"GENERATE_GAMEPACK"_B){
@ -734,23 +739,23 @@ void AiL::UpdateBullets(float fElapsedTime){
b->distanceTraveled+=totalDistance/24.f*100.f; b->distanceTraveled+=totalDistance/24.f*100.f;
const auto CollisionCheck=[&](){ const auto CollisionCheck=[&](){
if(b->friendly){ if(b->friendly){
for(Monster&m:MONSTER_LIST){ for(std::unique_ptr<Monster>&m:MONSTER_LIST){
if(geom2d::overlaps(m.Hitbox(),geom2d::circle(b->pos,b->radius))){ if(geom2d::overlaps(m->Hitbox(),geom2d::circle(b->pos,b->radius))){
if(b->hitList.find(&m)==b->hitList.end()&&m.Hurt(b->damage,b->OnUpperLevel(),0)){ if(b->hitList.find(&*m)==b->hitList.end()&&m->Hurt(b->damage,b->OnUpperLevel(),0)){
if(!b->hitsMultiple){ if(!b->hitsMultiple){
if(b->MonsterHit(m)){ if(b->MonsterHit(*m)){
b->dead=true; b->dead=true;
} }
return false; return false;
} }
b->hitList.insert(&m); b->hitList.insert(&*m);
} }
} }
} }
} else { } else {
if(geom2d::overlaps(player->Hitbox(),geom2d::circle(b->pos,b->radius))){ if(geom2d::overlaps(player->Hitbox(),geom2d::circle(b->pos,b->radius))){
if(player->Hurt(b->damage,b->OnUpperLevel(),0)){ if(player->Hurt(b->damage,b->OnUpperLevel(),0)){
if(b->PlayerHit(player.get())){ if(b->PlayerHit(&*player)){
b->dead=true; b->dead=true;
} }
return false; return false;
@ -790,10 +795,10 @@ void AiL::UpdateBullets(float fElapsedTime){
} }
const MonsterHurtList AiL::HurtEnemies(vf2d pos,float radius,int damage,bool upperLevel,float z)const{ const MonsterHurtList AiL::HurtEnemies(vf2d pos,float radius,int damage,bool upperLevel,float z)const{
MonsterHurtList hitList; MonsterHurtList hitList;
for(Monster&m:MONSTER_LIST){ for(std::unique_ptr<Monster>&m:MONSTER_LIST){
if(geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m.GetPos(),12*m.GetSizeMult()))){ if(geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){
HurtReturnValue returnVal=m.Hurt(damage,upperLevel,z); HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z);
hitList.push_back({&m,returnVal}); hitList.push_back({&*m,returnVal});
} }
} }
return hitList; return hitList;
@ -801,11 +806,11 @@ const MonsterHurtList AiL::HurtEnemies(vf2d pos,float radius,int damage,bool upp
const MonsterHurtList AiL::HurtEnemiesNotHit(vf2d pos,float radius,int damage,HitList&hitList,bool upperLevel,float z){ const MonsterHurtList AiL::HurtEnemiesNotHit(vf2d pos,float radius,int damage,HitList&hitList,bool upperLevel,float z){
MonsterHurtList affectedList; MonsterHurtList affectedList;
for(Monster&m:MONSTER_LIST){ for(std::unique_ptr<Monster>&m:MONSTER_LIST){
if(!hitList.count(&m)&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m.GetPos(),12*m.GetSizeMult()))){ if(!hitList.count(&*m)&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){
HurtReturnValue returnVal=m.Hurt(damage,upperLevel,z); HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z);
affectedList.push_back({&m,returnVal}); affectedList.push_back({&*m,returnVal});
hitList.insert(&m); hitList.insert(&*m);
} }
} }
return affectedList; return affectedList;
@ -813,14 +818,14 @@ const MonsterHurtList AiL::HurtEnemiesNotHit(vf2d pos,float radius,int damage,Hi
const MonsterHurtList AiL::HurtEnemiesConeNotHit(vf2d pos,float radius,float angle,float sweepAngle,int damage,HitList&hitList,bool upperLevel,float z){ const MonsterHurtList AiL::HurtEnemiesConeNotHit(vf2d pos,float radius,float angle,float sweepAngle,int damage,HitList&hitList,bool upperLevel,float z){
MonsterHurtList affectedList; MonsterHurtList affectedList;
for(Monster&m:MONSTER_LIST){ for(std::unique_ptr<Monster>&m:MONSTER_LIST){
if(!hitList.count(&m)&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m.GetPos(),12*m.GetSizeMult()))){ if(!hitList.count(&*m)&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){
float angleToMonster=geom2d::line<float>{pos,m.GetPos()}.vector().polar().y; float angleToMonster=geom2d::line<float>{pos,m->GetPos()}.vector().polar().y;
float angleDiff=util::angle_difference(angleToMonster,angle); float angleDiff=util::angle_difference(angleToMonster,angle);
if(abs(angleDiff)<=sweepAngle){ if(abs(angleDiff)<=sweepAngle){
HurtReturnValue returnVal=m.Hurt(damage,upperLevel,z); HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z);
affectedList.push_back({&m,returnVal}); affectedList.push_back({&*m,returnVal});
hitList.insert(&m); hitList.insert(&*m);
} }
} }
} }
@ -851,14 +856,14 @@ void AiL::PopulateRenderLists(){
Player*pl=GetPlayer(); Player*pl=GetPlayer();
pl->rendered=false; pl->rendered=false;
std::sort(MONSTER_LIST.begin(),MONSTER_LIST.end(),[](Monster&m1,Monster&m2){return m1.GetPos().y<m2.GetPos().y;}); std::sort(MONSTER_LIST.begin(),MONSTER_LIST.end(),[](std::unique_ptr<Monster>&m1,std::unique_ptr<Monster>&m2){return m1->GetPos().y<m2->GetPos().y;});
std::sort(ItemDrop::drops.begin(),ItemDrop::drops.end(),[](ItemDrop&id1,ItemDrop&id2){return id1.GetPos().y<id2.GetPos().y;}); std::sort(ItemDrop::drops.begin(),ItemDrop::drops.end(),[](ItemDrop&id1,ItemDrop&id2){return id1.GetPos().y<id2.GetPos().y;});
std::sort(BULLET_LIST.begin(),BULLET_LIST.end(),[](std::unique_ptr<Bullet>&b1,std::unique_ptr<Bullet>&b2){return b1->pos.y<b2->pos.y;}); std::sort(BULLET_LIST.begin(),BULLET_LIST.end(),[](std::unique_ptr<Bullet>&b1,std::unique_ptr<Bullet>&b2){return b1->pos.y<b2->pos.y;});
std::sort(foregroundEffects.begin(),foregroundEffects.end(),[](std::unique_ptr<Effect>&e1,std::unique_ptr<Effect>&e2){return e1->pos.y<e2->pos.y;}); std::sort(foregroundEffects.begin(),foregroundEffects.end(),[](std::unique_ptr<Effect>&e1,std::unique_ptr<Effect>&e2){return e1->pos.y<e2->pos.y;});
std::sort(backgroundEffects.begin(),backgroundEffects.end(),[](std::unique_ptr<Effect>&e1,std::unique_ptr<Effect>&e2){return e1->pos.y<e2->pos.y;}); std::sort(backgroundEffects.begin(),backgroundEffects.end(),[](std::unique_ptr<Effect>&e1,std::unique_ptr<Effect>&e2){return e1->pos.y<e2->pos.y;});
for(auto it=MONSTER_LIST.begin();it!=MONSTER_LIST.end();++it){ for(auto it=MONSTER_LIST.begin();it!=MONSTER_LIST.end();++it){
Monster&m=*it; Monster&m=**it;
if(m.GetPos().y<pl->GetPos().y){//This monster renders before the player does (behind the player) if(m.GetPos().y<pl->GetPos().y){//This monster renders before the player does (behind the player)
if(m.OnUpperLevel()){ if(m.OnUpperLevel()){
monstersBeforeUpper.push_back(&m); monstersBeforeUpper.push_back(&m);
@ -1021,8 +1026,8 @@ void AiL::RenderWorld(float fElapsedTime){
multiplierX*=(1-abs(cos(1.5f*reflectionStepTime))*"water_reflection_scale_factor"_F); multiplierX*=(1-abs(cos(1.5f*reflectionStepTime))*"water_reflection_scale_factor"_F);
float reflectionRatioX=abs(sin(reflectionStepTime))*"water_reflection_scale_factor"_F; float reflectionRatioX=abs(sin(reflectionStepTime))*"water_reflection_scale_factor"_F;
RenderPlayer(player->GetPos()+vf2d{reflectionRatioX*player->GetFrame().GetSourceRect().size.x,float(player->GetFrame().GetSourceRect().size.y)-8}*player->GetSizeMult(),{multiplierX,-1}); RenderPlayer(player->GetPos()+vf2d{reflectionRatioX*player->GetFrame().GetSourceRect().size.x,float(player->GetFrame().GetSourceRect().size.y)-8}*player->GetSizeMult(),{multiplierX,-1});
for(Monster&m:MONSTER_LIST){ for(std::unique_ptr<Monster>&m:MONSTER_LIST){
m.DrawReflection(reflectionRatioX,multiplierX); m->DrawReflection(reflectionRatioX,multiplierX);
} }
SetDecalMode(DecalMode::NORMAL); SetDecalMode(DecalMode::NORMAL);
} }
@ -1689,8 +1694,8 @@ void AiL::RenderWorld(float fElapsedTime){
} }
} }
for(Monster&m:MONSTER_LIST){ for(std::unique_ptr<Monster>&m:MONSTER_LIST){
m.strategyDrawOverlay(this,m,MONSTER_DATA[m.GetName()].GetAIStrategy()); m->strategyDrawOverlay(this,*m,MONSTER_DATA[m->GetName()].GetAIStrategy());
} }
#ifdef _DEBUG #ifdef _DEBUG
@ -1805,6 +1810,10 @@ void AiL::RenderHud(){
std::string displayText=player->notificationDisplay.first; std::string displayText=player->notificationDisplay.first;
DrawShadowStringPropDecal(vf2d{float(ScreenWidth()/2),float(ScreenHeight()/4)-24}-GetTextSizeProp(displayText)/2,displayText,BLUE,VERY_DARK_BLUE); DrawShadowStringPropDecal(vf2d{float(ScreenWidth()/2),float(ScreenHeight()/4)-24}-GetTextSizeProp(displayText)/2,displayText,BLUE,VERY_DARK_BLUE);
} }
minimap.Update();
minimap.Draw();
DisplayBossEncounterInfo(); DisplayBossEncounterInfo();
#ifdef _DEBUG #ifdef _DEBUG
if("debug_player_info"_I){ if("debug_player_info"_I){
@ -1989,6 +1998,8 @@ void AiL::InitializeLevel(std::string mapFile,MapName map){
if(MAP_TILESETS.find("assets/maps/"+baseSourceDir)==MAP_TILESETS.end()){ if(MAP_TILESETS.find("assets/maps/"+baseSourceDir)==MAP_TILESETS.end()){
TSXParser tileset(baseDir+tag.data["source"]); TSXParser tileset(baseDir+tag.data["source"]);
Renderable*r=NEW Renderable(); Renderable*r=NEW Renderable();
if(tileset.GetData().tilewidth==0||tileset.GetData().tileheight==0)ERR(std::format("WARNING! Failed to load map {}! Found a tileset {} with a width of {} and height of {}. Zero values are not allowed!",map,"assets/maps/"+baseSourceDir,tileset.GetData().tilewidth,tileset.GetData().tileheight));
MAP_TILESETS["assets/maps/"+baseSourceDir].tilewidth=tileset.GetData().tilewidth; MAP_TILESETS["assets/maps/"+baseSourceDir].tilewidth=tileset.GetData().tilewidth;
MAP_TILESETS["assets/maps/"+baseSourceDir].tileheight=tileset.GetData().tileheight; MAP_TILESETS["assets/maps/"+baseSourceDir].tileheight=tileset.GetData().tileheight;
MAP_TILESETS["assets/maps/"+baseSourceDir].tileset=r; MAP_TILESETS["assets/maps/"+baseSourceDir].tileset=r;
@ -2049,6 +2060,8 @@ void AiL::InitializeLevel(std::string mapFile,MapName map){
SetDrawTarget(nullptr); SetDrawTarget(nullptr);
r->Decal()->Update(); r->Decal()->Update();
} }
ComputeModeColors(MAP_TILESETS["assets/maps/"+baseSourceDir]);
} }
} }
@ -2215,6 +2228,13 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
}); });
#pragma endregion #pragma endregion
#pragma region Foreground and Upper Foreground Tile Fade Group Setup (Loading phase 3.5)
LoadingScreen::AddPhase([&](){
minimap.Reset();
return true;
});
#pragma endregion
#pragma region Foreground and Upper Foreground Tile Fade Group Setup (Loading phase 4) #pragma region Foreground and Upper Foreground Tile Fade Group Setup (Loading phase 4)
LoadingScreen::AddPhase([&](){ LoadingScreen::AddPhase([&](){
std::set<vi2d>foregroundTilesAdded,upperForegroundTilesAdded; std::set<vi2d>foregroundTilesAdded,upperForegroundTilesAdded;
@ -2400,9 +2420,9 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
LoadingScreen::AddPhase([&](){ LoadingScreen::AddPhase([&](){
for(NPCData data:game->MAP_DATA[game->GetCurrentLevel()].npcs){ for(NPCData data:game->MAP_DATA[game->GetCurrentLevel()].npcs){
if(Unlock::IsUnlocked(data.unlockCondition)){ if(Unlock::IsUnlocked(data.unlockCondition)){
MONSTER_LIST.push_back(Monster{data.spawnPos,MONSTER_DATA[data.name]}); MONSTER_LIST.push_back(std::make_unique<Monster>(data.spawnPos,MONSTER_DATA[data.name]));
MONSTER_LIST.back().iframe_timer=INFINITE; MONSTER_LIST.back()->iframe_timer=INFINITE;
MONSTER_LIST.back().npcData=data; MONSTER_LIST.back()->npcData=data;
} }
} }
return true; return true;
@ -2498,18 +2518,24 @@ TilesheetData AiL::GetTileSheet(MapName map,int tileID){
if(tileData.size()==1){ if(tileData.size()==1){
size_t slashMarkerSourceDir = tileData[0].data["source"].find_last_of('/'); size_t slashMarkerSourceDir = tileData[0].data["source"].find_last_of('/');
std::string baseSourceDir=tileData[0].data["source"].substr(slashMarkerSourceDir+1); std::string baseSourceDir=tileData[0].data["source"].substr(slashMarkerSourceDir+1);
return {&MAP_TILESETS["assets/maps/"+baseSourceDir],1}; return {&MAP_TILESETS["assets/maps/"+baseSourceDir],1,MAP_TILESETS["assets/maps/"+baseSourceDir].tilecols[tileID]};
} else { } else {
for (int i=1;i<tileData.size();i++){ for (int i=1;i<tileData.size();i++){
int firstgid=stoi(tileData[i-1].data["firstgid"]);
if(tileID%1000000<stoi(tileData[i].data["firstgid"])-1){ if(tileID%1000000<stoi(tileData[i].data["firstgid"])-1){
size_t slashMarkerSourceDir = tileData[size_t(i-1)].data["source"].find_last_of('/'); size_t slashMarkerSourceDir = tileData[size_t(i-1)].data["source"].find_last_of('/');
std::string baseSourceDir=tileData[size_t(i-1)].data["source"].substr(slashMarkerSourceDir+1); std::string baseSourceDir=tileData[size_t(i-1)].data["source"].substr(slashMarkerSourceDir+1);
return {&MAP_TILESETS["assets/maps/"+baseSourceDir],stoi(tileData[i-1].data["firstgid"])}; if(tileID!=-1){
return {&MAP_TILESETS["assets/maps/"+baseSourceDir],firstgid,MAP_TILESETS["assets/maps/"+baseSourceDir].tilecols[tileID-(firstgid-1)]};
}else{
return {&MAP_TILESETS["assets/maps/"+baseSourceDir],firstgid,BLANK};
}
} }
} }
size_t slashMarkerSourceDir = tileData[tileData.size()-1].data["source"].find_last_of('/'); size_t slashMarkerSourceDir = tileData[tileData.size()-1].data["source"].find_last_of('/');
std::string baseSourceDir=tileData[tileData.size()-1].data["source"].substr(slashMarkerSourceDir+1); std::string baseSourceDir=tileData[tileData.size()-1].data["source"].substr(slashMarkerSourceDir+1);
return {&MAP_TILESETS["assets/maps/"+baseSourceDir],stoi(tileData[tileData.size()-1].data["firstgid"])}; int firstgid=stoi(tileData[tileData.size()-1].data["firstgid"]);
return {&MAP_TILESETS["assets/maps/"+baseSourceDir],firstgid,MAP_TILESETS["assets/maps/"+baseSourceDir].tilecols[tileID-(firstgid-1)]};
} }
} }
@ -2529,35 +2555,36 @@ bool AiL::IsOverlayLayer(LayerTag&layer){
} }
geom2d::rect<float>AiL::GetTileCollision(MapName map,vf2d pos,bool upperLevel){ geom2d::rect<float>AiL::GetTileCollision(MapName map,vf2d pos,bool upperLevel){
if(pos.x<0||pos.y<0||pos.x>=GetCurrentMapData().width*game->GetCurrentMapData().tilewidth||pos.y>=GetCurrentMapData().height*game->GetCurrentMapData().tilewidth)return NO_COLLISION; MapTag&mapData=MAP_DATA[map].MapData;
if(GetCurrentMap().optimizedTile)return NO_COLLISION; //Overworld map has no collision. if(pos.x<0||pos.y<0||pos.x>=mapData.width*mapData.tilewidth||pos.y>=mapData.height*mapData.tilewidth)return NO_COLLISION;
if(MAP_DATA[map].optimizedTile)return NO_COLLISION; //Overworld map has no collision.
bool hasTerrain=false; bool hasTerrain=false;
for(const LayerTag&layer:GetCurrentMap().LayerData){ //Figure out if any tile at this position is terrain. If so, we have a collision box to check. for(const LayerTag&layer:MAP_DATA[map].LayerData){ //Figure out if any tile at this position is terrain. If so, we have a collision box to check.
if(Unlock::IsUnlocked(layer.unlockCondition)){ if(Unlock::IsUnlocked(layer.unlockCondition)){
int tileID=layer.tiles[pos.y/GetCurrentMapData().tilewidth][pos.x/GetCurrentMapData().tilewidth]-1; int tileID=layer.tiles[pos.y/mapData.tilewidth][pos.x/mapData.tilewidth]-1;
if(tileID==-1)continue; if(tileID==-1)continue;
const TilesheetData&data=GetTileSheet(GetCurrentLevel(),tileID); const TilesheetData&data=GetTileSheet(map,tileID);
if(data.tileset->isTerrain){ if(data.tileset->isTerrain){
hasTerrain=true; hasTerrain=true;
break; break;
} }
} }
} }
if(!hasTerrain)return geom2d::rect<float>({0.f,0.f},{float(GetCurrentMapData().tilewidth),float(GetCurrentMapData().tilewidth)}); //We assume no terrain means we can't walk on this. if(!hasTerrain)return geom2d::rect<float>({0.f,0.f},{float(mapData.tilewidth),float(mapData.tilewidth)}); //We assume no terrain means we can't walk on this.
#pragma region Lower Bridge Collision Check #pragma region Lower Bridge Collision Check
if(!upperLevel){ //We are looking for lower bridge collisions. if(!upperLevel){ //We are looking for lower bridge collisions.
for(ZoneData&zone:MAP_DATA[map].ZoneData["LowerBridgeCollision"]){ for(ZoneData&zone:MAP_DATA[map].ZoneData["LowerBridgeCollision"]){
if(geom2d::contains(zone.zone,pos)){ if(geom2d::contains(zone.zone,pos)){
return {{0,0},{float(game->GetCurrentMapData().tilewidth),float(game->GetCurrentMapData().tilewidth)}}; return {{0,0},{float(mapData.tilewidth),float(mapData.tilewidth)}};
} }
} }
} }
#pragma endregion #pragma endregion
//The logic here is, if there's a tile on the bridge, we respect that tile instead if we're on the upper level. So we don't check other layers when we are on the upper level and there is a tile below us. //The logic here is, if there's a tile on the bridge, we respect that tile instead if we're on the upper level. So we don't check other layers when we are on the upper level and there is a tile below us.
if(upperLevel&&bridgeLayerIndex!=-1){ if(upperLevel&&bridgeLayerIndex!=-1){
int tileID=MAP_DATA[map].LayerData[bridgeLayerIndex].tiles[int(pos.y)/GetCurrentMapData().tilewidth][int(pos.x)/GetCurrentMapData().tilewidth]-1; int tileID=MAP_DATA[map].LayerData[bridgeLayerIndex].tiles[int(pos.y)/mapData.tilewidth][int(pos.x)/mapData.tilewidth]-1;
if(tileID!=-1){ if(tileID!=-1){
if (GetTileSheet(map,tileID%1000000).tileset->collision.find(tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1)!=GetTileSheet(map,tileID%1000000).tileset->collision.end()){ if (GetTileSheet(map,tileID%1000000).tileset->collision.find(tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1)!=GetTileSheet(map,tileID%1000000).tileset->collision.end()){
return GetTileSheet(map,tileID%1000000).tileset->collision[tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1].collision; return GetTileSheet(map,tileID%1000000).tileset->collision[tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1].collision;
@ -2570,7 +2597,7 @@ geom2d::rect<float>AiL::GetTileCollision(MapName map,vf2d pos,bool upperLevel){
if(Unlock::IsUnlocked(layer.unlockCondition)){ if(Unlock::IsUnlocked(layer.unlockCondition)){
//auto HasNoClass=[&](){return layer.tag.data.find("class")==layer.tag.data.end();}; //auto HasNoClass=[&](){return layer.tag.data.find("class")==layer.tag.data.end();};
if(counter!=bridgeLayerIndex){ if(counter!=bridgeLayerIndex){
int tileID=layer.tiles[int(pos.y)/GetCurrentMapData().tilewidth][int(pos.x)/GetCurrentMapData().tilewidth]-1; int tileID=layer.tiles[int(pos.y)/mapData.tilewidth][int(pos.x)/mapData.tilewidth]-1;
if(tileID!=-1&&GetTileSheet(map,tileID%1000000).tileset->collision.find(tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1)!=GetTileSheet(map,tileID%1000000).tileset->collision.end()){ if(tileID!=-1&&GetTileSheet(map,tileID%1000000).tileset->collision.find(tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1)!=GetTileSheet(map,tileID%1000000).tileset->collision.end()){
geom2d::rect<float>collisionRect=GetTileSheet(map,tileID%1000000).tileset->collision[tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1].collision; geom2d::rect<float>collisionRect=GetTileSheet(map,tileID%1000000).tileset->collision[tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1].collision;
if(foundRect==NO_COLLISION){ if(foundRect==NO_COLLISION){
@ -2590,6 +2617,25 @@ geom2d::rect<float>AiL::GetTileCollision(MapName map,vf2d pos,bool upperLevel){
return foundRect; return foundRect;
} }
Pixel AiL::GetTileColor(MapName map,vf2d pos,bool upperLevel){
MapTag&mapData=MAP_DATA[map].MapData;
if(pos.x<0||pos.y<0||pos.x>=mapData.width*mapData.tilewidth||pos.y>=mapData.height*mapData.tilewidth)return BLANK;
if(MAP_DATA[map].optimizedTile)return BLANK; //Overworld map has no collision.
bool hasTerrain=false;
for(const LayerTag&layer:MAP_DATA[map].LayerData){ //Figure out if any tile at this position is terrain. If so, we have a collision box to check.
if(Unlock::IsUnlocked(layer.unlockCondition)){
int tileID=layer.tiles[pos.y/mapData.tilewidth][pos.x/mapData.tilewidth]-1;
if(tileID==-1)continue;
const TilesheetData&data=GetTileSheet(map,tileID);
return data.tilecol;
}
}
return BLANK;
}
const MapName&AiL::GetCurrentLevel()const{ const MapName&AiL::GetCurrentLevel()const{
if(GameState::STATE!=nullptr&&GameState::STATE==GameState::states[States::STORY]){ //If we're inside a story, we expect the map/level name to be from the visual novel itself. Right now currentLevel would be WORLD_MAP which is not useful for a function like this. if(GameState::STATE!=nullptr&&GameState::STATE==GameState::states[States::STORY]){ //If we're inside a story, we expect the map/level name to be from the visual novel itself. Right now currentLevel would be WORLD_MAP which is not useful for a function like this.
return VisualNovel::novel.storyLevel; return VisualNovel::novel.storyLevel;
@ -3889,12 +3935,12 @@ rcode AiL::LoadResource(Renderable&renderable,std::string_view imgPath,bool filt
} }
void AiL::UpdateMonsters(){ void AiL::UpdateMonsters(){
for(Monster&m:MONSTER_LIST){ for(std::unique_ptr<Monster>&m:MONSTER_LIST){
m.Update(game->GetElapsedTime()); m->Update(game->GetElapsedTime());
} }
for(Monster&m:game->monstersToBeSpawned){ for(Monster&m:game->monstersToBeSpawned){
size_t prevCapacity=MONSTER_LIST.capacity(); size_t prevCapacity=MONSTER_LIST.capacity();
MONSTER_LIST.push_back(m); MONSTER_LIST.push_back(std::make_unique<Monster>(m));
if(MONSTER_LIST.capacity()>prevCapacity)LOG(std::format("WARNING! The monster list has automatically reserved more space and resized to {}! This caused one potential frame where bullet/effect hitlists that stored information on what monsters were hit to potentially be hit a second time or cause monsters that should've been hit to never be hit. Consider starting with a larger default reserved size for MONSTER_LIST if your intention was to have this many monsters!",MONSTER_LIST.capacity())); if(MONSTER_LIST.capacity()>prevCapacity)LOG(std::format("WARNING! The monster list has automatically reserved more space and resized to {}! This caused one potential frame where bullet/effect hitlists that stored information on what monsters were hit to potentially be hit a second time or cause monsters that should've been hit to never be hit. Consider starting with a larger default reserved size for MONSTER_LIST if your intention was to have this many monsters!",MONSTER_LIST.capacity()));
} }
game->monstersToBeSpawned.clear(); game->monstersToBeSpawned.clear();
@ -3983,4 +4029,28 @@ void AiL::SetCompletedStageFlag(){
} }
void AiL::ResetCompletedStageFlag(){ void AiL::ResetCompletedStageFlag(){
prevStageCompleted=false; prevStageCompleted=false;
}
void AiL::ComputeModeColors(TilesetData&tileset){
for(int y=0;y<tileset.tileset->Sprite()->height/tileset.tileheight;y++){
for(int x=0;x<tileset.tileset->Sprite()->width/tileset.tilewidth;x++){
#pragma region Individual tile iteration
std::unordered_map<uint32_t,int>pixelCounts;
Pixel modeCol=BLANK;
int maxCount=0;
vi2d pixelOffset=vi2d{x,y}*tileset.tilewidth;
for(int pixelY=0;pixelY<tileset.tileheight;pixelY++){
for(int pixelX=0;pixelX<tileset.tilewidth;pixelX++){
vi2d targetPixel=vi2d{pixelX,pixelY}+pixelOffset;
Pixel tileCol=tileset.tileset->Sprite()->GetPixel(targetPixel);
pixelCounts[tileCol.n]++;
if(pixelCounts[tileCol.n]>maxCount){
modeCol=tileCol;
pixelCounts[tileCol.n]=maxCount;
}
}
}
tileset.tilecols.push_back(modeCol);
#pragma endregion
}
}
} }

@ -56,6 +56,7 @@ All rights reserved.
#include "olcPixelGameEngine.h" #include "olcPixelGameEngine.h"
#include "DynamicCounter.h" #include "DynamicCounter.h"
#include "UndefKeys.h" #include "UndefKeys.h"
#include "Minimap.h"
class SteamKeyboardCallbackHandler; class SteamKeyboardCallbackHandler;
@ -74,6 +75,7 @@ class AiL : public olc::PixelGameEngine
friend class SaveFile; friend class SaveFile;
friend class sig::Animation; friend class sig::Animation;
friend class Audio; friend class Audio;
friend class Minimap;
std::unique_ptr<Player>player; std::unique_ptr<Player>player;
SplashScreen splash; SplashScreen splash;
public: public:
@ -188,6 +190,8 @@ private:
void ValidateGameStatus(); void ValidateGameStatus();
void _PrepareLevel(MapName map,MusicChange changeMusic); void _PrepareLevel(MapName map,MusicChange changeMusic);
//This function assigns the mode tile colors of each loaded tileset.
void ComputeModeColors(TilesetData&tileset);
#ifndef __EMSCRIPTEN__ #ifndef __EMSCRIPTEN__
::discord::Result SetupDiscord(); ::discord::Result SetupDiscord();
#endif #endif
@ -250,6 +254,7 @@ public:
TilesheetData GetTileSheet(MapName map,int tileID); TilesheetData GetTileSheet(MapName map,int tileID);
//Gets the rectangle of the tile collision at this tile. If upperLevel is set to true, the collision tile must be in a Bridge class layer for the tile to hit. Also, zones containing LowerBridgeCollision will apply when upperLevel is set to false. //Gets the rectangle of the tile collision at this tile. If upperLevel is set to true, the collision tile must be in a Bridge class layer for the tile to hit. Also, zones containing LowerBridgeCollision will apply when upperLevel is set to false.
geom2d::rect<float>GetTileCollision(MapName map,vf2d pos,bool upperLevel=false); geom2d::rect<float>GetTileCollision(MapName map,vf2d pos,bool upperLevel=false);
Pixel GetTileColor(MapName map,vf2d pos,bool upperLevel=false);
//Checks if the point resides inside of a collision tile. //Checks if the point resides inside of a collision tile.
bool HasTileCollision(MapName map,vf2d pos,bool upperLevel=false); bool HasTileCollision(MapName map,vf2d pos,bool upperLevel=false);
const MapName&GetCurrentLevel()const; const MapName&GetCurrentLevel()const;
@ -329,6 +334,7 @@ public:
const bool PreviousStageCompleted()const; const bool PreviousStageCompleted()const;
void SetCompletedStageFlag(); void SetCompletedStageFlag();
void ResetCompletedStageFlag(); void ResetCompletedStageFlag();
Minimap minimap;
struct TileGroupData{ struct TileGroupData{
vi2d tilePos; vi2d tilePos;

@ -93,10 +93,10 @@ void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){
game->GetPlayer()->Knockback(playerDirVecNorm*ConfigFloat("Attack Knockback Amount")); game->GetPlayer()->Knockback(playerDirVecNorm*ConfigFloat("Attack Knockback Amount"));
} }
} }
for(Monster&otherM:MONSTER_LIST){ for(std::unique_ptr<Monster>&otherM:MONSTER_LIST){
if(!otherM.AttackAvoided(m.GetZ())&&&m!=&otherM&&geom2d::overlaps(attackCircle,otherM.Hitbox())){ if(!otherM->AttackAvoided(m.GetZ())&&&m!=otherM.get()&&geom2d::overlaps(attackCircle,otherM->Hitbox())){
otherM.Knockup(ConfigFloat("Attack Knockup Duration")); otherM->Knockup(ConfigFloat("Attack Knockup Duration"));
vf2d monsterDirVecNorm=geom2d::line<float>(m.GetPos(),otherM.GetPos()).vector().norm(); vf2d monsterDirVecNorm=geom2d::line<float>(m.GetPos(),otherM->GetPos()).vector().norm();
game->GetPlayer()->Knockback(monsterDirVecNorm*ConfigFloat("Attack Knockback Amount")); game->GetPlayer()->Knockback(monsterDirVecNorm*ConfigFloat("Attack Knockback Amount"));
} }
} }

@ -85,7 +85,8 @@ struct FrogTongue:public Bullet{
float tongueLength; float tongueLength;
float duration; float duration;
float knockbackStrength; float knockbackStrength;
FrogTongue(vf2d pos,vf2d targetPos,float lifetime,int damage,bool upperLevel,float knockbackStrength=1.0f,bool friendly=false,Pixel col=WHITE); Monster&sourceMonster;
FrogTongue(Monster&sourceMonster,vf2d targetPos,float lifetime,int damage,bool upperLevel,float knockbackStrength=1.0f,bool friendly=false,Pixel col=WHITE);
void Update(float fElapsedTime)override; void Update(float fElapsedTime)override;
bool PlayerHit(Player*player)override; bool PlayerHit(Player*player)override;
bool MonsterHit(Monster&monster)override; bool MonsterHit(Monster&monster)override;

@ -42,7 +42,7 @@ All rights reserved.
using BackdropName=std::string; using BackdropName=std::string;
#define INCLUDE_ANIMATION_DATA extern safemap<std::string,Animate2D::FrameSequence>ANIMATION_DATA; #define INCLUDE_ANIMATION_DATA extern safemap<std::string,Animate2D::FrameSequence>ANIMATION_DATA;
#define INCLUDE_MONSTER_LIST extern std::vector<Monster>MONSTER_LIST; #define INCLUDE_MONSTER_LIST extern std::vector<std::unique_ptr<Monster>>MONSTER_LIST;
#define INCLUDE_SPAWNER_LIST extern std::vector<MonsterSpawner>SPAWNER_LIST; #define INCLUDE_SPAWNER_LIST extern std::vector<MonsterSpawner>SPAWNER_LIST;
#define INCLUDE_DAMAGENUMBER_LIST extern std::vector<std::shared_ptr<DamageNumber>>DAMAGENUMBER_LIST; #define INCLUDE_DAMAGENUMBER_LIST extern std::vector<std::shared_ptr<DamageNumber>>DAMAGENUMBER_LIST;
#define INCLUDE_game extern AiL*game; #define INCLUDE_game extern AiL*game;

@ -70,7 +70,7 @@ void Monster::STRATEGY::FROG(Monster&m,float fElapsedTime,std::string strategy){
m.F(A::LOCKON_WAITTIME)=ConfigFloat("Attack Duration"); m.F(A::LOCKON_WAITTIME)=ConfigFloat("Attack Duration");
vf2d tongueMaxRangePos=geom2d::line<float>(m.GetPos(),m.V(A::LOCKON_POS)).upoint(ConfigFloat("Tongue Max Range")/ConfigFloat("Range")); vf2d tongueMaxRangePos=geom2d::line<float>(m.GetPos(),m.V(A::LOCKON_POS)).upoint(ConfigFloat("Tongue Max Range")/ConfigFloat("Range"));
SoundEffect::PlaySFX("Slime Shoot",m.pos); SoundEffect::PlaySFX("Slime Shoot",m.pos);
CreateBullet(FrogTongue)(m.pos,tongueMaxRangePos,ConfigFloat("Attack Duration"),m.GetAttack(),m.OnUpperLevel(),ConfigFloat("Tongue Knockback Strength"),false,ConfigPixel("Tongue Color"))EndBullet; CreateBullet(FrogTongue)(m,tongueMaxRangePos,ConfigFloat("Attack Duration"),m.GetAttack(),m.OnUpperLevel(),ConfigFloat("Tongue Knockback Strength"),false,ConfigPixel("Tongue Color"))EndBullet;
m.PerformShootAnimation(); m.PerformShootAnimation();
m.I(A::PHASE)=2; m.I(A::PHASE)=2;
} }

@ -44,28 +44,32 @@ INCLUDE_game
INCLUDE_MONSTER_LIST INCLUDE_MONSTER_LIST
INCLUDE_GFX INCLUDE_GFX
FrogTongue::FrogTongue(vf2d pos,vf2d targetPos,float lifetime,int damage,bool upperLevel,float knockbackStrength,bool friendly,Pixel col) FrogTongue::FrogTongue(Monster&sourceMonster,vf2d targetPos,float lifetime,int damage,bool upperLevel,float knockbackStrength,bool friendly,Pixel col)
:Bullet(pos,{},0,damage,upperLevel,friendly,col),targetPos(targetPos),tongueLength(0.f),knockbackStrength(knockbackStrength){ :Bullet(sourceMonster.GetPos(),{},0,damage,upperLevel,friendly,col),targetPos(targetPos),tongueLength(0.f),knockbackStrength(knockbackStrength),sourceMonster(sourceMonster){
this->lifetime=lifetime; this->lifetime=lifetime;
duration=lifetime; duration=lifetime;
} }
void FrogTongue::Update(float fElapsedTime){ void FrogTongue::Update(float fElapsedTime){
geom2d::line<float>lineToTarget(pos,targetPos); pos=sourceMonster.GetPos();
vf2d drawVec=lineToTarget.vector().norm()*3;
if(sourceMonster.IsAlive()){
geom2d::line<float>lineToTarget(pos,targetPos);
vf2d drawVec=lineToTarget.vector().norm()*3;
tongueLength=util::lerp(0,lineToTarget.length(),pow(sin((lifetime*PI)/duration),20.f)); tongueLength=util::lerp(0,lineToTarget.length(),pow(sin((lifetime*PI)/duration),20.f));
vf2d tongueEndPos=geom2d::line<float>(pos+drawVec,targetPos).upoint(pow(sin((lifetime*PI)/duration),20.f)); vf2d tongueEndPos=geom2d::line<float>(pos+drawVec,targetPos).upoint(pow(sin((lifetime*PI)/duration),20.f));
geom2d::line<float>tongueLine(pos+drawVec,tongueEndPos); geom2d::line<float>tongueLine(pos+drawVec,tongueEndPos);
if(!friendly&&geom2d::overlaps(game->GetPlayer()->Hitbox(),tongueLine)){ if(!friendly&&geom2d::overlaps(game->GetPlayer()->Hitbox(),tongueLine)){
PlayerHit(game->GetPlayer()); PlayerHit(game->GetPlayer());
} }
if(friendly){ if(friendly){
for(Monster&m:MONSTER_LIST){ for(std::unique_ptr<Monster>&m:MONSTER_LIST){
if(hitList.find(&m)==hitList.end()&&geom2d::overlaps(m.Hitbox(),tongueLine)){ if(hitList.find(&*m)==hitList.end()&&geom2d::overlaps(m->Hitbox(),tongueLine)){
MonsterHit(m); MonsterHit(*m);
hitList.insert(&m); hitList.insert(&*m);
}
} }
} }
} }

@ -94,14 +94,14 @@ bool LightningBolt::MonsterHit(Monster& monster)
fadeOutTime="Wizard.Ability 2.BulletFadeoutTime"_F; fadeOutTime="Wizard.Ability 2.BulletFadeoutTime"_F;
game->AddEffect(std::make_unique<Effect>(monster.GetPos(),"Wizard.Ability 2.SplashLifetime"_F,"lightning_splash_effect.png",upperLevel,monster.GetSizeMult(),"Wizard.Ability 2.SplashFadeoutTime"_F,vf2d{},WHITE,"Wizard.Ability 2.SplashRotationRange"_FRange)); game->AddEffect(std::make_unique<Effect>(monster.GetPos(),"Wizard.Ability 2.SplashLifetime"_F,"lightning_splash_effect.png",upperLevel,monster.GetSizeMult(),"Wizard.Ability 2.SplashFadeoutTime"_F,vf2d{},WHITE,"Wizard.Ability 2.SplashRotationRange"_FRange));
int targetsHit=0; int targetsHit=0;
for(Monster&m:MONSTER_LIST){ for(std::unique_ptr<Monster>&m:MONSTER_LIST){
if(&m==&monster||monster.OnUpperLevel()!=m.OnUpperLevel())continue; if(&*m==&monster||monster.OnUpperLevel()!=m->OnUpperLevel())continue;
geom2d::line<float>lineToTarget=geom2d::line<float>(monster.GetPos(),m.GetPos()); geom2d::line<float>lineToTarget=geom2d::line<float>(monster.GetPos(),m->GetPos());
float dist=lineToTarget.length(); float dist=lineToTarget.length();
if(dist<="Wizard.Ability 2.LightningChainRadius"_F/100*24){ if(dist<="Wizard.Ability 2.LightningChainRadius"_F/100*24){
if(m.Hurt(int(game->GetPlayer()->GetAttack()*"Wizard.Ability 2.LightningChainDamageMult"_F),OnUpperLevel(),0)){ if(m->Hurt(int(game->GetPlayer()->GetAttack()*"Wizard.Ability 2.LightningChainDamageMult"_F),OnUpperLevel(),0)){
EMITTER_LIST.push_back(std::make_unique<LightningBoltEmitter>(LightningBoltEmitter(monster.GetPos(),m.GetPos(),"Wizard.Ability 2.LightningChainFrequency"_F,"Wizard.Ability 2.LightningChainLifetime"_F,upperLevel))); EMITTER_LIST.push_back(std::make_unique<LightningBoltEmitter>(LightningBoltEmitter(monster.GetPos(),m->GetPos(),"Wizard.Ability 2.LightningChainFrequency"_F,"Wizard.Ability 2.LightningChainLifetime"_F,upperLevel)));
game->AddEffect(std::make_unique<Effect>(m.GetPos(),"Wizard.Ability 2.LightningChainSplashLifetime"_F,"lightning_splash_effect.png",upperLevel,monster.GetSizeMult(),"Wizard.Ability 2.LightningChainSplashFadeoutTime"_F,vf2d{},WHITE,"Wizard.Ability 2.LightningChainSplashRotationRange"_FRange)); game->AddEffect(std::make_unique<Effect>(m->GetPos(),"Wizard.Ability 2.LightningChainSplashLifetime"_F,"lightning_splash_effect.png",upperLevel,monster.GetSizeMult(),"Wizard.Ability 2.LightningChainSplashFadeoutTime"_F,vf2d{},WHITE,"Wizard.Ability 2.LightningChainSplashRotationRange"_FRange));
targetsHit++; targetsHit++;
} }
} }

@ -64,11 +64,13 @@ struct TilesetData{
std::map<int,XMLTag>staircaseTiles; std::map<int,XMLTag>staircaseTiles;
std::map<int,std::vector<int>>animationData; std::map<int,std::vector<int>>animationData;
std::set<int>reflectiveData; std::set<int>reflectiveData;
std::vector<Pixel>tilecols;
}; };
struct TilesheetData{ struct TilesheetData{
TilesetData*tileset; TilesetData*tileset;
int firstgid; int firstgid;
Pixel tilecol;
bool operator==(const TilesheetData&rhs){ bool operator==(const TilesheetData&rhs){
return tileset==rhs.tileset&&firstgid==rhs.firstgid; return tileset==rhs.tileset&&firstgid==rhs.firstgid;
} }

@ -0,0 +1,177 @@
#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 "DEFINES.h"
#include "Unlock.h"
#include "util.h"
INCLUDE_game
void Minimap::Initialize(){
std::vector<vf2d>enlargedCircle;
for(int i=360;i>=0;i-=4){
float angle=util::degToRad(float(i))-PI/2;
if(i==360){enlargedCircle.push_back(vf2d{cos(angle),sin(angle)}*"Minimap.Minimap HUD Size"_I+"Minimap.Minimap HUD Size"_I);}
enlargedCircle.push_back(vf2d{cos(angle),sin(angle)}*"Minimap.Minimap HUD Size"_I+"Minimap.Minimap HUD Size"_I);
}
mapCircleHud=ViewPort{enlargedCircle};
}
void Minimap::Reset(){
loadedChunks.clear();
if(minimap.Sprite()==nullptr)minimap.Create(1,1);
if(cover.Sprite()==nullptr)cover.Create(1,1);
Sprite baseMinimap;
#pragma region Cleanup minimap and cover images
minimap.Sprite()->Resize(game->GetCurrentMapData().width,game->GetCurrentMapData().height);
cover.Sprite()->Resize(game->GetCurrentMapData().width,game->GetCurrentMapData().height);
baseMinimap.Resize(game->GetCurrentMapData().width,game->GetCurrentMapData().height);
game->SetDrawTarget(minimap.Sprite());
game->SetPixelMode(Pixel::ALPHA);
game->Clear(BLANK);
game->SetDrawTarget(&baseMinimap);
game->Clear(BLANK);
game->SetDrawTarget(cover.Sprite());
game->Clear(BLANK);
game->SetPixelMode(Pixel::NORMAL);
//Will update the minimap decal at the end, since we are about to change it anyways.
cover.Decal()->Update();
#pragma endregion
for(int x=0;x<game->GetCurrentMapData().width;x++){
for(int y=0;y<game->GetCurrentMapData().height;y++){
bool tileFound=false;
bool collision=false;
Pixel tileCol;
for(const LayerTag&layer:game->MAP_DATA[game->GetCurrentLevel()].GetLayers()){
if(Unlock::IsUnlocked(layer.unlockCondition)){
int tileID=layer.tiles[y][x]-1;
if(tileID!=-1){
tileFound=true;
if(game->GetTileCollision(game->GetCurrentMapName(),vf2d{float(x),float(y)}*game->GetCurrentMapData().tilewidth)!=game->NO_COLLISION){
baseMinimap.SetPixel({x,y},BLANK);
collision=true;
}
tileCol=game->GetTileColor(game->GetCurrentMapName(),vf2d{float(x),float(y)}*game->GetCurrentMapData().tilewidth);
}
}
}
if(tileFound&&!collision){
baseMinimap.SetPixel({x,y},{uint8_t(std::min(255.f,tileCol.r*1.5f)),uint8_t(std::min(255.f,tileCol.g*1.5f)),uint8_t(std::min(255.f,tileCol.b*1.5f))});
}else
if(!tileFound){
baseMinimap.SetPixel({x,y},BLANK);
}
}
}
for(int sy=0;sy<baseMinimap.height;sy++){
for(int sx=0;sx<baseMinimap.width;sx++){
if(baseMinimap.GetPixel(sx,sy).a>0){
for(int y=-1;y<=1;y++){
for(int x=-1;x<=1;x++){
if(x==0&&y==0)continue;
minimap.Sprite()->SetPixel(sx+x,sy+y,BLACK);
}
}
}
}
}
for(int sy=0;sy<baseMinimap.height;sy++){
for(int sx=0;sx<baseMinimap.width;sx++){
if(baseMinimap.GetPixel(sx,sy).a>0){
minimap.Sprite()->SetPixel(sx,sy,baseMinimap.GetPixel(sx,sy));
}
}
}
game->SetDrawTarget(nullptr);
minimap.Decal()->Update();
}
void Minimap::Update(){
}
void Minimap::UpdateChunk(const vi2d chunkPos){
if(!loadedChunks.count(std::format("{}_{}",chunkPos.x,chunkPos.y))){
loadedChunks.insert(std::format("{}_{}",chunkPos.x,chunkPos.y));
vi2d centerChunkPos=chunkPos*"Minimap.Chunk Size"_I;
vi2d pixelPos=centerChunkPos-"Minimap.Chunk Size"_I*2;
vi2d chunkEndPixelPos=centerChunkPos+"Minimap.Chunk Size"_I*4;
//We start twice the distance we are supposed to outwards.
for(int y=pixelPos.y;y<chunkEndPixelPos.y;y++){
for(int x=pixelPos.x;x<chunkEndPixelPos.x;x++){
if(cover.Sprite()->GetPixel(x,y).a==255||minimap.Sprite()->GetPixel(x,y).a==0)continue; //Already revealed or invisible anyways.
vi2d chunk=vi2d{x,y}/"Minimap.Chunk Size"_I;
if(chunk==chunkPos){
cover.Sprite()->SetPixel(x,y,minimap.Sprite()->GetPixel(x,y));
}else{
const vi2d chunkOffset={"Minimap.Chunk Size"_I/2,"Minimap.Chunk Size"_I/2};
const float distance=geom2d::line<float>(centerChunkPos+chunkOffset,vf2d{float(x),float(y)}).length();
const int alpha=std::clamp(util::lerp(255,0,(distance-"Minimap.Chunk Size"_I)/"Minimap.Chunk Size"_I),0.f,255.f);
if(cover.Sprite()->GetPixel(x,y).a>alpha)continue; //The distance was uncovered by another closer chunk, don't need to reveal it here.
Pixel sourceCol=minimap.Sprite()->GetPixel(x,y);
sourceCol.a=alpha;
cover.Sprite()->SetPixel(x,y,sourceCol);
}
}
}
cover.Decal()->Update();
}
}
void Minimap::Draw(){
mapCircleHud.DrawRotatedDecal(vf2d{"Minimap.Minimap HUD Size"_I/2.f,"Minimap.Minimap HUD Size"_I/2.f},cover.Decal(),0.f,game->GetPlayer()->GetPos()/24);
mapCircleHud.DrawStringDecal({0,0},"Hello World! Hello World! Hello World! Hello World! \nHello World! Hello World! Hello World! Hello World! \nHello World! Hello World! Hello World! ");
mapCircleHud.drawEdges();
}

@ -0,0 +1,55 @@
#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 "olcPGEX_ViewPort.h"
class Minimap{
public:
void Initialize();
void Reset();
void Update();
void Draw();
void UpdateChunk(const vi2d chunkPos);
private:
ViewPort mapCircleHud;
Renderable minimap;
Renderable cover;
std::unordered_set<std::string>loadedChunks;
};

@ -269,14 +269,14 @@ bool Monster::Update(float fElapsedTime){
} }
} }
if(!HasIframes()){ if(!HasIframes()){
for(Monster&m:MONSTER_LIST){ for(std::unique_ptr<Monster>&m:MONSTER_LIST){
if(&m==this)continue; if(&*m==this)continue;
if(!m.HasIframes()&&OnUpperLevel()==m.OnUpperLevel()&&abs(m.GetZ()-GetZ())<=1&&geom2d::overlaps(geom2d::circle(pos,12*size/2),geom2d::circle(m.GetPos(),12*m.GetSizeMult()/2))){ if(!m->HasIframes()&&OnUpperLevel()==m->OnUpperLevel()&&abs(m->GetZ()-GetZ())<=1&&geom2d::overlaps(geom2d::circle(pos,12*size/2),geom2d::circle(m->GetPos(),12*m->GetSizeMult()/2))){
m.Collision(*this); m->Collision(*this);
geom2d::line line(pos,m.GetPos()); geom2d::line line(pos,m->GetPos());
float dist = line.length(); float dist = line.length();
m.SetPos(line.rpoint(dist*1.1f)); m->SetPos(line.rpoint(dist*1.1f));
if(m.IsAlive()){ if(m->IsAlive()){
vel=line.vector().norm()*-128; vel=line.vector().norm()*-128;
} }
} }

@ -224,7 +224,7 @@ Pathfinding::sPoint2D Pathfinding::sSpline::GetSplinePoint(float t, bool bLooped
} }
else else
{ {
p1 = (int)t1; p1 = std::clamp(size_t(t1),size_t(0),points.size()-1);
p2 = (p1 + 1) % points.size(); p2 = (p1 + 1) % points.size();
p3 = (p2 + 1) % points.size(); p3 = (p2 + 1) % points.size();
p0 = p1 >= 1 ? p1 - 1 : points.size() - 1; p0 = p1 >= 1 ? p1 - 1 : points.size() - 1;

@ -482,19 +482,19 @@ void Player::Update(float fElapsedTime){
if(item3.cooldown<0){ if(item3.cooldown<0){
item3.cooldown=0; item3.cooldown=0;
} }
for(Monster&m:MONSTER_LIST){ for(std::unique_ptr<Monster>&m:MONSTER_LIST){
if(!HasIframes()&&abs(m.GetZ()-GetZ())<=1&&OnUpperLevel()==m.OnUpperLevel()&&geom2d::overlaps(geom2d::circle(pos,12*size/2),geom2d::circle(m.GetPos(),12*m.GetSizeMult()/2))){ if(!HasIframes()&&abs(m->GetZ()-GetZ())<=1&&OnUpperLevel()==m->OnUpperLevel()&&geom2d::overlaps(geom2d::circle(pos,12*size/2),geom2d::circle(m->GetPos(),12*m->GetSizeMult()/2))){
if(m.IsAlive()){ if(m->IsAlive()){
m.Collision(this); m->Collision(this);
} }
geom2d::line line(pos,m.GetPos()); geom2d::line line(pos,m->GetPos());
float dist = line.length(); float dist = line.length();
if(dist<=0.001){ if(dist<=0.001){
m.SetPos(m.GetPos()+vf2d{util::random(2)-1,util::random(2)-1}); m->SetPos(m->GetPos()+vf2d{util::random(2)-1,util::random(2)-1});
}else{ }else{
m.SetPos(line.rpoint(dist*1.1f)); m->SetPos(line.rpoint(dist*1.1f));
} }
if(m.IsAlive()&&!m.IsNPC()){ //Don't set the knockback if this monster is actually an NPC. Let's just push them around. if(m->IsAlive()&&!m->IsNPC()){ //Don't set the knockback if this monster is actually an NPC. Let's just push them around.
vel=line.vector().norm()*-128; vel=line.vector().norm()*-128;
} }
} }
@ -865,6 +865,8 @@ void Player::Moved(){
ERR(std::format("WARNING! Player Y position is {}...Trying to recover. THIS SHOULD NOT BE HAPPENING!",pos.y)); ERR(std::format("WARNING! Player Y position is {}...Trying to recover. THIS SHOULD NOT BE HAPPENING!",pos.y));
ForceSetPos({pos.x,float(game->GetCurrentMapData().playerSpawnLocation.y)}); ForceSetPos({pos.x,float(game->GetCurrentMapData().playerSpawnLocation.y)});
} }
game->minimap.UpdateChunk(GetPos()/24/"Minimap.Chunk Size"_I);
} }
void Player::Spin(float duration,float spinSpd){ void Player::Spin(float duration,float spinSpd){
@ -1445,13 +1447,13 @@ const vf2d Player::GetAimingLocation(bool useWalkDir,bool invert){
}else{ }else{
//Find the closest monster target. //Find the closest monster target.
vf2d closestPoint={std::numeric_limits<float>::max(),std::numeric_limits<float>::max()}; vf2d closestPoint={std::numeric_limits<float>::max(),std::numeric_limits<float>::max()};
for(Monster&m:MONSTER_LIST){ for(std::unique_ptr<Monster>&m:MONSTER_LIST){
if(m.IsAlive()){ if(m->IsAlive()){
geom2d::line<float>aimingLine=geom2d::line<float>(GetPos(),m.GetPos()); geom2d::line<float>aimingLine=geom2d::line<float>(GetPos(),m->GetPos());
float distToMonster=aimingLine.length(); float distToMonster=aimingLine.length();
float distToClosestPoint=geom2d::line<float>(GetPos(),closestPoint).length(); float distToClosestPoint=geom2d::line<float>(GetPos(),closestPoint).length();
if(distToClosestPoint>distToMonster&&distToMonster<=operator""_Pixels("Player.Auto Aim Detection Distance"_F)){ if(distToClosestPoint>distToMonster&&distToMonster<=operator""_Pixels("Player.Auto Aim Detection Distance"_F)){
closestPoint=m.GetPos(); closestPoint=m->GetPos();
} }
} }
} }

@ -19,6 +19,11 @@ Steel Weapons appear in the demo for Chapter 2.
Update display counters on the overworld map. Update display counters on the overworld map.
Pressing movement keys that negate each other shouldn't cause a walking animation to occur. Pressing movement keys that negate each other shouldn't cause a walking animation to occur.
>As the player navigates around the map, the blank map canvas gets updated based on distance.
>If a chunk has not been explored yet, it gets flagged as explored (probably unlock the chunks around the player as well).
>When a map is visited later, all visited chunks are revealed again.
============================================ ============================================
Consider a "killed by player" / "marked by player" flag for monsters to determine if a player gets credit for a monster kill (for achievements) Consider a "killed by player" / "marked by player" flag for monsters to determine if a player gets credit for a monster kill (for achievements)
Make another actions config file for the main build (The app # is different) Make another actions config file for the main build (The app # is different)

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1 #define VERSION_MAJOR 1
#define VERSION_MINOR 1 #define VERSION_MINOR 1
#define VERSION_PATCH 0 #define VERSION_PATCH 0
#define VERSION_BUILD 8667 #define VERSION_BUILD 8815
#define stringify(a) stringify_(a) #define stringify(a) stringify_(a)
#define stringify_(a) #a #define stringify_(a) #a

@ -70,12 +70,12 @@ bool Warrior::AutoAttack(){
bool attack=false; bool attack=false;
Monster*closest=nullptr; Monster*closest=nullptr;
float closest_dist=999999; float closest_dist=999999;
for(Monster&m:MONSTER_LIST){ for(std::unique_ptr<Monster>&m:MONSTER_LIST){
if(m.IsAlive() if(m->IsAlive()&&
&&geom2d::overlaps(geom2d::circle<float>(GetPos(),attack_range*GetSizeMult()*12),geom2d::circle<float>(m.GetPos(),m.GetSizeMult()*12)) geom2d::overlaps(geom2d::circle<float>(GetPos(),attack_range*GetSizeMult()*12),geom2d::circle<float>(m->GetPos(),m->GetSizeMult()*12))&&
&&geom2d::line<float>(GetWorldAimingLocation(),m.GetPos()).length()<closest_dist){ geom2d::line<float>(GetWorldAimingLocation(),m->GetPos()).length()<closest_dist){
closest_dist=geom2d::line<float>(GetWorldAimingLocation(),m.GetPos()).length(); closest_dist=geom2d::line<float>(GetWorldAimingLocation(),m->GetPos()).length();
closest=&m; closest=&*m;
} }
} }
@ -121,9 +121,9 @@ void Warrior::InitializeClassAbilities(){
game->AddEffect(std::make_unique<Effect>(p->GetPos(),"Warrior.Ability 1.EffectLifetime"_F,"battlecry_effect.png",p->upperLevel,"Warrior.Ability 1.Range"_F/350,"Warrior.Ability 1.EffectFadetime"_F)); game->AddEffect(std::make_unique<Effect>(p->GetPos(),"Warrior.Ability 1.EffectLifetime"_F,"battlecry_effect.png",p->upperLevel,"Warrior.Ability 1.Range"_F/350,"Warrior.Ability 1.EffectFadetime"_F));
p->AddBuff(BuffType::STAT_UP,"Warrior.Ability 1.AttackUpDuration"_F,"Warrior.Ability 1.AttackIncrease"_F,{"Attack %"}); p->AddBuff(BuffType::STAT_UP,"Warrior.Ability 1.AttackUpDuration"_F,"Warrior.Ability 1.AttackIncrease"_F,{"Attack %"});
p->AddBuff(BuffType::DAMAGE_REDUCTION,"Warrior.Ability 1.DamageReductionDuration"_F,"Warrior.Ability 1.DamageReduction"_F); p->AddBuff(BuffType::DAMAGE_REDUCTION,"Warrior.Ability 1.DamageReductionDuration"_F,"Warrior.Ability 1.DamageReduction"_F);
for(Monster&m:MONSTER_LIST){ for(std::unique_ptr<Monster>&m:MONSTER_LIST){
if(m.GetSizeMult()>="Warrior.Ability 1.AffectedSizeRange"_f[0]&&m.GetSizeMult()<="Warrior.Ability 1.AffectedSizeRange"_f[1]&&geom2d::overlaps(geom2d::circle<float>(p->GetPos(),12*"Warrior.Ability 1.Range"_I/100.f),geom2d::circle<float>(m.GetPos(),m.GetSizeMult()*12))){ if(m->GetSizeMult()>="Warrior.Ability 1.AffectedSizeRange"_f[0]&&m->GetSizeMult()<="Warrior.Ability 1.AffectedSizeRange"_f[1]&&geom2d::overlaps(geom2d::circle<float>(p->GetPos(),12*"Warrior.Ability 1.Range"_I/100.f),geom2d::circle<float>(m->GetPos(),m->GetSizeMult()*12))){
m.AddBuff(BuffType::SLOWDOWN,"Warrior.Ability 1.SlowdownDuration"_F,"Warrior.Ability 1.SlowdownAmt"_F); m->AddBuff(BuffType::SLOWDOWN,"Warrior.Ability 1.SlowdownDuration"_F,"Warrior.Ability 1.SlowdownAmt"_F);
} }
} }
SoundEffect::PlaySFX("Warrior Battlecry",SoundEffect::CENTERED); SoundEffect::PlaySFX("Warrior Battlecry",SoundEffect::CENTERED);

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" class="Map" orientation="orthogonal" renderorder="right-down" width="182" height="150" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="109"> <map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="182" height="150" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="109">
<properties> <properties>
<property name="Backdrop" propertytype="Backdrop" value="forest"/> <property name="Backdrop" propertytype="Backdrop" value="forest"/>
<property name="Background Music" propertytype="BGM" value="foresty1_1"/> <property name="Background Music" propertytype="BGM" value="foresty1_1"/>

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" class="Map" orientation="orthogonal" renderorder="right-down" width="240" height="120" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="85"> <map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="240" height="120" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="85">
<properties> <properties>
<property name="Backdrop" propertytype="Backdrop" value="forest"/> <property name="Backdrop" propertytype="Backdrop" value="forest"/>
<property name="Background Music" propertytype="BGM" value="foresty1_1"/> <property name="Background Music" propertytype="BGM" value="foresty1_1"/>

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" class="Map" orientation="orthogonal" renderorder="right-down" width="72" height="80" tilewidth="24" tileheight="24" infinite="0" nextlayerid="5" nextobjectid="7"> <map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="72" height="80" tilewidth="24" tileheight="24" infinite="0" nextlayerid="5" nextobjectid="7">
<properties> <properties>
<property name="Backdrop" propertytype="Backdrop" value="forest"/> <property name="Backdrop" propertytype="Backdrop" value="forest"/>
<property name="Background Music" propertytype="BGM" value="foresty_boss"/> <property name="Background Music" propertytype="BGM" value="foresty_boss"/>

@ -9,8 +9,7 @@
<tileset firstgid="8138" source="../maps/Minifantasy_TinyOverworldConstructions.tsx"/> <tileset firstgid="8138" source="../maps/Minifantasy_TinyOverworldConstructions.tsx"/>
<tileset firstgid="9286" source="../maps/Minifantasy_TinyOverworldAllProps.tsx"/> <tileset firstgid="9286" source="../maps/Minifantasy_TinyOverworldAllProps.tsx"/>
<tileset firstgid="10806" source="../maps/Stage_Plate.tsx"/> <tileset firstgid="10806" source="../maps/Stage_Plate.tsx"/>
<tileset firstgid="11066" source=":/automap-tiles.tsx"/> <tileset firstgid="11066" source="../maps/Tilesheet_No_Shadow24x24.tsx"/>
<tileset firstgid="11071" source="../maps/Tilesheet_No_Shadow24x24.tsx"/>
<layer id="1" name="Layer 1" width="250" height="177"> <layer id="1" name="Layer 1" width="250" height="177">
<data encoding="csv"> <data encoding="csv">
713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,2147,82,82,82,239,256,256,256,256,256,256, 713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,713,2147,82,82,82,239,256,256,256,256,256,256,

@ -40,6 +40,9 @@ monsterstrategies_config = MonsterStrategies.txt
# Interface Theme Config # Interface Theme Config
themes_config = gfx/themes.txt themes_config = gfx/themes.txt
# Minimap Config
minimap_config = minimap.txt
# Path to theme image files # Path to theme image files
theme_img_directory = themes/ theme_img_directory = themes/

@ -0,0 +1,8 @@
Minimap
{
# Minimap Hud Size
Minimap HUD Size = 48
# Chunk size in tiles
Chunk Size = 16
}

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" orientation="orthogonal" renderorder="right-down" width="40" height="40" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="1"> <map version="1.10" tiledversion="1.10.2" orientation="orthogonal" renderorder="right-down" width="40" height="40" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="1">
<tileset firstgid="1" source="Tilesheet_No_Shadow24x24.tsx"/> <tileset firstgid="1" source="Tilesheet_No_Shadow24x24.tsx"/>
<tileset firstgid="2913" source="Decorations_c1_No_Shadow24x24.tsx"/> <tileset firstgid="2913" source="Decorations_c1_No_Shadow24x24.tsx"/>
<layer id="2" name="Layer 1" width="40" height="40"> <layer id="2" name="Layer 1" width="40" height="40">

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

@ -17,7 +17,7 @@ namespace olc {
public: public:
ViewPort(); ViewPort();
ViewPort(std::vector<vf2d> vertices, vf2d offset = {0, 0}); ViewPort(std::vector<vf2d> vertices, vf2d offset = {0, 0});
geom2d::rect<float>rect; geom2d::rect<float>rect{};
virtual ~ViewPort(); virtual ~ViewPort();
void addPoint(vf2d point); void addPoint(vf2d point);
void clear(); void clear();
@ -533,16 +533,18 @@ void olc::ViewPort::drawClippedDecal(Decal *decal,
vf2d min={std::numeric_limits<float>::max(),std::numeric_limits<float>::max()},max; vf2d min={std::numeric_limits<float>::max(),std::numeric_limits<float>::max()},max;
bool pointsOutside=false; bool pointsOutside=false;
for(vf2d&points:outputList){ if(rect!=geom2d::rect<float>{}){
if(!geom2d::contains(rect,points)){ for(vf2d&points:outputList){
pointsOutside=true; if(!geom2d::contains(rect,points)){
break; pointsOutside=true;
break;
}
} }
} }else{pointsOutside=true;}
if(!pointsOutside)goto render; if(!pointsOutside)goto render;
for (auto i = 0u; i < clipVertices.size(); i++) { for (auto i = 0u; i < clipVertices.size(); i++) {
auto clipA = clipVertices[i]; auto clipA = clipVertices[i];
auto clipB = clipVertices[(i + 1) % 4]; auto clipB = clipVertices[(i + 1) % clipVertices.size()];
auto inputList{outputList}; auto inputList{outputList};
auto inputUvs{outputUvs}; auto inputUvs{outputUvs};

@ -1151,8 +1151,8 @@ namespace olc
void FillTexturedTriangle(std::vector<olc::vf2d> vPoints, std::vector<olc::vf2d> vTex, std::vector<olc::Pixel> vColour, olc::Sprite* sprTex); void FillTexturedTriangle(std::vector<olc::vf2d> vPoints, std::vector<olc::vf2d> vTex, std::vector<olc::Pixel> vColour, olc::Sprite* sprTex);
void FillTexturedPolygon(const std::vector<olc::vf2d>& vPoints, const std::vector<olc::vf2d>& vTex, const std::vector<olc::Pixel>& vColour, olc::Sprite* sprTex, olc::DecalStructure structure = olc::DecalStructure::LIST); void FillTexturedPolygon(const std::vector<olc::vf2d>& vPoints, const std::vector<olc::vf2d>& vTex, const std::vector<olc::Pixel>& vColour, olc::Sprite* sprTex, olc::DecalStructure structure = olc::DecalStructure::LIST);
// Draws an entire sprite at location (x,y) // Draws an entire sprite at location (x,y)
void DrawSprite(int32_t x, int32_t y, Sprite* sprite, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE, std::function<Pixel(Pixel&)>colorFunc=[](Pixel&in){return in;}); void DrawSprite(int32_t x, int32_t y, Sprite* sprite, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE, std::function<Pixel(const Pixel&)>colorFunc=[](const Pixel&in){return in;});
void DrawSprite(const olc::vi2d& pos, Sprite* sprite, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE, std::function<Pixel(Pixel&)>colorFunc=[](Pixel&in){return in;}); void DrawSprite(const olc::vi2d& pos, Sprite* sprite, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE, std::function<Pixel(const Pixel&)>colorFunc=[](const Pixel&in){return in;});
// Draws an area of a sprite at location (x,y), where the // Draws an area of a sprite at location (x,y), where the
// selected area is (ox,oy) to (ox+w,oy+h) // selected area is (ox,oy) to (ox+w,oy+h)
void DrawPartialSprite(int32_t x, int32_t y, Sprite* sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE,Pixel colorOverride=WHITE); void DrawPartialSprite(int32_t x, int32_t y, Sprite* sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE,Pixel colorOverride=WHITE);
@ -1608,6 +1608,7 @@ namespace olc
std::string PixelGameEngine::White; std::string PixelGameEngine::White;
std::string PixelGameEngine::Black; std::string PixelGameEngine::Black;
std::string PixelGameEngine::Reset; //Will render the original color provided when used. std::string PixelGameEngine::Reset; //Will render the original color provided when used.
// O------------------------------------------------------------------------------O // O------------------------------------------------------------------------------O
// | olc::Pixel IMPLEMENTATION | // | olc::Pixel IMPLEMENTATION |
// O------------------------------------------------------------------------------O // O------------------------------------------------------------------------------O
@ -2980,10 +2981,10 @@ namespace olc
} }
void PixelGameEngine::DrawSprite(const olc::vi2d& pos, Sprite* sprite, uint32_t scale, uint8_t flip, std::function<Pixel(Pixel&)>colorFunc) void PixelGameEngine::DrawSprite(const olc::vi2d& pos, Sprite* sprite, uint32_t scale, uint8_t flip, std::function<Pixel(const Pixel&)>colorFunc)
{ DrawSprite(pos.x, pos.y, sprite, scale, flip); } { DrawSprite(pos.x, pos.y, sprite, scale, flip, colorFunc); }
void PixelGameEngine::DrawSprite(int32_t x, int32_t y, Sprite* sprite, uint32_t scale, uint8_t flip, std::function<Pixel(Pixel&)>colorFunc) void PixelGameEngine::DrawSprite(int32_t x, int32_t y, Sprite* sprite, uint32_t scale, uint8_t flip, std::function<Pixel(const Pixel&)>colorFunc)
{ {
if (sprite == nullptr) if (sprite == nullptr)
return; return;
@ -3002,7 +3003,7 @@ namespace olc
for (int32_t j = 0; j < sprite->height; j++, fy += fym) for (int32_t j = 0; j < sprite->height; j++, fy += fym)
for (uint32_t is = 0; is < scale; is++) for (uint32_t is = 0; is < scale; is++)
for (uint32_t js = 0; js < scale; js++) for (uint32_t js = 0; js < scale; js++)
Draw(x + (i * scale) + is, y + (j * scale) + js, sprite->GetPixel(fx, fy)); Draw(x + (i * scale) + is, y + (j * scale) + js, colorFunc(sprite->GetPixel(fx, fy)));
} }
} }
else else
@ -3012,7 +3013,7 @@ namespace olc
{ {
fy = fys; fy = fys;
for (int32_t j = 0; j < sprite->height; j++, fy += fym) for (int32_t j = 0; j < sprite->height; j++, fy += fym)
Draw(x + i, y + j, sprite->GetPixel(fx, fy)); Draw(x + i, y + j, colorFunc(sprite->GetPixel(fx, fy)));
} }
} }
} }
@ -6437,6 +6438,17 @@ namespace olc
private: private:
HWND olc_hWnd = nullptr; HWND olc_hWnd = nullptr;
std::wstring wsAppName; std::wstring wsAppName;
static RECT resultRect;
static bool monitorFound;
static BOOL CALLBACK monitorCallback(HMONITOR monitor, HDC deviceContext, LPRECT rectPtr, LPARAM data){
monitorFound=true;
tagMONITORINFO monitorInfo;
monitorInfo.cbSize=sizeof(MONITORINFO);
GetMonitorInfoA(monitor,&monitorInfo);
resultRect=monitorInfo.rcWork;
return FALSE; //Return false here because we will use the first monitor that EnumDisplayMonitors gives us. By returning false, we are ending the enumeration immediately.
};
std::wstring ConvertS2W(std::string s) std::wstring ConvertS2W(std::string s)
{ {
@ -6526,6 +6538,24 @@ namespace olc
olc_hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE"), olcT(""), dwStyle, olc_hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE"), olcT(""), dwStyle,
vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this);
monitorFound=false; //After calling EnumDisplayMonitors, monitorCallback will set this variable to true if the clipping rectangle region intersects any monitor.
RECT monitorClippingRect{vTopLeft.x,vTopLeft.y,vTopLeft.x+width,vTopLeft.y+height};
EnumDisplayMonitors(NULL,&monitorClippingRect,monitorCallback,NULL);
if(monitorFound){
vTopLeft.x=std::clamp(long(vTopLeft.x),resultRect.left,resultRect.right);
vTopLeft.y=std::clamp(long(vTopLeft.y),resultRect.top,resultRect.bottom);
int monitorWidth=resultRect.right-resultRect.left;
int monitorHeight=resultRect.bottom-resultRect.top;
bool rightEdgeOutsideBounds_AND_windowWidthFitsOnScreen=vTopLeft.x+width>resultRect.right&&width<monitorWidth;
if(rightEdgeOutsideBounds_AND_windowWidthFitsOnScreen)vTopLeft.x=resultRect.left+(monitorWidth-width);
bool topEdgeOutsideBounds_AND_windowHeightFitsOnScreen=vTopLeft.y+height>resultRect.bottom&&height<monitorHeight;
if(topEdgeOutsideBounds_AND_windowHeightFitsOnScreen)vTopLeft.y=resultRect.top+(monitorHeight-height);
}else vTopLeft={0,0}; //We just give up and put the window back to the default location.
MoveWindow(olc_hWnd,vTopLeft.x,vTopLeft.y,width,height,false); //A hack to get the window's position updated in the correct spot (WM_MOVE reports the correct upper-left corner of the client area) MoveWindow(olc_hWnd,vTopLeft.x,vTopLeft.y,width,height,false); //A hack to get the window's position updated in the correct spot (WM_MOVE reports the correct upper-left corner of the client area)
DragAcceptFiles(olc_hWnd, true); DragAcceptFiles(olc_hWnd, true);
@ -6612,14 +6642,32 @@ namespace olc
} }
return olc::OK; return olc::OK;
} }
virtual void SetWindowPos(vi2d pos)override{ virtual void SetWindowPos(vi2d pos)override{
if(!ptrPGE->IsFullscreen()){ if(!ptrPGE->IsFullscreen()){
RECT rWndRect = { 0, 0, ptrPGE->GetWindowSize().x, ptrPGE->GetWindowSize().y }; RECT rWndRect = { 0, 0, ptrPGE->GetWindowSize().x, ptrPGE->GetWindowSize().y };
int width = rWndRect.right - rWndRect.left; int windowWidth = rWndRect.right - rWndRect.left;
int height = rWndRect.bottom - rWndRect.top; int windowHeight = rWndRect.bottom - rWndRect.top;
MoveWindow(olc_hWnd,pos.x, pos.y, width, height,true);
monitorFound=false; //After calling EnumDisplayMonitors, monitorCallback will set this variable to true if the clipping rectangle region intersects any monitor.
RECT monitorClippingRect{pos.x,pos.y,pos.x+windowWidth,pos.y+windowHeight};
EnumDisplayMonitors(NULL,&monitorClippingRect,monitorCallback,NULL);
if(monitorFound){
pos.x=std::clamp(long(pos.x),resultRect.left,resultRect.right);
pos.y=std::clamp(long(pos.y),resultRect.top,resultRect.bottom);
int monitorWidth=resultRect.right-resultRect.left;
int monitorHeight=resultRect.bottom-resultRect.top;
bool rightEdgeOutsideBounds_AND_windowWidthFitsOnScreen=pos.x+windowWidth>resultRect.right&&windowWidth<monitorWidth;
if(rightEdgeOutsideBounds_AND_windowWidthFitsOnScreen)pos.x=resultRect.left+(monitorWidth-windowWidth);
bool topEdgeOutsideBounds_AND_windowHeightFitsOnScreen=pos.y+windowHeight>resultRect.bottom&&windowHeight<monitorHeight;
if(topEdgeOutsideBounds_AND_windowHeightFitsOnScreen)pos.y=resultRect.top+(monitorHeight-windowHeight);
}else pos={0,0}; //We just give up and put the window back to the default location.
MoveWindow(olc_hWnd,pos.x, pos.y, windowWidth, windowHeight,true);
} }
} }
virtual void SetWindowSize(vi2d size)override{ virtual void SetWindowSize(vi2d size)override{
@ -6746,6 +6794,9 @@ namespace olc
return DefWindowProc(hWnd, uMsg, wParam, lParam); return DefWindowProc(hWnd, uMsg, wParam, lParam);
} }
}; };
RECT Platform_Windows::resultRect;
bool Platform_Windows::monitorFound=false;
} }
#endif #endif
// O------------------------------------------------------------------------------O // O------------------------------------------------------------------------------O

Loading…
Cancel
Save