Added tutorial tooltips. Release Build 7758.

mac-build
sigonasr2 9 months ago
parent 65587bed88
commit 2bd3e370e5
  1. 2
      Adventures in Lestoria/Adventures in Lestoria.vcxproj
  2. 6
      Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters
  3. 9
      Adventures in Lestoria/AdventuresInLestoria.cpp
  4. 2
      Adventures in Lestoria/CharacterAbilityPreviewComponent.h
  5. 2
      Adventures in Lestoria/InputDisplayComponent.h
  6. 7
      Adventures in Lestoria/ItemLoadoutWindow.cpp
  7. 105
      Adventures in Lestoria/Key.cpp
  8. 4
      Adventures in Lestoria/Key.h
  9. 4
      Adventures in Lestoria/MonsterAttribute.h
  10. 4
      Adventures in Lestoria/NPC.cpp
  11. 18
      Adventures in Lestoria/OverworldMapLevelWindow.cpp
  12. 23
      Adventures in Lestoria/Player.cpp
  13. 14
      Adventures in Lestoria/SaveFile.cpp
  14. 2
      Adventures in Lestoria/State_GameHub.cpp
  15. 4
      Adventures in Lestoria/State_GameRun.cpp
  16. 5
      Adventures in Lestoria/State_OverworldMap.cpp
  17. 1
      Adventures in Lestoria/TMXParser.h
  18. 6
      Adventures in Lestoria/TODO.txt
  19. 63
      Adventures in Lestoria/Tutorial.cpp
  20. 192
      Adventures in Lestoria/Tutorial.h
  21. 2
      Adventures in Lestoria/Version.h
  22. 2
      Adventures in Lestoria/assets/Campaigns/World_Map.tmx
  23. 6
      Adventures in Lestoria/assets/config/levels.txt
  24. BIN
      x64/Release/Adventures in Lestoria.exe

@ -512,6 +512,7 @@
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="Tutorial.h" />
<ClInclude Include="VisualNovel.h" />
<ClInclude Include="Test.h" />
<ClInclude Include="Theme.h" />
@ -753,6 +754,7 @@
<ClCompile Include="TitleScreen.cpp" />
<ClCompile Include="Trapper.cpp" />
<ClCompile Include="Turret.cpp" />
<ClCompile Include="Tutorial.cpp" />
<ClCompile Include="Unlock.cpp" />
<ClCompile Include="Ursule.cpp" />
<ClCompile Include="UserIDMenu.cpp">

@ -471,6 +471,9 @@
<ClInclude Include="State_Death.h">
<Filter>Header Files\State</Filter>
</ClInclude>
<ClInclude Include="Tutorial.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Player.cpp">
@ -830,6 +833,9 @@
<ClCompile Include="DeathMenu.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="Tutorial.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="cpp.hint" />

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

@ -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});
}
};

@ -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);
}
};

@ -52,7 +52,7 @@ void Menu::InitializeItemLoadoutWindow(){
itemLoadoutWindow->ADD("Loadout Label",MenuLabel)(geom2d::rect<float>{{0,24},{itemLoadoutWindowWidth,24}},"Setup Item Loadout",2,ComponentAttr::SHADOW|ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)END;
itemLoadoutWindow->ADD("Loadout Map Name Label",MenuLabel)(geom2d::rect<float>{{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<float>{{0,48},{itemLoadoutWindowWidth,24}},std::format("About to Enter: #FFFF00{}",""),1,ComponentAttr::SHADOW)END;
itemLoadoutWindow->ADD("Loadout Description Label",MenuLabel)(geom2d::rect<float>{{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<float>{{0,158},{itemLoadoutWindowWidth,24}},"",1,ComponentAttr::SHADOW)END;
itemLoadoutWindow->ADD("Start Level Button",MenuComponent)(geom2d::rect<float>{{itemLoadoutWindowWidth/2+32,202},{64,16}},"Start",[](MenuFuncData data){State_OverworldMap::StartLevel();return true;})END;
itemLoadoutWindow->ADD("Start Level Button",MenuComponent)(geom2d::rect<float>{{itemLoadoutWindowWidth/2+32,202},{64,16}},"Start",[](MenuFuncData data){
State_OverworldMap::StartLevel();
return true;
})END;
itemLoadoutWindow->ADD("Back Button",MenuComponent)(geom2d::rect<float>{{itemLoadoutWindowWidth/2-96,202},{64,16}},"Back",[](MenuFuncData data){Menu::CloseMenu();return true;})END;
itemLoadoutWindow->SetupKeyboardNavigation(

@ -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::variant<AiL*const,TileTransformedView*const,ViewPort*const>renderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha,InputType type,vf2d textScale)const{
std::vector<Input>displayKeys;
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<std::variant<Decal*,std::string>>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<Decal*>(button)){
Decal*img=std::get<Decal*>(button);
#pragma region Render Macro
#define Render(rendererType) \
std::get<rendererType*const>(renderer)->DrawDecal(pos+offset-vf2d{0.f,2.f},img,textScale,{255,255,255,alpha});
#pragma endregion
if(std::holds_alternative<AiL*const>(renderer)){
Render(AiL);
}else
if(std::holds_alternative<TileTransformedView*const>(renderer)){
Render(TileTransformedView);
}else
if(std::holds_alternative<ViewPort*const>(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<std::string>(button)){
std::string label=std::get<std::string>(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<rendererType*const>(renderer)->FillRectDecal(pos+offset+vf2d{-2.f,0.f},vf2d{textSize.x+4,textSize.y},buttonBackCol); \
std::get<rendererType*const>(renderer)->FillRectDecal(pos+offset+vf2d{-1.f,-1.f},vf2d{textSize.x+2,textSize.y},buttonBackCol); \
std::get<rendererType*const>(renderer)->FillRectDecal(pos+offset+vf2d{-1.f,0.f},vf2d{textSize.x+2,textSize.y+1.f},buttonBackCol); \
std::get<rendererType*const>(renderer)->DrawStringPropDecal(pos+offset+vf2d{0.f,0.f},label,buttonTextCol,textScale);
#pragma endregion
if(std::holds_alternative<AiL*const>(renderer)){
Render(AiL);
}else
if(std::holds_alternative<TileTransformedView*const>(renderer)){
Render(TileTransformedView);
}else
if(std::holds_alternative<ViewPort*const>(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<rendererType*const>(renderer)->DrawShadowStringPropDecal(pos+offset,displayText,{255,255,255,alpha},{0,0,0,alpha},textScale);
#pragma endregion
if(std::holds_alternative<AiL*const>(renderer)){
Render(AiL);
}else
if(std::holds_alternative<TileTransformedView*const>(renderer)){
Render(TileTransformedView);
}else
if(std::holds_alternative<ViewPort*const>(renderer)){
Render(ViewPort);
}else ERR("Could not find proper renderer for rendering Inputs!");
#pragma endregion
}
void InputGroup::DrawPrimaryInput(const std::variant<AiL*const,TileTransformedView*const,ViewPort*const>renderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha,InputType type,vf2d textScale)const{
std::optional<Input>primaryKey;
switch(type){
case CONTROLLER:primaryKey=GetPrimaryKey(CONTROLLER);break;
@ -419,12 +520,12 @@ void InputGroup::DrawInput(const std::variant<AiL*const,TileTransformedView*cons
#pragma endregion
}
void InputGroup::DrawInput(const std::variant<AiL*const,TileTransformedView*const,ViewPort*const>renderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha)const{
void InputGroup::DrawPrimaryInput(const std::variant<AiL*const,TileTransformedView*const,ViewPort*const>renderer,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{

@ -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::variant<AiL*const,TileTransformedView*const,ViewPort*const>renderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha)const;
void DrawPrimaryInput(const std::variant<AiL*const,TileTransformedView*const,ViewPort*const>renderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha)const;
void DrawPrimaryInput(const std::variant<AiL*const,TileTransformedView*const,ViewPort*const>renderer,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::variant<AiL*const,TileTransformedView*const,ViewPort*const>renderer,const vf2d pos,const std::string_view displayText,const uint8_t alpha,const InputType type,vf2d textScale={1.f,1.f})const;
const std::optional<Input>GetPrimaryKey(InputType type)const;
};

@ -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,
};

@ -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();

@ -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<float>{{1,64},{windowSize.x-2,84}},ComponentAttr::BACKGROUND)END;
levelSelectWindow->ADD("Enter Button",MenuComponent)(geom2d::rect<float>{{0,166},{windowSize.x-1,16}},"Enter",[](MenuFuncData data){
Component<MenuLabel>(ITEM_LOADOUT,"Loadout Map Name Label")->SetLabel(std::format("About to Enter: {}",State_OverworldMap::GetCurrentConnectionPoint().name));
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<MenuLabel>(ITEM_LOADOUT,"Loadout Map Name Label")->SetLabel(std::format("About to Enter: #FFFF00{}",State_OverworldMap::GetCurrentConnectionPoint().name));
Menu::OpenMenu(ITEM_LOADOUT);
}
return true;
})END;

@ -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()<game->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<float>{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()<game->GetPlayer()->GetMaxHealth()*0.8f&&!Tutorial::TaskIsComplete(TutorialTaskName::USE_DEFENSIVE)){
Tutorial::SetNextTask(TutorialTaskName::USE_DEFENSIVE);
}
}
spawner.SetTriggered(true);
}
}

@ -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<<std::format("WARNING! File {} does not exist for loading!","save_file_path"_S+std::format("save.{:04}",saveFileID))<<std::endl;

@ -61,7 +61,7 @@ void State_GameHub::OnStateChange(GameState*prevState){
game->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);

@ -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<MenuComponent>(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());

@ -252,11 +252,6 @@ void State_OverworldMap::UpdateCurrentConnectionPoint(const ConnectionPoint&conn
}else{
Component<MenuComponent>(OVERWORLD_LEVEL_SELECT,"Enter Button")->Disable();
}
if(currentConnectionPoint->levelDataExists&&!(currentConnectionPoint->type=="STORY"||currentConnectionPoint->type=="SHOP")){
Component<MenuComponent>(OVERWORLD_LEVEL_SELECT,"Change Loadout Button")->Enable();
}else{
Component<MenuComponent>(OVERWORLD_LEVEL_SELECT,"Change Loadout Button")->Disable();
}
}
std::optional<ConnectionPoint*>State_OverworldMap::ConnectionPointFromString(std::string_view mapName){

@ -132,6 +132,7 @@ private:
std::map<std::string,std::vector<::ZoneData>> ZoneData;
const std::map<std::string,std::vector<::ZoneData>>&GetZones()const;
public:
bool skipLoadoutScreen=false;
const MapTag&GetMapData()const;
const std::string_view GetMapType()const;
const std::vector<ItemMapData>&GetStageLoot()const;

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

@ -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<TutorialTaskName,std::unique_ptr<TutorialTask>>Tutorial::taskList;
void Tutorial::Initialize(){
using enum TutorialTaskName;
#define CREATETASK(enum,class) taskList.insert(enum,std::make_unique<class>());
CREATETASK(SET_LOADOUT_ITEM,SetLoadoutItemTask);
CREATETASK(MOVE_AROUND,MoveAroundTask);
#define CREATETASK(enum,class) taskList[enum]=std::move(std::make_unique<class>());
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();

@ -36,28 +36,41 @@ All rights reserved.
*/
#pragma endregion
#pragma once
#include <unordered_map>
#include <memory>
#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;
public:
TutorialTask();
protected:
bool completed=false;
bool systemInitializeCalled=false;
TutorialTask();
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<TutorialTaskName,std::unique_ptr<TutorialTask>>taskList;
static std::map<TutorialTaskName,std::unique_ptr<TutorialTask>>taskList;
};
class SetLoadoutItemTask:protected TutorialTask{
class SetLoadoutItemTask:public TutorialTask{
public:
inline SetLoadoutItemTask():TutorialTask(){};
private:
virtual inline void Initialize()override final{
Component<MenuComponent>(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<float>(initialPlayerPos,game->GetPlayer()).length()>60;
return initialPlayerPos!=vf2d{}&&geom2d::line<float>(initialPlayerPos,game->GetPlayer()->GetPos()).length()>60;
}
virtual inline void OnComplete()override final{
initialPlayerPos={};
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<MenuComponent>(SHERMAN,"Leave Button")->SetGrayedOut(true);
}
virtual inline bool CompleteCondition()override final{
return I(A::ITEM_USE_COUNT)>=1;
}
virtual inline void OnComplete()override final{
Component<MenuComponent>(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);
}
};

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

@ -601,7 +601,7 @@
<property name="Unlock Condition" propertytype="Level" value="CAMPAIGN_1_2"/>
</properties>
</object>
<object id="8" name="Hub Area" type="StagePlate" x="416" y="416" width="20" height="24">
<object id="8" name="Camp" type="StagePlate" x="416" y="416" width="20" height="24">
<properties>
<property name="Map" propertytype="Level" value="HUB"/>
<property name="Type" propertytype="StageType" value="HUB"/>

@ -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
}
}
Loading…
Cancel
Save