diff --git a/Adventures in Lestoria/AdventuresInLestoria.cpp b/Adventures in Lestoria/AdventuresInLestoria.cpp index bc7ff135..9db27b59 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.cpp +++ b/Adventures in Lestoria/AdventuresInLestoria.cpp @@ -1416,6 +1416,10 @@ void AiL::RenderWorld(float fElapsedTime){ } } + for(Monster&m:MONSTER_LIST){ + m.strategyDrawOverlay(this,m,MONSTER_DATA[m.GetName()].GetAIStrategy()); + } + #ifdef _DEBUG if(DEBUG_PATHFINDING){ std::vectorpathing=game->pathfinder.Solve_AStar(player.get()->GetPos(),GetWorldMousePos(),8,player.get()->OnUpperLevel()); @@ -2000,6 +2004,7 @@ void AiL::LoadLevel(MapName map){ for(NPCData data:game->MAP_DATA[game->GetCurrentLevel()].npcs){ MONSTER_LIST.push_back(Monster{data.spawnPos,MONSTER_DATA[data.name]}); MONSTER_LIST.back().iframe_timer=INFINITE; + MONSTER_LIST.back().npcData=data; } player->GetAbility1().cooldown=0.f; @@ -2555,7 +2560,11 @@ void AiL::ReduceBossEncounterMobCount(){ void AiL::RenderMenu(){ if(!GamePaused()&&Menu::stack.size()>0){ - Menu::stack.back()->Update(this); + if(Menu::alreadyClicked){ //Do not run a click event for this round. + Menu::alreadyClicked=false; + }else{ + Menu::stack.back()->Update(this); + } } if(Menu::stack.size()>0){ for(Menu*menu:Menu::stack){ diff --git a/Adventures in Lestoria/ClassSelectionWindow.cpp b/Adventures in Lestoria/ClassSelectionWindow.cpp index 2ae8c87d..2fe793da 100644 --- a/Adventures in Lestoria/ClassSelectionWindow.cpp +++ b/Adventures in Lestoria/ClassSelectionWindow.cpp @@ -78,12 +78,18 @@ void Menu::InitializeClassSelectionWindow(){ classSelectionWindow->ADD("Confirm",MenuComponent)(geom2d::rect{{outlineSize.x+4-navigationButtonSize.x-2,outlineSize.y+29-navigationButtonSize.y-2},navigationButtonSize},"Confirm",[](MenuFuncData data){ std::string selectedClass=data.component.lock()->S(A::CLASS_SELECTION); data.game->ChangePlayerClass(classutils::StringToClass(selectedClass)); - if(SaveFile::IsOnline()){ - SaveFile::SetSaveFileID(SaveFile::GetOnlineSaveFileCount()); - SaveFile::UpdateSaveGameData([](){GameState::ChangeState(States::OVERWORLD_MAP);}); - }else{ - SaveFile::SetSaveFileOfflineID_TransitionToOverworldMap(); - } + #ifdef __EMSCRIPTEN__ + if(SaveFile::IsOnline()){ + SaveFile::SetSaveFileID(SaveFile::GetOnlineSaveFileCount()); + SaveFile::UpdateSaveGameData([](){GameState::ChangeState(States::OVERWORLD_MAP);}); + }else{ + SaveFile::SetSaveFileOfflineID_TransitionToOverworldMap(); + } + #else + SaveFile::SetSaveFileID(SaveFile::GetSaveFileCount()); + SaveFile::SaveGame(); + GameState::ChangeState(States::OVERWORLD_MAP); + #endif return true; })END ->disabled=true; diff --git a/Adventures in Lestoria/Key.cpp b/Adventures in Lestoria/Key.cpp index 429dab68..f4b0bb39 100644 --- a/Adventures in Lestoria/Key.cpp +++ b/Adventures in Lestoria/Key.cpp @@ -39,6 +39,7 @@ All rights reserved. #include "DEFINES.h" #include "AdventuresInLestoria.h" #include "olcPGEX_Gamepad.h" +#include "Menu.h" INCLUDE_game INCLUDE_GFX @@ -240,6 +241,50 @@ std::string InputGroup::GetDisplayName(){ return combinationDisplay; } +void InputGroup::DrawInput(const vf2d pos,const std::string_view displayText,const uint8_t alpha)const{ + std::optionalprimaryKey; + if(Input::UsingGamepad())primaryKey=GetPrimaryKey(CONTROLLER); + else if(Menu::UsingMouseNavigation())primaryKey=GetPrimaryKey(MOUSE); + else primaryKey=GetPrimaryKey(KEY); + + vf2d buttonImgSize{}; + std::vector>buttonImgs; + if(displayText.length()>0&&primaryKey.has_value()){ + if(primaryKey.value().HasIcon()){ + buttonImgSize+=primaryKey.value().GetIcon().Sprite()->Size()+vf2d{"Interface.InputHelperSpacing"_F,"Interface.InputHelperSpacing"_F}; + buttonImgs.push_back(primaryKey.value().GetIcon().Decal()); + }else{ + buttonImgSize+=game->GetTextSizeProp(primaryKey.value().GetDisplayName())+vf2d{"Interface.InputHelperSpacing"_F,"Interface.InputHelperSpacing"_F}; + buttonImgs.push_back(primaryKey.value().GetDisplayName()); + } + } + vf2d descriptionTextSize=game->GetTextSizeProp(displayText); + vf2d offset=-((buttonImgSize+descriptionTextSize)/2.f); + if(buttonImgs.size()!=1)ERR("WARNING! Did not have two elements when rendering input button group display! THIS SHOULD NOT BE HAPPENING!") + for(auto&button:buttonImgs){ + if(std::holds_alternative(button)){ + Decal*img=std::get(button); + game->view.DrawDecal(pos+offset,img,{1.f,1.f},{255,255,255,alpha}); + offset.x+=img->sprite->width+"Interface.InputHelperSpacing"_I; + }else + if(std::holds_alternative(button)){ + std::string label=std::get(button); + vf2d textSize=game->GetTextSizeProp(label); + Pixel buttonBackCol="Interface.InputButtonBackCol"_Pixel; + Pixel buttonTextCol="Interface.InputButtonTextCol"_Pixel; + buttonBackCol.a=alpha; + buttonTextCol.a=alpha; + game->view.FillRectDecal(pos+offset+vf2d{-2.f,0.f},vf2d{textSize.x+4,textSize.y},buttonBackCol); + game->view.FillRectDecal(pos+offset+vf2d{-1.f,-1.f},vf2d{textSize.x+2,textSize.y},buttonBackCol); + game->view.FillRectDecal(pos+offset+vf2d{-1.f,0.f},vf2d{textSize.x+2,textSize.y+1.f},buttonBackCol); + game->view.DrawStringPropDecal(pos+offset+vf2d{0.f,0.f},label,buttonTextCol); + offset.x+=textSize.x+"Interface.InputHelperSpacing"_I; + }else [[unlikely]]ERR("WARNING! Hit a state where no data is inside of the button! THIS SHOULD NOT BE HAPPENING!"); + + game->view.DrawShadowStringPropDecal(pos+offset,displayText,{255,255,255,alpha},{0,0,0,alpha}); + } +} + const bool Input::HasIcon()const{ return GenericKey::keyLiteral.at({type,key}).iconName.length()>0; } @@ -247,6 +292,8 @@ const Renderable&Input::GetIcon()const{ return GFX.at(GenericKey::keyLiteral.at({type,key}).iconName); } +#undef END + std::map,GenericKey::KeyInfo> GenericKey::keyLiteral={ {{KEY, NONE},{""}}, {{KEY, A},{"A"}}, diff --git a/Adventures in Lestoria/Key.h b/Adventures in Lestoria/Key.h index e719e338..9c34044f 100644 --- a/Adventures in Lestoria/Key.h +++ b/Adventures in Lestoria/Key.h @@ -89,6 +89,8 @@ public: const bool Released()const; const float Analog()const; std::string GetDisplayName(); + //Draws an input display with accompanying text centered at given position. + void DrawInput(const vf2d pos,const std::string_view displayText,const uint8_t alpha)const; const std::optionalGetPrimaryKey(InputType type)const; }; diff --git a/Adventures in Lestoria/Menu.cpp b/Adventures in Lestoria/Menu.cpp index 25dc6d6f..38e50c51 100644 --- a/Adventures in Lestoria/Menu.cpp +++ b/Adventures in Lestoria/Menu.cpp @@ -61,6 +61,7 @@ std::vector>Menu::chapterListeners; const vf2d Menu::CENTERED = {-456,-456}; MenuType Menu::lastMenuTypeCreated; std::string Menu::lastRegisteredComponent; +bool Menu::alreadyClicked=false; INCLUDE_game INCLUDE_GFX @@ -120,12 +121,13 @@ void Menu::CheckClickAndPerformMenuSelect(AiL*game){ if(game->KEY_CONFIRM.Released()&&(MOUSE_NAVIGATION||(!MOUSE_NAVIGATION&&!game->GetMouse(Mouse::LEFT).bReleased&&!game->GetMouse(Mouse::RIGHT).bReleased&&!game->GetMouse(Mouse::MIDDLE).bReleased))){ if(!GetSelection().expired()){ //If we are on controller/gamepad it's possible we haven't highlighted a button yet, so don't click this button right away. MenuSelect(game); + Menu::alreadyClicked=true; } } } void Menu::HoverMenuSelect(AiL*game){ - if(!game->IsFocused()||selection.expired()||selection.lock()->disabled||selection.lock()->grayedOut)return; + if(!game->IsFocused()||selection.expired()||selection.lock()->disabled||selection.lock()->grayedOut||Menu::alreadyClicked)return; if(selection.lock()->draggable){ if(buttonHoldTime<"ThemeGlobal.MenuHoldTime"_F){ @@ -283,6 +285,7 @@ void Menu::Draw(AiL*game){ }; void Menu::OpenMenu(MenuType menu,bool cover){ + Menu::alreadyClicked=true; menus[menu]->cover=cover; if(menus[menu]->onOpenFunc){ Data returnData; diff --git a/Adventures in Lestoria/Menu.h b/Adventures in Lestoria/Menu.h index 39968fab..b314e5bb 100644 --- a/Adventures in Lestoria/Menu.h +++ b/Adventures in Lestoria/Menu.h @@ -119,6 +119,7 @@ class Menu:public IAttributable{ static vi2d lastActiveMousePos; int componentCount=0; float componentSelectionIndex=0.f; + static bool alreadyClicked; std::unique_ptrdraggingComponent; ViewPort window; diff --git a/Adventures in Lestoria/Monster.cpp b/Adventures in Lestoria/Monster.cpp index 2923e48f..ba3f8d13 100644 --- a/Adventures in Lestoria/Monster.cpp +++ b/Adventures in Lestoria/Monster.cpp @@ -665,6 +665,10 @@ void Monster::SetStrategyDrawFunction(std::functionfunc){ + strategyDrawOverlay=func; +} + std::mapMonster::SpawnDrops(){ std::mapdrops; for(MonsterDropData data:MONSTER_DATA.at(name).GetDropData()){ diff --git a/Adventures in Lestoria/Monster.h b/Adventures in Lestoria/Monster.h index 3066ac6a..f5a14cfa 100644 --- a/Adventures in Lestoria/Monster.h +++ b/Adventures in Lestoria/Monster.h @@ -46,6 +46,7 @@ All rights reserved. #include "safemap.h" #include "Pathfinding.h" #include "GameEvent.h" +#include "TMXParser.h" INCLUDE_ITEM_DATA @@ -181,7 +182,9 @@ public: void SetSize(float newSize,bool immediate=true); geom2d::circleHitbox(); void SetStrategyDrawFunction(std::functionfunc); + void SetStrategyDrawOverlayFunction(std::functionfunc); std::functionstrategyDraw=[](AiL*pge,Monster&m,const std::string&strategy){}; + std::functionstrategyDrawOverlay=[](AiL*pge,Monster&m,const std::string&strategy){}; const ItemAttributable&GetStats()const; const EventName&GetHurtSound(); const EventName&GetDeathSound(); @@ -239,6 +242,7 @@ private: bool isBoss=false; void OnDeath(); bool hasStrategyDeathFunction=false; + NPCData npcData; std::functionstrategyDeathFunc; void SetStrategyDeathFunction(std::functionfunc); ItemAttribute&Get(std::string_view attr); diff --git a/Adventures in Lestoria/NPC.cpp b/Adventures in Lestoria/NPC.cpp index d8315664..34de4503 100644 --- a/Adventures in Lestoria/NPC.cpp +++ b/Adventures in Lestoria/NPC.cpp @@ -37,9 +37,39 @@ All rights reserved. #pragma endregion #include "Monster.h" +#include "AdventuresInLestoria.h" +#include "MonsterStrategyHelpers.h" +#include "util.h" +#include "Menu.h" using A=Attribute; -void Monster::STRATEGY::NPC(Monster&m,float fElapsedTime,std::string strategy){ +INCLUDE_game +void Monster::STRATEGY::NPC(Monster&m,float fElapsedTime,std::string strategy){ + if(m.phase==0){ //Initialization. + if(m.npcData.function.length()>0){ + m.SetStrategyDrawOverlayFunction([](AiL*game,Monster&m,const std::string&strategy){ + game->KEY_CONFIRM.DrawInput(m.GetPos()+vf2d{ConfigFloatArr("Interaction Display Offset",0),ConfigFloatArr("Interaction Display Offset",1)},"Interact",uint8_t(util::lerp(0.f,255.f,m.F(A::TARGET_TIMER)/ConfigFloat("Interaction Display Ease in Timer")))); + }); + } + m.phase=1; + } + float distFromPlayer=geom2d::line(m.GetPos(),game->GetPlayer()->GetPos()).length(); + if(distFromPlayerKEY_CONFIRM.Released()&&Menu::stack.size()==0){ + if(m.npcData.function=="Blacksmith"){ + Menu::OpenMenu(MenuType::BLACKSMITH); + }else + if(m.npcData.function=="PotionCrafting"){ + Menu::OpenMenu(MenuType::CRAFT_CONSUMABLE); + }else + if(m.npcData.function=="TravelingMerchant"){ + Menu::OpenMenu(MenuType::MERCHANT); + } + } + }else{ + m.F(A::TARGET_TIMER)=std::max(0.f,m.F(A::TARGET_TIMER)-fElapsedTime); + } } \ No newline at end of file diff --git a/Adventures in Lestoria/RUN_STRATEGY.cpp b/Adventures in Lestoria/RUN_STRATEGY.cpp index e072835a..0220fc04 100644 --- a/Adventures in Lestoria/RUN_STRATEGY.cpp +++ b/Adventures in Lestoria/RUN_STRATEGY.cpp @@ -58,35 +58,50 @@ void Monster::InitializeStrategies(){ } int Monster::STRATEGY::_GetInt(Monster&m,std::string param,std::string strategy,int index){ - if(DATA["Monsters"][m.name].HasProperty(param)){ + if(m.IsNPC()&&DATA["NPCs"][m.name].HasProperty(param)){ + return float(DATA["NPCs"][m.name].GetProperty(param).GetInt(index)); + }else + if(!m.IsNPC()&&DATA["Monsters"][m.name].HasProperty(param)){ return DATA["Monsters"][m.name].GetProperty(param).GetInt(index); } else { return DATA["MonsterStrategy"][strategy].GetProperty(param).GetInt(index); } } float Monster::STRATEGY::_GetFloat(Monster&m,std::string param,std::string strategy,int index){ - if(DATA["Monsters"][m.name].HasProperty(param)){ + if(m.IsNPC()&&DATA["NPCs"][m.name].HasProperty(param)){ + return float(DATA["NPCs"][m.name].GetProperty(param).GetReal(index)); + }else + if(!m.IsNPC()&&DATA["Monsters"][m.name].HasProperty(param)){ return float(DATA["Monsters"][m.name].GetProperty(param).GetReal(index)); } else { return float(DATA["MonsterStrategy"][strategy].GetProperty(param).GetReal(index)); } } const std::string&Monster::STRATEGY::_GetString(Monster&m,std::string param,std::string strategy,int index){ - if(DATA["Monsters"][m.name].HasProperty(param)){ + if(m.IsNPC()&&DATA["NPCs"][m.name].HasProperty(param)){ + return DATA["NPCs"][m.name].GetProperty(param).GetString(index); + }else + if(!m.IsNPC()&&DATA["Monsters"][m.name].HasProperty(param)){ return DATA["Monsters"][m.name].GetProperty(param).GetString(index); } else { return DATA["MonsterStrategy"][strategy].GetProperty(param).GetString(index); } } datafile Monster::STRATEGY::_Get(Monster&m,std::string param,std::string strategy){ - if(DATA["Monsters"][m.name].HasProperty(param)){ + if(m.IsNPC()&&DATA["NPCs"][m.name].HasProperty(param)){ + return DATA["NPCs"][m.name].GetProperty(param); + }else + if(!m.IsNPC()&&DATA["Monsters"][m.name].HasProperty(param)){ return DATA["Monsters"][m.name].GetProperty(param); } else { return DATA["MonsterStrategy"][strategy].GetProperty(param); } } Pixel Monster::STRATEGY::_GetPixel(Monster&m,std::string param,std::string strategy,int index){ - if(DATA["Monsters"][m.name].HasProperty(param)){ + if(m.IsNPC()&&DATA["NPCs"][m.name].HasProperty(param)){ + return DATA["NPCs"][m.name].GetProperty(param).GetPixel(index); + }else + if(!m.IsNPC()&&DATA["Monsters"][m.name].HasProperty(param)){ return DATA["Monsters"][m.name].GetProperty(param).GetPixel(index); } else { return DATA["MonsterStrategy"][strategy].GetProperty(param).GetPixel(index); diff --git a/Adventures in Lestoria/TMXParser.h b/Adventures in Lestoria/TMXParser.h index 3f6e442e..c02fd05d 100644 --- a/Adventures in Lestoria/TMXParser.h +++ b/Adventures in Lestoria/TMXParser.h @@ -101,6 +101,7 @@ struct NPCData{ std::string spawnFlag=""; uint32_t roamingRange=0; vf2d spawnPos; + NPCData(); NPCData(XMLTag npcTag); }; @@ -296,6 +297,7 @@ class TMXParser{ const std::string_view Map::GetMapDisplayName()const{ return name; } + NPCData::NPCData(){} NPCData::NPCData(XMLTag npcTag){ const std::arraytags={"Function","NPC Name","Roaming Range","Spawn Flag","Spritesheet","x","y"}; diff --git a/Adventures in Lestoria/Version.h b/Adventures in Lestoria/Version.h index 403cf47b..a9caeae2 100644 --- a/Adventures in Lestoria/Version.h +++ b/Adventures in Lestoria/Version.h @@ -39,7 +39,7 @@ All rights reserved. #define VERSION_MAJOR 0 #define VERSION_MINOR 3 #define VERSION_PATCH 0 -#define VERSION_BUILD 6533 +#define VERSION_BUILD 6563 #define stringify(a) stringify_(a) #define stringify_(a) #a diff --git a/Adventures in Lestoria/assets/config/MonsterStrategies.txt b/Adventures in Lestoria/assets/config/MonsterStrategies.txt index 77a5738b..8e751bd5 100644 --- a/Adventures in Lestoria/assets/config/MonsterStrategies.txt +++ b/Adventures in Lestoria/assets/config/MonsterStrategies.txt @@ -491,5 +491,13 @@ MonsterStrategy } NPC { + # How much to offset the text for the Interaction input display. + Interaction Display Offset = 0,-16 + + # Amount of time for the interaction display input to fade in/out. + Interaction Display Ease in Timer = 0.5s + + # The maximum distance from the player that the NPC can be to interact with them. + Interaction Distance = 200 } } \ No newline at end of file diff --git a/Adventures in Lestoria/assets/maps/Tilesheet_No_Shadow24x24.tsx b/Adventures in Lestoria/assets/maps/Tilesheet_No_Shadow24x24.tsx index b114ef5d..7bb3ad13 100644 --- a/Adventures in Lestoria/assets/maps/Tilesheet_No_Shadow24x24.tsx +++ b/Adventures in Lestoria/assets/maps/Tilesheet_No_Shadow24x24.tsx @@ -1,5 +1,6 @@ + diff --git a/x64/Release/Adventures in Lestoria.exe b/x64/Release/Adventures in Lestoria.exe index 4c74b024..d03b59cc 100644 Binary files a/x64/Release/Adventures in Lestoria.exe and b/x64/Release/Adventures in Lestoria.exe differ