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

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

@ -92,7 +92,7 @@ bool _DEBUG_MAP_LOAD_INFO = false;
//360x240
vi2d WINDOW_SIZE={24*15,24*10};
safemap<std::string,Animate2D::FrameSequence>ANIMATION_DATA;
std::vector<Monster>MONSTER_LIST;
std::vector<std::unique_ptr<Monster>>MONSTER_LIST;
std::vector<MonsterSpawner>SPAWNER_LIST;
std::vector<std::shared_ptr<DamageNumber>>DAMAGENUMBER_LIST;
std::vector<std::unique_ptr<Bullet>>BULLET_LIST;
@ -236,6 +236,9 @@ AiL::AiL()
std::string ACHIEVEMENT_CONFIG = CONFIG_PATH + "achievement_config"_S;
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;
sAppName = "GAME_NAME"_S;
@ -350,6 +353,8 @@ bool AiL::OnUserCreate(){
SetupDiscord();
#endif
minimap.Initialize();
gameInitialized=true;
if(!gamepack.Loaded()&&"GENERATE_GAMEPACK"_B){
@ -734,23 +739,23 @@ void AiL::UpdateBullets(float fElapsedTime){
b->distanceTraveled+=totalDistance/24.f*100.f;
const auto CollisionCheck=[&](){
if(b->friendly){
for(Monster&m:MONSTER_LIST){
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)){
for(std::unique_ptr<Monster>&m:MONSTER_LIST){
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->hitsMultiple){
if(b->MonsterHit(m)){
if(b->MonsterHit(*m)){
b->dead=true;
}
return false;
}
b->hitList.insert(&m);
b->hitList.insert(&*m);
}
}
}
} else {
if(geom2d::overlaps(player->Hitbox(),geom2d::circle(b->pos,b->radius))){
if(player->Hurt(b->damage,b->OnUpperLevel(),0)){
if(b->PlayerHit(player.get())){
if(b->PlayerHit(&*player)){
b->dead=true;
}
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{
MonsterHurtList hitList;
for(Monster&m:MONSTER_LIST){
if(geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m.GetPos(),12*m.GetSizeMult()))){
HurtReturnValue returnVal=m.Hurt(damage,upperLevel,z);
hitList.push_back({&m,returnVal});
for(std::unique_ptr<Monster>&m:MONSTER_LIST){
if(geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){
HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z);
hitList.push_back({&*m,returnVal});
}
}
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){
MonsterHurtList affectedList;
for(Monster&m:MONSTER_LIST){
if(!hitList.count(&m)&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m.GetPos(),12*m.GetSizeMult()))){
HurtReturnValue returnVal=m.Hurt(damage,upperLevel,z);
affectedList.push_back({&m,returnVal});
hitList.insert(&m);
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()))){
HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z);
affectedList.push_back({&*m,returnVal});
hitList.insert(&*m);
}
}
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){
MonsterHurtList affectedList;
for(Monster&m:MONSTER_LIST){
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;
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()))){
float angleToMonster=geom2d::line<float>{pos,m->GetPos()}.vector().polar().y;
float angleDiff=util::angle_difference(angleToMonster,angle);
if(abs(angleDiff)<=sweepAngle){
HurtReturnValue returnVal=m.Hurt(damage,upperLevel,z);
affectedList.push_back({&m,returnVal});
hitList.insert(&m);
HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z);
affectedList.push_back({&*m,returnVal});
hitList.insert(&*m);
}
}
}
@ -851,14 +856,14 @@ void AiL::PopulateRenderLists(){
Player*pl=GetPlayer();
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(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(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){
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.OnUpperLevel()){
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);
float reflectionRatioX=abs(sin(reflectionStepTime))*"water_reflection_scale_factor"_F;
RenderPlayer(player->GetPos()+vf2d{reflectionRatioX*player->GetFrame().GetSourceRect().size.x,float(player->GetFrame().GetSourceRect().size.y)-8}*player->GetSizeMult(),{multiplierX,-1});
for(Monster&m:MONSTER_LIST){
m.DrawReflection(reflectionRatioX,multiplierX);
for(std::unique_ptr<Monster>&m:MONSTER_LIST){
m->DrawReflection(reflectionRatioX,multiplierX);
}
SetDecalMode(DecalMode::NORMAL);
}
@ -1689,8 +1694,8 @@ void AiL::RenderWorld(float fElapsedTime){
}
}
for(Monster&m:MONSTER_LIST){
m.strategyDrawOverlay(this,m,MONSTER_DATA[m.GetName()].GetAIStrategy());
for(std::unique_ptr<Monster>&m:MONSTER_LIST){
m->strategyDrawOverlay(this,*m,MONSTER_DATA[m->GetName()].GetAIStrategy());
}
#ifdef _DEBUG
@ -1805,6 +1810,10 @@ void AiL::RenderHud(){
std::string displayText=player->notificationDisplay.first;
DrawShadowStringPropDecal(vf2d{float(ScreenWidth()/2),float(ScreenHeight()/4)-24}-GetTextSizeProp(displayText)/2,displayText,BLUE,VERY_DARK_BLUE);
}
minimap.Update();
minimap.Draw();
DisplayBossEncounterInfo();
#ifdef _DEBUG
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()){
TSXParser tileset(baseDir+tag.data["source"]);
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].tileheight=tileset.GetData().tileheight;
MAP_TILESETS["assets/maps/"+baseSourceDir].tileset=r;
@ -2049,6 +2060,8 @@ void AiL::InitializeLevel(std::string mapFile,MapName map){
SetDrawTarget(nullptr);
r->Decal()->Update();
}
ComputeModeColors(MAP_TILESETS["assets/maps/"+baseSourceDir]);
}
}
@ -2215,6 +2228,13 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
});
#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)
LoadingScreen::AddPhase([&](){
std::set<vi2d>foregroundTilesAdded,upperForegroundTilesAdded;
@ -2400,9 +2420,9 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
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;
MONSTER_LIST.push_back(std::make_unique<Monster>(data.spawnPos,MONSTER_DATA[data.name]));
MONSTER_LIST.back()->iframe_timer=INFINITE;
MONSTER_LIST.back()->npcData=data;
}
}
return true;
@ -2498,18 +2518,24 @@ TilesheetData AiL::GetTileSheet(MapName map,int tileID){
if(tileData.size()==1){
size_t slashMarkerSourceDir = tileData[0].data["source"].find_last_of('/');
std::string baseSourceDir=tileData[0].data["source"].substr(slashMarkerSourceDir+1);
return {&MAP_TILESETS["assets/maps/"+baseSourceDir],1};
return {&MAP_TILESETS["assets/maps/"+baseSourceDir],1,MAP_TILESETS["assets/maps/"+baseSourceDir].tilecols[tileID]};
} else {
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){
size_t slashMarkerSourceDir = tileData[size_t(i-1)].data["source"].find_last_of('/');
std::string baseSourceDir=tileData[size_t(i-1)].data["source"].substr(slashMarkerSourceDir+1);
return {&MAP_TILESETS["assets/maps/"+baseSourceDir],stoi(tileData[i-1].data["firstgid"])};
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('/');
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){
if(pos.x<0||pos.y<0||pos.x>=GetCurrentMapData().width*game->GetCurrentMapData().tilewidth||pos.y>=GetCurrentMapData().height*game->GetCurrentMapData().tilewidth)return NO_COLLISION;
if(GetCurrentMap().optimizedTile)return NO_COLLISION; //Overworld map has no collision.
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 NO_COLLISION;
if(MAP_DATA[map].optimizedTile)return NO_COLLISION; //Overworld map has no collision.
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)){
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;
const TilesheetData&data=GetTileSheet(GetCurrentLevel(),tileID);
const TilesheetData&data=GetTileSheet(map,tileID);
if(data.tileset->isTerrain){
hasTerrain=true;
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
if(!upperLevel){ //We are looking for lower bridge collisions.
for(ZoneData&zone:MAP_DATA[map].ZoneData["LowerBridgeCollision"]){
if(geom2d::contains(zone.zone,pos)){
return {{0,0},{float(game->GetCurrentMapData().tilewidth),float(game->GetCurrentMapData().tilewidth)}};
return {{0,0},{float(mapData.tilewidth),float(mapData.tilewidth)}};
}
}
}
#pragma endregion
//The logic here is, if there's a tile on the bridge, we respect that tile instead if we're on the upper level. So we don't check other layers when we are on the upper level and there is a tile below us.
if(upperLevel&&bridgeLayerIndex!=-1){
int tileID=MAP_DATA[map].LayerData[bridgeLayerIndex].tiles[int(pos.y)/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 (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;
@ -2570,7 +2597,7 @@ geom2d::rect<float>AiL::GetTileCollision(MapName map,vf2d pos,bool upperLevel){
if(Unlock::IsUnlocked(layer.unlockCondition)){
//auto HasNoClass=[&](){return layer.tag.data.find("class")==layer.tag.data.end();};
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()){
geom2d::rect<float>collisionRect=GetTileSheet(map,tileID%1000000).tileset->collision[tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1].collision;
if(foundRect==NO_COLLISION){
@ -2590,6 +2617,25 @@ geom2d::rect<float>AiL::GetTileCollision(MapName map,vf2d pos,bool upperLevel){
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{
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;
@ -3889,12 +3935,12 @@ rcode AiL::LoadResource(Renderable&renderable,std::string_view imgPath,bool filt
}
void AiL::UpdateMonsters(){
for(Monster&m:MONSTER_LIST){
m.Update(game->GetElapsedTime());
for(std::unique_ptr<Monster>&m:MONSTER_LIST){
m->Update(game->GetElapsedTime());
}
for(Monster&m:game->monstersToBeSpawned){
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()));
}
game->monstersToBeSpawned.clear();
@ -3983,4 +4029,28 @@ void AiL::SetCompletedStageFlag(){
}
void AiL::ResetCompletedStageFlag(){
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 "DynamicCounter.h"
#include "UndefKeys.h"
#include "Minimap.h"
class SteamKeyboardCallbackHandler;
@ -74,6 +75,7 @@ class AiL : public olc::PixelGameEngine
friend class SaveFile;
friend class sig::Animation;
friend class Audio;
friend class Minimap;
std::unique_ptr<Player>player;
SplashScreen splash;
public:
@ -188,6 +190,8 @@ private:
void ValidateGameStatus();
void _PrepareLevel(MapName map,MusicChange changeMusic);
//This function assigns the mode tile colors of each loaded tileset.
void ComputeModeColors(TilesetData&tileset);
#ifndef __EMSCRIPTEN__
::discord::Result SetupDiscord();
#endif
@ -250,6 +254,7 @@ public:
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.
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.
bool HasTileCollision(MapName map,vf2d pos,bool upperLevel=false);
const MapName&GetCurrentLevel()const;
@ -329,6 +334,7 @@ public:
const bool PreviousStageCompleted()const;
void SetCompletedStageFlag();
void ResetCompletedStageFlag();
Minimap minimap;
struct TileGroupData{
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"));
}
}
for(Monster&otherM:MONSTER_LIST){
if(!otherM.AttackAvoided(m.GetZ())&&&m!=&otherM&&geom2d::overlaps(attackCircle,otherM.Hitbox())){
otherM.Knockup(ConfigFloat("Attack Knockup Duration"));
vf2d monsterDirVecNorm=geom2d::line<float>(m.GetPos(),otherM.GetPos()).vector().norm();
for(std::unique_ptr<Monster>&otherM:MONSTER_LIST){
if(!otherM->AttackAvoided(m.GetZ())&&&m!=otherM.get()&&geom2d::overlaps(attackCircle,otherM->Hitbox())){
otherM->Knockup(ConfigFloat("Attack Knockup Duration"));
vf2d monsterDirVecNorm=geom2d::line<float>(m.GetPos(),otherM->GetPos()).vector().norm();
game->GetPlayer()->Knockback(monsterDirVecNorm*ConfigFloat("Attack Knockback Amount"));
}
}

@ -85,7 +85,8 @@ struct FrogTongue:public Bullet{
float tongueLength;
float duration;
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;
bool PlayerHit(Player*player)override;
bool MonsterHit(Monster&monster)override;

@ -42,7 +42,7 @@ All rights reserved.
using BackdropName=std::string;
#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_DAMAGENUMBER_LIST extern std::vector<std::shared_ptr<DamageNumber>>DAMAGENUMBER_LIST;
#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");
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);
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.I(A::PHASE)=2;
}

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

@ -94,14 +94,14 @@ bool LightningBolt::MonsterHit(Monster& monster)
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));
int targetsHit=0;
for(Monster&m:MONSTER_LIST){
if(&m==&monster||monster.OnUpperLevel()!=m.OnUpperLevel())continue;
geom2d::line<float>lineToTarget=geom2d::line<float>(monster.GetPos(),m.GetPos());
for(std::unique_ptr<Monster>&m:MONSTER_LIST){
if(&*m==&monster||monster.OnUpperLevel()!=m->OnUpperLevel())continue;
geom2d::line<float>lineToTarget=geom2d::line<float>(monster.GetPos(),m->GetPos());
float dist=lineToTarget.length();
if(dist<="Wizard.Ability 2.LightningChainRadius"_F/100*24){
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)));
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));
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)));
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++;
}
}

@ -64,11 +64,13 @@ struct TilesetData{
std::map<int,XMLTag>staircaseTiles;
std::map<int,std::vector<int>>animationData;
std::set<int>reflectiveData;
std::vector<Pixel>tilecols;
};
struct TilesheetData{
TilesetData*tileset;
int firstgid;
Pixel tilecol;
bool operator==(const TilesheetData&rhs){
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()){
for(Monster&m:MONSTER_LIST){
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))){
m.Collision(*this);
geom2d::line line(pos,m.GetPos());
for(std::unique_ptr<Monster>&m:MONSTER_LIST){
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))){
m->Collision(*this);
geom2d::line line(pos,m->GetPos());
float dist = line.length();
m.SetPos(line.rpoint(dist*1.1f));
if(m.IsAlive()){
m->SetPos(line.rpoint(dist*1.1f));
if(m->IsAlive()){
vel=line.vector().norm()*-128;
}
}

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

@ -482,19 +482,19 @@ void Player::Update(float fElapsedTime){
if(item3.cooldown<0){
item3.cooldown=0;
}
for(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(m.IsAlive()){
m.Collision(this);
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(m->IsAlive()){
m->Collision(this);
}
geom2d::line line(pos,m.GetPos());
geom2d::line line(pos,m->GetPos());
float dist = line.length();
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{
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;
}
}
@ -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));
ForceSetPos({pos.x,float(game->GetCurrentMapData().playerSpawnLocation.y)});
}
game->minimap.UpdateChunk(GetPos()/24/"Minimap.Chunk Size"_I);
}
void Player::Spin(float duration,float spinSpd){
@ -1445,13 +1447,13 @@ const vf2d Player::GetAimingLocation(bool useWalkDir,bool invert){
}else{
//Find the closest monster target.
vf2d closestPoint={std::numeric_limits<float>::max(),std::numeric_limits<float>::max()};
for(Monster&m:MONSTER_LIST){
if(m.IsAlive()){
geom2d::line<float>aimingLine=geom2d::line<float>(GetPos(),m.GetPos());
for(std::unique_ptr<Monster>&m:MONSTER_LIST){
if(m->IsAlive()){
geom2d::line<float>aimingLine=geom2d::line<float>(GetPos(),m->GetPos());
float distToMonster=aimingLine.length();
float distToClosestPoint=geom2d::line<float>(GetPos(),closestPoint).length();
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.
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)
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_MINOR 1
#define VERSION_PATCH 0
#define VERSION_BUILD 8667
#define VERSION_BUILD 8815
#define stringify(a) stringify_(a)
#define stringify_(a) #a

@ -70,12 +70,12 @@ bool Warrior::AutoAttack(){
bool attack=false;
Monster*closest=nullptr;
float closest_dist=999999;
for(Monster&m:MONSTER_LIST){
if(m.IsAlive()
&&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){
closest_dist=geom2d::line<float>(GetWorldAimingLocation(),m.GetPos()).length();
closest=&m;
for(std::unique_ptr<Monster>&m:MONSTER_LIST){
if(m->IsAlive()&&
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){
closest_dist=geom2d::line<float>(GetWorldAimingLocation(),m->GetPos()).length();
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));
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);
for(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))){
m.AddBuff(BuffType::SLOWDOWN,"Warrior.Ability 1.SlowdownDuration"_F,"Warrior.Ability 1.SlowdownAmt"_F);
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))){
m->AddBuff(BuffType::SLOWDOWN,"Warrior.Ability 1.SlowdownDuration"_F,"Warrior.Ability 1.SlowdownAmt"_F);
}
}
SoundEffect::PlaySFX("Warrior Battlecry",SoundEffect::CENTERED);

@ -1,5 +1,5 @@
<?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>
<property name="Backdrop" propertytype="Backdrop" value="forest"/>
<property name="Background Music" propertytype="BGM" value="foresty1_1"/>

@ -1,5 +1,5 @@
<?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>
<property name="Backdrop" propertytype="Backdrop" value="forest"/>
<property name="Background Music" propertytype="BGM" value="foresty1_1"/>

@ -1,5 +1,5 @@
<?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>
<property name="Backdrop" propertytype="Backdrop" value="forest"/>
<property name="Background Music" propertytype="BGM" value="foresty_boss"/>

@ -9,8 +9,7 @@
<tileset firstgid="8138" source="../maps/Minifantasy_TinyOverworldConstructions.tsx"/>
<tileset firstgid="9286" source="../maps/Minifantasy_TinyOverworldAllProps.tsx"/>
<tileset firstgid="10806" source="../maps/Stage_Plate.tsx"/>
<tileset firstgid="11066" source=":/automap-tiles.tsx"/>
<tileset firstgid="11071" source="../maps/Tilesheet_No_Shadow24x24.tsx"/>
<tileset firstgid="11066" source="../maps/Tilesheet_No_Shadow24x24.tsx"/>
<layer id="1" name="Layer 1" width="250" height="177">
<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,

@ -40,6 +40,9 @@ monsterstrategies_config = MonsterStrategies.txt
# Interface Theme Config
themes_config = gfx/themes.txt
# Minimap Config
minimap_config = minimap.txt
# Path to theme image files
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"?>
<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="2913" source="Decorations_c1_No_Shadow24x24.tsx"/>
<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:
ViewPort();
ViewPort(std::vector<vf2d> vertices, vf2d offset = {0, 0});
geom2d::rect<float>rect;
geom2d::rect<float>rect{};
virtual ~ViewPort();
void addPoint(vf2d point);
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;
bool pointsOutside=false;
for(vf2d&points:outputList){
if(!geom2d::contains(rect,points)){
pointsOutside=true;
break;
if(rect!=geom2d::rect<float>{}){
for(vf2d&points:outputList){
if(!geom2d::contains(rect,points)){
pointsOutside=true;
break;
}
}
}
}else{pointsOutside=true;}
if(!pointsOutside)goto render;
for (auto i = 0u; i < clipVertices.size(); i++) {
auto clipA = clipVertices[i];
auto clipB = clipVertices[(i + 1) % 4];
auto clipB = clipVertices[(i + 1) % clipVertices.size()];
auto inputList{outputList};
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 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)
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(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(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(const Pixel&)>colorFunc=[](const Pixel&in){return in;});
// Draws an area of a sprite at location (x,y), where the
// 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);
@ -1608,6 +1608,7 @@ namespace olc
std::string PixelGameEngine::White;
std::string PixelGameEngine::Black;
std::string PixelGameEngine::Reset; //Will render the original color provided when used.
// O------------------------------------------------------------------------------O
// | olc::Pixel IMPLEMENTATION |
// 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)
{ DrawSprite(pos.x, pos.y, sprite, scale, flip); }
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, 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)
return;
@ -3002,7 +3003,7 @@ namespace olc
for (int32_t j = 0; j < sprite->height; j++, fy += fym)
for (uint32_t is = 0; is < scale; is++)
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
@ -3012,7 +3013,7 @@ namespace olc
{
fy = fys;
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:
HWND olc_hWnd = nullptr;
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)
{
@ -6526,6 +6538,24 @@ namespace olc
olc_hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE"), olcT(""), dwStyle,
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)
DragAcceptFiles(olc_hWnd, true);
@ -6612,14 +6642,32 @@ namespace olc
}
return olc::OK;
}
virtual void SetWindowPos(vi2d pos)override{
if(!ptrPGE->IsFullscreen()){
RECT rWndRect = { 0, 0, ptrPGE->GetWindowSize().x, ptrPGE->GetWindowSize().y };
int width = rWndRect.right - rWndRect.left;
int height = rWndRect.bottom - rWndRect.top;
MoveWindow(olc_hWnd,pos.x, pos.y, width, height,true);
int windowWidth = rWndRect.right - rWndRect.left;
int windowHeight = rWndRect.bottom - rWndRect.top;
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{
@ -6746,6 +6794,9 @@ namespace olc
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
};
RECT Platform_Windows::resultRect;
bool Platform_Windows::monitorFound=false;
}
#endif
// O------------------------------------------------------------------------------O

Loading…
Cancel
Save