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 @@
-