diff --git a/Adventures in Lestoria/Adventures in Lestoria.vcxproj b/Adventures in Lestoria/Adventures in Lestoria.vcxproj index 35b643c2..7e2958e0 100644 --- a/Adventures in Lestoria/Adventures in Lestoria.vcxproj +++ b/Adventures in Lestoria/Adventures in Lestoria.vcxproj @@ -512,6 +512,7 @@ + @@ -753,6 +754,7 @@ + diff --git a/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters b/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters index fc42a45f..2084fa2d 100644 --- a/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters +++ b/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters @@ -471,6 +471,9 @@ Header Files\State + + Header Files + @@ -830,6 +833,9 @@ Source Files\Interface + + Source Files + diff --git a/Adventures in Lestoria/AdventuresInLestoria.cpp b/Adventures in Lestoria/AdventuresInLestoria.cpp index c598492a..ba3c9e12 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.cpp +++ b/Adventures in Lestoria/AdventuresInLestoria.cpp @@ -291,6 +291,8 @@ bool AiL::OnUserCreate(){ TitleScreen::Initialize(); + Tutorial::Initialize(); + Stats::InitializeDamageReductionTable(); #ifdef __EMSCRIPTEN__ @@ -1752,7 +1754,7 @@ void AiL::RenderCooldowns(){ if(loadoutSlot!=-1){ drawOffset.y+=2.f; } - a.input->DrawInput(game,pos+vf2d{14,0.f}+drawOffset,"",255,controlType,{0.75f,0.75f}); + a.input->DrawPrimaryInput(game,pos+vf2d{14,0.f}+drawOffset,"",255,controlType,{0.75f,0.75f}); if(a.cooldown>0.1){ vf2d iconScale={1,1}; @@ -1885,6 +1887,9 @@ void AiL::InitializeLevel(std::string mapFile,MapName map){ int pctChance=100; if(data.GetValueCount()>3)pctChance=data.GetInt(3U); MAP_DATA[map].stageLoot.push_back(ItemMapData(data.GetString(0U),data.GetInt(1U),data.GetInt(2U),pctChance)); + }else + if(key.starts_with("Skip Loadout Selection")){ + MAP_DATA[map].skipLoadoutScreen=DATA["Levels"][map]["Skip Loadout Selection"].GetBool(); } } @@ -3265,6 +3270,7 @@ void AiL::SetLoadoutItem(int slot,std::string itemName){ bool AiL::UseLoadoutItem(int slot){ if(slot<0||slot>GetLoadoutSize()-1)ERR("Invalid inventory slot "+std::to_string(slot)+", please choose a slot in range (0-"+std::to_string(GetLoadoutSize()-1)+")."); if(GetLoadoutItem(slot).lock()->Amt()>0){ + Tutorial::GetTask(TutorialTaskName::USE_RECOVERY_ITEMS).I(A::ITEM_USE_COUNT)++; Inventory::UseItem(GetLoadoutItem(slot).lock()->ActualName()); Inventory::AddLoadoutItemUsed(GetLoadoutItem(slot).lock()->ActualName(),slot); GetLoadoutItem(slot).lock()->amt--; @@ -3450,6 +3456,7 @@ void AiL::ResetGame(){ State_OverworldMap::ResetConnectionPoints(); SetChapter(1); SaveFile::SetSaveFileName(""); + Tutorial::Initialize(); } void AiL::OnRequestCompleted(const std::string_view receivedData)const{ diff --git a/Adventures in Lestoria/CharacterAbilityPreviewComponent.h b/Adventures in Lestoria/CharacterAbilityPreviewComponent.h index a9b4ae0f..4c1fca48 100644 --- a/Adventures in Lestoria/CharacterAbilityPreviewComponent.h +++ b/Adventures in Lestoria/CharacterAbilityPreviewComponent.h @@ -73,6 +73,6 @@ protected: InputType controlType=KEY; if(Input::UsingGamepad())controlType=CONTROLLER; - ability->input->DrawInput(&window,textPos+vf2d{boxWidth/2,7},"",255,controlType,{0.5f,0.5f}); + ability->input->DrawPrimaryInput(&window,textPos+vf2d{boxWidth/2,7},"",255,controlType,{0.5f,0.5f}); } }; \ No newline at end of file diff --git a/Adventures in Lestoria/InputDisplayComponent.h b/Adventures in Lestoria/InputDisplayComponent.h index f5d6139c..7a80276e 100644 --- a/Adventures in Lestoria/InputDisplayComponent.h +++ b/Adventures in Lestoria/InputDisplayComponent.h @@ -57,6 +57,6 @@ public: protected: inline void DrawDecal(ViewPort&window,bool focused)override{ MenuComponent::DrawDecal(window,focused); - input.DrawInput(game,Menu::menus[parentMenu]->pos+vf2d{rect.middle().x,rect.bottom().end.y+1},""sv,255,type); + input.DrawPrimaryInput(game,Menu::menus[parentMenu]->pos+vf2d{rect.middle().x,rect.bottom().end.y+1},""sv,255,type); } }; \ No newline at end of file diff --git a/Adventures in Lestoria/ItemLoadoutWindow.cpp b/Adventures in Lestoria/ItemLoadoutWindow.cpp index 8079bf36..5dd8f6f7 100644 --- a/Adventures in Lestoria/ItemLoadoutWindow.cpp +++ b/Adventures in Lestoria/ItemLoadoutWindow.cpp @@ -52,7 +52,7 @@ void Menu::InitializeItemLoadoutWindow(){ itemLoadoutWindow->ADD("Loadout Label",MenuLabel)(geom2d::rect{{0,24},{itemLoadoutWindowWidth,24}},"Setup Item Loadout",2,ComponentAttr::SHADOW|ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)END; - itemLoadoutWindow->ADD("Loadout Map Name Label",MenuLabel)(geom2d::rect{{0,48},{itemLoadoutWindowWidth,24}},std::format("About to Enter: {}",State_OverworldMap::GetCurrentConnectionPoint().name),1,ComponentAttr::SHADOW)END; + itemLoadoutWindow->ADD("Loadout Map Name Label",MenuLabel)(geom2d::rect{{0,48},{itemLoadoutWindowWidth,24}},std::format("About to Enter: #FFFF00{}",""),1,ComponentAttr::SHADOW)END; itemLoadoutWindow->ADD("Loadout Description Label",MenuLabel)(geom2d::rect{{0,58},{itemLoadoutWindowWidth,24}},"Bring up to 3 items with you.",1,ComponentAttr::SHADOW)END; float buttonBorderPadding=64; @@ -98,7 +98,10 @@ void Menu::InitializeItemLoadoutWindow(){ itemLoadoutWindow->ADD("Item Description",MenuLabel)(geom2d::rect{{0,158},{itemLoadoutWindowWidth,24}},"",1,ComponentAttr::SHADOW)END; - itemLoadoutWindow->ADD("Start Level Button",MenuComponent)(geom2d::rect{{itemLoadoutWindowWidth/2+32,202},{64,16}},"Start",[](MenuFuncData data){State_OverworldMap::StartLevel();return true;})END; + itemLoadoutWindow->ADD("Start Level Button",MenuComponent)(geom2d::rect{{itemLoadoutWindowWidth/2+32,202},{64,16}},"Start",[](MenuFuncData data){ + State_OverworldMap::StartLevel(); + return true; + })END; itemLoadoutWindow->ADD("Back Button",MenuComponent)(geom2d::rect{{itemLoadoutWindowWidth/2-96,202},{64,16}},"Back",[](MenuFuncData data){Menu::CloseMenu();return true;})END; itemLoadoutWindow->SetupKeyboardNavigation( diff --git a/Adventures in Lestoria/Key.cpp b/Adventures in Lestoria/Key.cpp index ed719a7b..4fe00880 100644 --- a/Adventures in Lestoria/Key.cpp +++ b/Adventures in Lestoria/Key.cpp @@ -199,6 +199,11 @@ void InputGroup::AddKeybind(Input bind){ keyOrder.push_back(bind); } +void InputGroup::ClearAllKeybinds(){ + keys.clear(); + keyOrder.clear(); +} + void InputGroup::RemoveKeybind(Input bind){ size_t erased=keys.erase(bind); erased+=std::erase(keyOrder,bind); @@ -303,6 +308,102 @@ std::string InputGroup::GetDisplayName(){ } void InputGroup::DrawInput(const std::variantrenderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha,InputType type,vf2d textScale)const{ + std::vectordisplayKeys; + switch(type){ + case CONTROLLER:std::copy_if(keys.begin(),keys.end(),std::back_inserter(displayKeys),[](const Input&input){return input.GetType()==CONTROLLER;});break; + case MOUSE:std::copy_if(keys.begin(),keys.end(),std::back_inserter(displayKeys),[](const Input&input){return input.GetType()==KEY||input.GetType()==MOUSE;});break; + default:std::copy_if(keys.begin(),keys.end(),std::back_inserter(displayKeys),[](const Input&input){return input.GetType()==KEY||input.GetType()==MOUSE;});break; + } + + vf2d buttonImgSize{}; + std::vector>buttonImgs; + + for(const Input&input:displayKeys){ + if(input.HasExtendedIcons()){ + buttonImgSize.x+=input.GetIcon(GameSettings::GetIconType()).Sprite()->width*textScale.x+"Interface.InputHelperSpacing"_F; + buttonImgSize.y=std::max(buttonImgSize.y,float(input.GetIcon(GameSettings::GetIconType()).Sprite()->height)); + buttonImgs.push_back(input.GetIcon(GameSettings::GetIconType()).Decal()); + }else + if(input.HasIcon()){ + buttonImgSize.x+=input.GetIcon().Sprite()->width*textScale.x+"Interface.InputHelperSpacing"_F; + buttonImgSize.y=std::max(buttonImgSize.y,float(input.GetIcon().Sprite()->height)); + buttonImgs.push_back(input.GetIcon().Decal()); + }else{ + buttonImgSize.x+=game->GetTextSizeProp(input.GetDisplayName()).x*textScale.x+"Interface.InputHelperSpacing"_F; + buttonImgSize.y=std::max(buttonImgSize.y,float(game->GetTextSizeProp(input.GetDisplayName()).y)+"Interface.InputHelperSpacing"_F); + buttonImgs.push_back(input.GetDisplayName()); + } + } + + vf2d descriptionTextSize=game->GetTextSizeProp(displayText); + vf2d offset=-((buttonImgSize+descriptionTextSize)/2.f); + for(auto&button:buttonImgs){ + if(std::holds_alternative(button)){ + Decal*img=std::get(button); + + #pragma region Render Macro + #define Render(rendererType) \ + std::get(renderer)->DrawDecal(pos+offset-vf2d{0.f,2.f},img,textScale,{255,255,255,alpha}); + #pragma endregion + if(std::holds_alternative(renderer)){ + Render(AiL); + }else + if(std::holds_alternative(renderer)){ + Render(TileTransformedView); + }else + if(std::holds_alternative(renderer)){ + Render(ViewPort); + }else ERR("Could not find proper renderer for rendering Inputs!"); + offset.x+=img->sprite->width*textScale.x+"Interface.InputHelperSpacing"_I; + }else + if(std::holds_alternative(button)){ + std::string label=std::get(button); + vf2d textSize=game->GetTextSizeProp(label)*textScale; + Pixel buttonBackCol="Interface.InputButtonBackCol"_Pixel; + Pixel buttonTextCol="Interface.InputButtonTextCol"_Pixel; + buttonBackCol.a=alpha; + buttonTextCol.a=alpha; + + #pragma region Render Macro + #define Render(rendererType) \ + std::get(renderer)->FillRectDecal(pos+offset+vf2d{-2.f,0.f},vf2d{textSize.x+4,textSize.y},buttonBackCol); \ + std::get(renderer)->FillRectDecal(pos+offset+vf2d{-1.f,-1.f},vf2d{textSize.x+2,textSize.y},buttonBackCol); \ + std::get(renderer)->FillRectDecal(pos+offset+vf2d{-1.f,0.f},vf2d{textSize.x+2,textSize.y+1.f},buttonBackCol); \ + std::get(renderer)->DrawStringPropDecal(pos+offset+vf2d{0.f,0.f},label,buttonTextCol,textScale); + #pragma endregion + + if(std::holds_alternative(renderer)){ + Render(AiL); + }else + if(std::holds_alternative(renderer)){ + Render(TileTransformedView); + }else + if(std::holds_alternative(renderer)){ + Render(ViewPort); + }else ERR("Could not find proper renderer for rendering Inputs!"); + offset.x+=textSize.x+"Interface.InputHelperSpacing"_I; + }else [[unlikely]]ERR("WARNING! Hit a state where no data is inside of the button! THIS SHOULD NOT BE HAPPENING!"); + } + + #pragma region Render Display Text + #pragma region Render Macro + #define Render(rendererType) \ + std::get(renderer)->DrawShadowStringPropDecal(pos+offset,displayText,{255,255,255,alpha},{0,0,0,alpha},textScale); + #pragma endregion + + if(std::holds_alternative(renderer)){ + Render(AiL); + }else + if(std::holds_alternative(renderer)){ + Render(TileTransformedView); + }else + if(std::holds_alternative(renderer)){ + Render(ViewPort); + }else ERR("Could not find proper renderer for rendering Inputs!"); + #pragma endregion +} + +void InputGroup::DrawPrimaryInput(const std::variantrenderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha,InputType type,vf2d textScale)const{ std::optionalprimaryKey; switch(type){ case CONTROLLER:primaryKey=GetPrimaryKey(CONTROLLER);break; @@ -419,12 +520,12 @@ void InputGroup::DrawInput(const std::variantrenderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha)const{ +void InputGroup::DrawPrimaryInput(const std::variantrenderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha)const{ InputType primaryType; if(Input::UsingGamepad())primaryType=CONTROLLER; else if(Menu::UsingMouseNavigation())primaryType=MOUSE; else primaryType=KEY; - DrawInput(renderer,pos,displayText,alpha,primaryType); + DrawPrimaryInput(renderer,pos,displayText,alpha,primaryType); } const bool Input::HasIcon()const{ diff --git a/Adventures in Lestoria/Key.h b/Adventures in Lestoria/Key.h index b10e9fb2..3d6f752b 100644 --- a/Adventures in Lestoria/Key.h +++ b/Adventures in Lestoria/Key.h @@ -118,9 +118,11 @@ public: const bool Released(); const float Analog()const; const float AnalogDAS(const float threshold=0.2f); + void ClearAllKeybinds(); std::string GetDisplayName(); //Draws an input display with accompanying text centered at given position. - void DrawInput(const std::variantrenderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha)const; + void DrawPrimaryInput(const std::variantrenderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha)const; + void DrawPrimaryInput(const std::variantrenderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha,const InputType type,vf2d textScale={1.f,1.f})const; void DrawInput(const std::variantrenderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha,const InputType type,vf2d textScale={1.f,1.f})const; const std::optionalGetPrimaryKey(InputType type)const; }; diff --git a/Adventures in Lestoria/MonsterAttribute.h b/Adventures in Lestoria/MonsterAttribute.h index bdc3fc0c..75a00553 100644 --- a/Adventures in Lestoria/MonsterAttribute.h +++ b/Adventures in Lestoria/MonsterAttribute.h @@ -100,4 +100,8 @@ enum class Attribute{ ITEM_SLOT, MERCHANT_ITEM_SLOT, RUMBLE_TIMER, //NOTE: This timer automatically counts down inside of Menu.cpp!! Stops controller rumble when the timer ends. + ATTACK_COUNT, + ABILITY_COUNT, + DEFENSIVE_COUNT, + ITEM_USE_COUNT, }; \ No newline at end of file diff --git a/Adventures in Lestoria/NPC.cpp b/Adventures in Lestoria/NPC.cpp index 55d7d361..05d9ee4b 100644 --- a/Adventures in Lestoria/NPC.cpp +++ b/Adventures in Lestoria/NPC.cpp @@ -44,6 +44,7 @@ All rights reserved. #include "Unlock.h" #include "RowInventoryScrollableWindowComponent.h" #include "InventoryCreator.h" +#include "Tutorial.h" using A=Attribute; @@ -57,7 +58,7 @@ void Monster::STRATEGY::NPC(Monster&m,float fElapsedTime,std::string strategy){ vf2d nameTextSize=game->GetTextSizeProp(m.GetName()); uint8_t alpha=uint8_t(util::lerp(0.f,255.f,m.F(A::TARGET_TIMER)/ConfigFloat("Interaction Display Ease in Timer"))); game->view.DrawShadowStringPropDecal(m.GetPos()-vf2d{0,12}-nameTextSize/2.f,m.GetName(),{255,255,0},{0,0,0}); - game->KEY_CONFIRM.DrawInput(&game->view,m.GetPos()+vf2d{ConfigFloatArr("Interaction Display Offset",0),ConfigFloatArr("Interaction Display Offset",1)},"Interact",alpha); + game->KEY_CONFIRM.DrawPrimaryInput(&game->view,m.GetPos()+vf2d{ConfigFloatArr("Interaction Display Offset",0),ConfigFloatArr("Interaction Display Offset",1)},"Interact",alpha); }); } m.phase=1; @@ -67,6 +68,7 @@ void Monster::STRATEGY::NPC(Monster&m,float fElapsedTime,std::string strategy){ m.F(A::TARGET_TIMER)=std::min(m.F(A::TARGET_TIMER)+fElapsedTime,ConfigFloat("Interaction Display Ease in Timer")); if(game->KEY_CONFIRM.Released()&&Menu::stack.size()==0){ if(m.npcData.function=="Blacksmith"){ + Tutorial::CompleteTask(TutorialTaskName::BLACKSMITH); Menu::OpenMenu(MenuType::BLACKSMITH); //First reset all items displayed in the blacksmith's shop (showing only our equipment.) Inventory::UpdateBlacksmithInventoryLists(); diff --git a/Adventures in Lestoria/OverworldMapLevelWindow.cpp b/Adventures in Lestoria/OverworldMapLevelWindow.cpp index 1447b92f..6dd4a6af 100644 --- a/Adventures in Lestoria/OverworldMapLevelWindow.cpp +++ b/Adventures in Lestoria/OverworldMapLevelWindow.cpp @@ -38,11 +38,13 @@ All rights reserved. #include "AdventuresInLestoria.h" #include "DEFINES.h" #include "Menu.h" -#include "EncountersSpawnListScrollableWindowComponent.h" +#include "EncountersSpawnListScrollableWindowComponent.h"a #include "MenuLabel.h" #include "MenuComponent.h" #include "State_OverworldMap.h" #include "Map.h" +#include "Unlock.h" +#include "Tutorial.h" INCLUDE_game INCLUDE_MONSTER_DATA @@ -61,8 +63,20 @@ void Menu::InitializeOverworldMapLevelWindow(){ levelSelectWindow->ADD("Spawns List",EncountersSpawnListScrollableWindowComponent)(geom2d::rect{{1,64},{windowSize.x-2,84}},ComponentAttr::BACKGROUND)END; levelSelectWindow->ADD("Enter Button",MenuComponent)(geom2d::rect{{0,166},{windowSize.x-1,16}},"Enter",[](MenuFuncData data){ - Component(ITEM_LOADOUT,"Loadout Map Name Label")->SetLabel(std::format("About to Enter: {}",State_OverworldMap::GetCurrentConnectionPoint().name)); - Menu::OpenMenu(ITEM_LOADOUT); + if(State_OverworldMap::GetCurrentConnectionPoint().map=="HUB"&&Unlock::IsUnlocked("CAMPAIGN_1_3")&&!Tutorial::TaskIsComplete(TutorialTaskName::BLACKSMITH)){ + Tutorial::SetNextTask(TutorialTaskName::BLACKSMITH); + } + + if(game->MAP_DATA[State_OverworldMap::GetCurrentConnectionPoint().map].skipLoadoutScreen|| + State_OverworldMap::GetCurrentConnectionPoint().type.starts_with("STORY")){ + State_OverworldMap::StartLevel(); + }else{ + if(!Tutorial::TaskIsComplete(TutorialTaskName::SET_LOADOUT_ITEM)){ + Tutorial::SetNextTask(TutorialTaskName::SET_LOADOUT_ITEM); + } + Component(ITEM_LOADOUT,"Loadout Map Name Label")->SetLabel(std::format("About to Enter: #FFFF00{}",State_OverworldMap::GetCurrentConnectionPoint().name)); + Menu::OpenMenu(ITEM_LOADOUT); + } return true; })END; diff --git a/Adventures in Lestoria/Player.cpp b/Adventures in Lestoria/Player.cpp index f5929ce3..bdcab531 100644 --- a/Adventures in Lestoria/Player.cpp +++ b/Adventures in Lestoria/Player.cpp @@ -56,6 +56,7 @@ All rights reserved. #include "MenuLabel.h" #include "GameSettings.h" #include "Unlock.h" +#include "Tutorial.h" INCLUDE_MONSTER_DATA INCLUDE_MONSTER_LIST @@ -496,6 +497,7 @@ void Player::Update(float fElapsedTime){ if(Menu::stack.empty()){ if(CanAct()&&attack_cooldown_timer==0&&AiL::KEY_ATTACK.Held()){ + Tutorial::GetTask(TutorialTaskName::USE_ATTACK).I(A::ATTACK_COUNT)++; AutoAttack(); } @@ -506,6 +508,11 @@ void Player::Update(float fElapsedTime){ if(CanAct(ability)){ if(ability.cooldown==0&&GetMana()>=ability.manaCost){ if(key.Held()||key.Released()&&&ability==castPrepAbility&&GetState()==State::PREP_CAST){ + if(&ability==&rightClickAbility){ + Tutorial::GetTask(TutorialTaskName::USE_DEFENSIVE).I(A::DEFENSIVE_COUNT)++; + }else{ + Tutorial::GetTask(TutorialTaskName::USE_ABILITIES).I(A::ABILITY_COUNT)++; + } if(AllowedToCast(ability)&&ability.action(this,{})){ bool allowed=ability.actionPerformedDuringCast; ability.cooldown=ability.GetCooldownTime(); @@ -733,6 +740,11 @@ bool Player::Hurt(int damage,bool onUpperLevel,float z){ DAMAGENUMBER_LIST.push_back(damageNumberPtr); } lastHitTimer=0.05f; + + if(game->GetPlayer()->GetHealth()GetPlayer()->GetMaxHealth()*0.5f&&!Tutorial::TaskIsComplete(TutorialTaskName::USE_RECOVERY_ITEMS)){ + Tutorial::SetNextTask(TutorialTaskName::USE_RECOVERY_ITEMS); + } + return true; } @@ -783,6 +795,17 @@ void Player::Moved(){ } for(MonsterSpawner&spawner:SPAWNER_LIST){ if(!spawner.SpawnTriggered()&&spawner.DoesUpperLevelSpawning()==OnUpperLevel()&&geom2d::contains(geom2d::rect{spawner.GetPos(),spawner.GetRange()},pos)){ + if(GameState::STATE==GameState::states[States::GAME_RUN]){ + if(!Tutorial::TaskIsComplete(TutorialTaskName::USE_ATTACK)){ + Tutorial::SetNextTask(TutorialTaskName::USE_ATTACK); + }else + if(!Tutorial::TaskIsComplete(TutorialTaskName::USE_ABILITIES)){ + Tutorial::SetNextTask(TutorialTaskName::USE_ABILITIES); + }else + if(game->GetPlayer()->GetHealth()GetPlayer()->GetMaxHealth()*0.8f&&!Tutorial::TaskIsComplete(TutorialTaskName::USE_DEFENSIVE)){ + Tutorial::SetNextTask(TutorialTaskName::USE_DEFENSIVE); + } + } spawner.SetTriggered(true); } } diff --git a/Adventures in Lestoria/SaveFile.cpp b/Adventures in Lestoria/SaveFile.cpp index 70c7fcb9..b758e472 100644 --- a/Adventures in Lestoria/SaveFile.cpp +++ b/Adventures in Lestoria/SaveFile.cpp @@ -48,6 +48,7 @@ All rights reserved. #include "Checkbox.h" #include "InputDisplayComponent.h" #include "GameSettings.h" +#include "Tutorial.h" INCLUDE_game @@ -119,6 +120,11 @@ const void SaveFile::SaveGame(){ saveFile["Unlocks"][unlockName].SetString("False",1U); } } + + for(auto&[taskName,task]:Tutorial::taskList){ + saveFile["Tutorial"][std::to_string(int(taskName))].SetBool(Tutorial::TaskIsComplete(taskName)); + } + saveFile["Overworld Map Location"].SetString(State_OverworldMap::GetCurrentConnectionPoint().name); saveFile["Chapter"].SetInt(game->GetCurrentChapter()); saveFile["Save Name"].SetString(std::string(GetSaveFileName())); @@ -334,6 +340,14 @@ void SaveFile::LoadFile(){ } #pragma endregion + if(loadFile.HasProperty("Tutorial")){ + for(auto&[key,size]:loadFile["Tutorial"].GetKeys()){ + if(loadFile["Tutorial"][key].GetBool()){ + Tutorial::CompleteTask(TutorialTaskName(stoi(key))); + } + } + } + GameState::ChangeState(States::OVERWORLD_MAP,0.5f); }else{ std::cout<LoadLevel("HUB"); } void State_GameHub::OnLevelLoad(){ - game->UpdateDiscordStatus("Hub Area",game->GetPlayer()->GetClassName()); + game->UpdateDiscordStatus("Camp",game->GetPlayer()->GetClassName()); } void State_GameHub::OnUserUpdate(AiL*game){ State_GameRun::OnUserUpdate(game); diff --git a/Adventures in Lestoria/State_GameRun.cpp b/Adventures in Lestoria/State_GameRun.cpp index 11140fda..47b09b39 100644 --- a/Adventures in Lestoria/State_GameRun.cpp +++ b/Adventures in Lestoria/State_GameRun.cpp @@ -45,6 +45,7 @@ All rights reserved. #include "GameEvent.h" #include "MenuComponent.h" #include "Unlock.h" +#include "Tutorial.h" INCLUDE_MONSTER_LIST INCLUDE_game @@ -79,6 +80,9 @@ void State_GameRun::OnLevelLoad(){ Component(MenuType::PAUSE,"Return to Camp Button")->SetGrayedOut( !Unlock::IsUnlocked("HUB")||game->GetCurrentMapName()=="HUB" ); + if(!Tutorial::TaskIsComplete(TutorialTaskName::MOVE_AROUND)){ + Tutorial::SetNextTask(TutorialTaskName::MOVE_AROUND); + } } void State_GameRun::OnUserUpdate(AiL*game){ game->bossDisplayTimer=std::max(0.f,game->bossDisplayTimer-game->GetElapsedTime()); diff --git a/Adventures in Lestoria/State_OverworldMap.cpp b/Adventures in Lestoria/State_OverworldMap.cpp index 0ddfb280..4e9db42d 100644 --- a/Adventures in Lestoria/State_OverworldMap.cpp +++ b/Adventures in Lestoria/State_OverworldMap.cpp @@ -252,11 +252,6 @@ void State_OverworldMap::UpdateCurrentConnectionPoint(const ConnectionPoint&conn }else{ Component(OVERWORLD_LEVEL_SELECT,"Enter Button")->Disable(); } - if(currentConnectionPoint->levelDataExists&&!(currentConnectionPoint->type=="STORY"||currentConnectionPoint->type=="SHOP")){ - Component(OVERWORLD_LEVEL_SELECT,"Change Loadout Button")->Enable(); - }else{ - Component(OVERWORLD_LEVEL_SELECT,"Change Loadout Button")->Disable(); - } } std::optionalState_OverworldMap::ConnectionPointFromString(std::string_view mapName){ diff --git a/Adventures in Lestoria/TMXParser.h b/Adventures in Lestoria/TMXParser.h index efd65fe2..14babdc1 100644 --- a/Adventures in Lestoria/TMXParser.h +++ b/Adventures in Lestoria/TMXParser.h @@ -132,6 +132,7 @@ private: std::map> ZoneData; const std::map>&GetZones()const; public: + bool skipLoadoutScreen=false; const MapTag&GetMapData()const; const std::string_view GetMapType()const; const std::vector&GetStageLoot()const; diff --git a/Adventures in Lestoria/TODO.txt b/Adventures in Lestoria/TODO.txt index 3370ada6..5a2db94d 100644 --- a/Adventures in Lestoria/TODO.txt +++ b/Adventures in Lestoria/TODO.txt @@ -10,6 +10,6 @@ March 30th -> Public Demo Release - Credits/Licensing -- Basic tutorial on the first stage, Only allow the player to select "Change Loadout", explain how to setup items, can only start once a loadout item is set. - -- show inputs that can be used by the player to navigate, ability usage, and defensive. When player takes enough damage show how to use recovery items. +- Check Story 2 Bugs +- Check online username boxes (does not show the *?)\ +- Show Game Saved \ No newline at end of file diff --git a/Adventures in Lestoria/Tutorial.cpp b/Adventures in Lestoria/Tutorial.cpp index ce8ce3aa..ec243402 100644 --- a/Adventures in Lestoria/Tutorial.cpp +++ b/Adventures in Lestoria/Tutorial.cpp @@ -38,20 +38,47 @@ All rights reserved. #include "Error.h" #include "Tutorial.h" -TutorialTaskName Tutorial::currentTaskState=TutorialTaskName::CHANGE_LOADOUT; +TutorialTaskName Tutorial::currentTaskState=TutorialTaskName::SET_LOADOUT_ITEM; +std::map>Tutorial::taskList; void Tutorial::Initialize(){ - using enum TutorialTaskName; - #define CREATETASK(enum,class) taskList.insert(enum,std::make_unique()); - CREATETASK(SET_LOADOUT_ITEM,SetLoadoutItemTask); - CREATETASK(MOVE_AROUND,MoveAroundTask); + #define CREATETASK(enum,class) taskList[enum]=std::move(std::make_unique()); + CREATETASK(TutorialTaskName::SET_LOADOUT_ITEM,SetLoadoutItemTask); + CREATETASK(TutorialTaskName::MOVE_AROUND,MoveAroundTask); + CREATETASK(TutorialTaskName::USE_ATTACK,UseAttackTask); + CREATETASK(TutorialTaskName::USE_ABILITIES,UseAbilitiesTask); + CREATETASK(TutorialTaskName::USE_DEFENSIVE,UseDefensiveTask); + CREATETASK(TutorialTaskName::USE_RECOVERY_ITEMS,UseRecoveryItemsTask); + CREATETASK(TutorialTaskName::BLACKSMITH,BlacksmithTask); + currentTaskState=TutorialTaskName::SET_LOADOUT_ITEM; ResetTasks(); } +void Tutorial::SetNextTask(TutorialTaskName task){ + currentTaskState=task; + if(taskList.count(currentTaskState)){ + taskList[currentTaskState]->OnActivate(); + } +} + +void Tutorial::CompleteTask(TutorialTaskName task){ + if(taskList.count(task)){ + taskList[task]->completed=true; + Tutorial::SetNextTask(TutorialTaskName::NONE); + taskList[task]->OnComplete(); + } +} + +TutorialTask&Tutorial::GetTask(TutorialTaskName task){ + return *taskList.at(task); +} + TutorialTask::TutorialTask(){ _Initialize(); } +void TutorialTask::Draw()const{} + void TutorialTask::_Initialize(){ completed=false; systemInitializeCalled=true; @@ -69,25 +96,33 @@ const bool TutorialTask::IsComplete()const{ void TutorialTask::Update(){} +void TutorialTask::OnActivate(){} + void Tutorial::ResetTasks(){ - for(TutorialTask&tt:taskList){ - tt._Initialize(); + for(auto&[task,tt]:taskList){ + tt->_Initialize(); } } void Tutorial::Update(){ - for(TutorialTask&tt:taskList){ - if(!tt.completed){ - tt.Update(); - if(tt.CompleteCondition()){ - tt.completed=true; - tt.OnComplete(); + if(taskList.count(currentTaskState)){ + TutorialTask*currentTask=taskList[currentTaskState].get(); + if(!currentTask->completed){ + currentTask->Update(); + if(currentTask->CompleteCondition()){ + currentTask->completed=true; + Tutorial::SetNextTask(TutorialTaskName::NONE); + currentTask->OnComplete(); } } } } -void Tutorial::Draw()const{} +void Tutorial::Draw(){ + if(taskList.count(currentTaskState)){ + taskList[currentTaskState]->Draw(); + } +} const bool Tutorial::TaskIsComplete(TutorialTaskName task){ return taskList.at(task)->IsComplete(); diff --git a/Adventures in Lestoria/Tutorial.h b/Adventures in Lestoria/Tutorial.h index 27a59275..39b953c7 100644 --- a/Adventures in Lestoria/Tutorial.h +++ b/Adventures in Lestoria/Tutorial.h @@ -36,28 +36,41 @@ All rights reserved. */ #pragma endregion #pragma once -#include -#include #include "AdventuresInLestoria.h" +#include "MenuComponent.h" +#include "LoadingScreen.h" +#include "Key.h" + +using A=Attribute; INCLUDE_game +#undef BLACKSMITH + enum class TutorialTaskName{ SET_LOADOUT_ITEM, MOVE_AROUND, + USE_ATTACK, + USE_ABILITIES, + USE_DEFENSIVE, + USE_RECOVERY_ITEMS, + BLACKSMITH, + NONE, }; -class TutorialTask{ +class TutorialTask:public IAttributable{ friend class Tutorial; - protected: - bool completed=false; - bool systemInitializeCalled=false; - TutorialTask(); - private: +public: + TutorialTask(); +protected: + bool completed=false; + bool systemInitializeCalled=false; + virtual void Initialize(); +private: void _Initialize(); //Actions that occur when this task is reset/locked. DO NOT CALL DIRECTLY, USE _Initialize()! - virtual void Initialize(); virtual void Update(); + virtual void OnActivate(); virtual void Draw()const; //Returns true when the task has detected it is completed. virtual bool CompleteCondition()=0; @@ -67,19 +80,25 @@ class TutorialTask{ }; class Tutorial{ + friend class SaveFile; public: static void Initialize(); static void ResetTasks(); static void Update(); static void Draw(); static const bool TaskIsComplete(TutorialTaskName task); + static void SetNextTask(TutorialTaskName task); + static TutorialTask&GetTask(TutorialTaskName task); + static void CompleteTask(TutorialTaskName task); private: static TutorialTaskName currentTaskState; - static std::unordered_map>taskList; + static std::map>taskList; }; -class SetLoadoutItemTask:protected TutorialTask{ +class SetLoadoutItemTask:public TutorialTask{ +public: inline SetLoadoutItemTask():TutorialTask(){}; +private: virtual inline void Initialize()override final{ Component(ITEM_LOADOUT,"Start Level Button")->SetGrayedOut(true); } @@ -96,11 +115,23 @@ class SetLoadoutItemTask:protected TutorialTask{ } }; -class MoveAroundTask:protected TutorialTask{ - vf2d initialPlayerPos=vf2d{}; +class MoveAroundTask:public TutorialTask{ +public: inline MoveAroundTask():TutorialTask(){}; +private: + InputGroup moveGroup; + vf2d initialPlayerPos=vf2d{}; virtual inline void Initialize()override final{ initialPlayerPos=vf2d{}; + moveGroup.ClearAllKeybinds(); + moveGroup.AddKeybind(game->KEY_LEFT.GetPrimaryKey(InputType::CONTROLLER).value()); + moveGroup.AddKeybind(game->KEY_DOWN.GetPrimaryKey(InputType::CONTROLLER).value()); + moveGroup.AddKeybind(game->KEY_UP.GetPrimaryKey(InputType::CONTROLLER).value()); + moveGroup.AddKeybind(game->KEY_RIGHT.GetPrimaryKey(InputType::CONTROLLER).value()); + moveGroup.AddKeybind(game->KEY_LEFT.GetPrimaryKey(InputType::KEY).value()); + moveGroup.AddKeybind(game->KEY_DOWN.GetPrimaryKey(InputType::KEY).value()); + moveGroup.AddKeybind(game->KEY_UP.GetPrimaryKey(InputType::KEY).value()); + moveGroup.AddKeybind(game->KEY_RIGHT.GetPrimaryKey(InputType::KEY).value()); } virtual inline void Update()override final{ if(!LoadingScreen::loading&&initialPlayerPos==vf2d{}){ @@ -108,9 +139,148 @@ class MoveAroundTask:protected TutorialTask{ } } virtual inline bool CompleteCondition()override final{ - return initialPlayerPos!={}&&geom2d::line(initialPlayerPos,game->GetPlayer()).length()>60; + return initialPlayerPos!=vf2d{}&&geom2d::line(initialPlayerPos,game->GetPlayer()->GetPos()).length()>60; + } + virtual inline void OnComplete()override final{ + initialPlayerPos=vf2d{}; + } + virtual inline void Draw()const override final{ + if(Input::UsingGamepad()){ + moveGroup.DrawInput(game,{game->ScreenWidth()/2.f,48.f},"Move",180,InputType::CONTROLLER); + }else{ + moveGroup.DrawInput(game,{game->ScreenWidth()/2.f,48.f},"Move",180,InputType::KEY); + } + } +}; + +#define ADDKEYBIND(group,key,type) \ + group.AddKeybind(key.GetPrimaryKey(InputType::type).value());\ + if(type==InputType::KEY&&key.GetPrimaryKey(InputType::MOUSE).has_value()){ \ + group.AddKeybind(key.GetPrimaryKey(InputType::MOUSE).value()); \ + } + +class UseAttackTask:public TutorialTask{ +public: + inline UseAttackTask():TutorialTask(){}; +private: + InputGroup attackGroup; + virtual inline void OnActivate()override final{ + attackGroup.ClearAllKeybinds(); + ADDKEYBIND(attackGroup,game->KEY_ATTACK,CONTROLLER); + ADDKEYBIND(attackGroup,game->KEY_ATTACK,KEY); + } + virtual inline bool CompleteCondition()override final{ + return I(A::ATTACK_COUNT)>=10; + } + virtual inline void OnComplete()override final{} + virtual inline void Draw()const override final{ + if(Input::UsingGamepad()){ + attackGroup.DrawInput(game,{game->ScreenWidth()/2.f,48.f},"Attack",180,InputType::CONTROLLER); + }else{ + attackGroup.DrawInput(game,{game->ScreenWidth()/2.f,48.f},"Attack",180,InputType::KEY); + } + } +}; + +class UseAbilitiesTask:public TutorialTask{ +public: + inline UseAbilitiesTask():TutorialTask(){}; +private: + InputGroup abilityGroup; + virtual inline void OnActivate()override final{ + abilityGroup.ClearAllKeybinds(); + ADDKEYBIND(abilityGroup,game->GetPlayer()->KEY_ABILITY1,CONTROLLER); + ADDKEYBIND(abilityGroup,game->GetPlayer()->KEY_ABILITY1,KEY); + ADDKEYBIND(abilityGroup,game->GetPlayer()->KEY_ABILITY2,CONTROLLER); + ADDKEYBIND(abilityGroup,game->GetPlayer()->KEY_ABILITY2,KEY); + ADDKEYBIND(abilityGroup,game->GetPlayer()->KEY_ABILITY3,CONTROLLER); + ADDKEYBIND(abilityGroup,game->GetPlayer()->KEY_ABILITY3,KEY); + } + virtual inline bool CompleteCondition()override final{ + return I(A::ABILITY_COUNT)>=5; + } + virtual inline void OnComplete()override final{} + virtual inline void Draw()const override final{ + if(Input::UsingGamepad()){ + abilityGroup.DrawInput(game,{game->ScreenWidth()/2.f,48.f},"Use Abilities",180,InputType::CONTROLLER); + }else{ + abilityGroup.DrawInput(game,{game->ScreenWidth()/2.f,48.f},"Use Abilities",180,InputType::KEY); + } + } +}; + +class UseDefensiveTask:public TutorialTask{ +public: + inline UseDefensiveTask():TutorialTask(){}; +private: + InputGroup defensiveGroup; + virtual inline void OnActivate()override final{ + defensiveGroup.ClearAllKeybinds(); + ADDKEYBIND(defensiveGroup,game->GetPlayer()->KEY_DEFENSIVE,CONTROLLER); + ADDKEYBIND(defensiveGroup,game->GetPlayer()->KEY_DEFENSIVE,KEY); + } + virtual inline bool CompleteCondition()override final{ + return I(A::DEFENSIVE_COUNT)>=2; + } + virtual inline void OnComplete()override final{} + virtual inline void Draw()const override final{ + if(Input::UsingGamepad()){ + defensiveGroup.DrawInput(game,{game->ScreenWidth()/2.f,48.f},"Defensive Ability",180,InputType::CONTROLLER); + }else{ + defensiveGroup.DrawInput(game,{game->ScreenWidth()/2.f,48.f},"Defensive Ability",180,InputType::KEY); + } + } +}; + +class UseRecoveryItemsTask:public TutorialTask{ +public: + inline UseRecoveryItemsTask():TutorialTask(){}; +private: + InputGroup itemsGroup; + virtual inline void OnActivate()override final{ + itemsGroup.ClearAllKeybinds(); + if(!ISBLANK(game->GetLoadoutItem(0))){ + ADDKEYBIND(itemsGroup,game->GetPlayer()->KEY_ITEM1,CONTROLLER); + ADDKEYBIND(itemsGroup,game->GetPlayer()->KEY_ITEM1,KEY); + } + if(!ISBLANK(game->GetLoadoutItem(1))){ + ADDKEYBIND(itemsGroup,game->GetPlayer()->KEY_ITEM2,CONTROLLER); + ADDKEYBIND(itemsGroup,game->GetPlayer()->KEY_ITEM2,KEY); + } + if(!ISBLANK(game->GetLoadoutItem(2))){ + ADDKEYBIND(itemsGroup,game->GetPlayer()->KEY_ITEM3,CONTROLLER); + ADDKEYBIND(itemsGroup,game->GetPlayer()->KEY_ITEM3,KEY); + } + } + virtual inline bool CompleteCondition()override final{ + return I(A::ITEM_USE_COUNT)>=1; + } + virtual inline void OnComplete()override final{} + virtual inline void Draw()const override final{ + if(Input::UsingGamepad()){ + itemsGroup.DrawInput(game,{game->ScreenWidth()/2.f,48.f},"Use Consumables",180,InputType::CONTROLLER); + }else{ + itemsGroup.DrawInput(game,{game->ScreenWidth()/2.f,48.f},"Use Consumables",180,InputType::KEY); + } + } +}; + +class BlacksmithTask:public TutorialTask{ +public: + inline BlacksmithTask():TutorialTask(){}; +private: + virtual inline void OnActivate()override final{ + Component(SHERMAN,"Leave Button")->SetGrayedOut(true); + } + virtual inline bool CompleteCondition()override final{ + return I(A::ITEM_USE_COUNT)>=1; } virtual inline void OnComplete()override final{ - initialPlayerPos={}; + Component(SHERMAN,"Leave Button")->SetGrayedOut(false); + } + virtual inline void Draw()const override final{ + std::string helpText="Visit #00FFD0\"Greg\" the Blacksmith#FFFFFF to browse craftable gear."; + float textWidth=game->GetTextSizeProp(helpText).x; + game->DrawShadowStringPropDecal({game->ScreenWidth()/2.f-textWidth/2.f,48.f},helpText); } }; \ No newline at end of file diff --git a/Adventures in Lestoria/Version.h b/Adventures in Lestoria/Version.h index 06cb601e..16084c8d 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 7706 +#define VERSION_BUILD 7758 #define stringify(a) stringify_(a) #define stringify_(a) #a diff --git a/Adventures in Lestoria/assets/Campaigns/World_Map.tmx b/Adventures in Lestoria/assets/Campaigns/World_Map.tmx index 362cf69a..e0c21131 100644 --- a/Adventures in Lestoria/assets/Campaigns/World_Map.tmx +++ b/Adventures in Lestoria/assets/Campaigns/World_Map.tmx @@ -601,7 +601,7 @@ - + diff --git a/Adventures in Lestoria/assets/config/levels.txt b/Adventures in Lestoria/assets/config/levels.txt index c5d961b5..5c002b8f 100644 --- a/Adventures in Lestoria/assets/config/levels.txt +++ b/Adventures in Lestoria/assets/config/levels.txt @@ -2,6 +2,10 @@ map_path = assets/Campaigns/ Levels { + # Optionally specify + # Skip Loadout Selection = True + # To Skip the loadout selection screen for a given location. + INTRO_MAP { Map File = Intro_Map.tmx @@ -105,6 +109,8 @@ Levels } HUB { + Skip Loadout Selection = True + Map File = Hub_v2.tmx } } \ No newline at end of file diff --git a/x64/Release/Adventures in Lestoria.exe b/x64/Release/Adventures in Lestoria.exe index e8fabb5b..ba1141fb 100644 Binary files a/x64/Release/Adventures in Lestoria.exe and b/x64/Release/Adventures in Lestoria.exe differ