diff --git a/Adventures in Lestoria/Adventures in Lestoria.tiled-project b/Adventures in Lestoria/Adventures in Lestoria.tiled-project
index 5f23812a..f4460e74 100644
--- a/Adventures in Lestoria/Adventures in Lestoria.tiled-project
+++ b/Adventures in Lestoria/Adventures in Lestoria.tiled-project
@@ -295,6 +295,36 @@
"type": "string",
"value": "None"
},
+ {
+ "name": "Dev Completion Time - Ranger (s)",
+ "type": "float",
+ "value": 0
+ },
+ {
+ "name": "Dev Completion Time - Thief (s)",
+ "type": "float",
+ "value": 0
+ },
+ {
+ "name": "Dev Completion Time - Trapper (s)",
+ "type": "float",
+ "value": 0
+ },
+ {
+ "name": "Dev Completion Time - Warrior (s)",
+ "type": "float",
+ "value": 0
+ },
+ {
+ "name": "Dev Completion Time - Witch (s)",
+ "type": "float",
+ "value": 0
+ },
+ {
+ "name": "Dev Completion Time - Wizard (s)",
+ "type": "float",
+ "value": 0
+ },
{
"name": "Level Type",
"propertyType": "LevelType",
@@ -618,6 +648,20 @@
"tileset"
]
},
+ {
+ "color": "#fff7ff5d",
+ "drawFill": true,
+ "id": 40,
+ "members": [
+ ],
+ "name": "TrialClock",
+ "type": "class",
+ "useAs": [
+ "property",
+ "object",
+ "project"
+ ]
+ },
{
"color": "#ffa40aa4",
"drawFill": true,
diff --git a/Adventures in Lestoria/Adventures in Lestoria.vcxproj b/Adventures in Lestoria/Adventures in Lestoria.vcxproj
index 96c4fbf3..e296c594 100644
--- a/Adventures in Lestoria/Adventures in Lestoria.vcxproj
+++ b/Adventures in Lestoria/Adventures in Lestoria.vcxproj
@@ -206,6 +206,10 @@
+
+
+
+
@@ -519,6 +523,10 @@
+
+
+
+
@@ -816,6 +824,10 @@
+
+
+
+
@@ -844,6 +856,7 @@
+
@@ -851,6 +864,7 @@
+
diff --git a/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters b/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters
index 8cf54293..2aa2f719 100644
--- a/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters
+++ b/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters
@@ -998,6 +998,9 @@
Source Files
+
+ Source Files
+
@@ -1009,6 +1012,7 @@
Header Files\steam
+
@@ -1159,6 +1163,9 @@
Configurations
+
+ Configurations
+
diff --git a/Adventures in Lestoria/AdventuresInLestoria.cpp b/Adventures in Lestoria/AdventuresInLestoria.cpp
index 81468f41..d5b2958b 100644
--- a/Adventures in Lestoria/AdventuresInLestoria.cpp
+++ b/Adventures in Lestoria/AdventuresInLestoria.cpp
@@ -80,6 +80,7 @@ All rights reserved.
#include "LoadingScreen.h"
#include "Tutorial.h"
#include "SteamKeyboardCallbackHandler.h"
+#include "SteamStatsReceivedHandler.h"
INCLUDE_EMITTER_LIST
INCLUDE_ITEM_CATEGORIES
@@ -232,6 +233,9 @@ AiL::AiL()
std::string CREDITS_CONFIG = CONFIG_PATH + "credits_config"_S;
utils::datafile::Read(DATA,CREDITS_CONFIG);
+ std::string ACHIEVEMENT_CONFIG = CONFIG_PATH + "achievement_config"_S;
+ utils::datafile::Read(DATA,ACHIEVEMENT_CONFIG);
+
utils::datafile::DEBUG_ACCESS_OPTIONS="debug_access_options"_I;
sAppName = "GAME_NAME"_S;
@@ -334,6 +338,9 @@ bool AiL::OnUserCreate(){
if(steamKeyboardCallbackListener==nullptr){
steamKeyboardCallbackListener=new SteamKeyboardCallbackHandler();
}
+ if(steamStatsReceivedHandlerListener==nullptr){
+ steamStatsReceivedHandlerListener=new SteamStatsReceivedHandler();
+ }
#endif
utils::datafile::INITIAL_SETUP_COMPLETE=true;
@@ -446,6 +453,7 @@ void AiL::HandleUserInput(float fElapsedTime){
if(KEY_MENU.Released()){
Menu::OpenMenu(MenuType::PAUSE);
}
+ float animationSpd=0.f;
if(player->GetVelocity().mag()<"Player.Move Allowed Velocity Lower Limit"_F&&player->CanMove()){
auto GetPlayerStaircaseDirection=[&](){
for(LayerTag&layer:MAP_DATA[GetCurrentLevel()].LayerData){
@@ -462,20 +470,26 @@ void AiL::HandleUserInput(float fElapsedTime){
std::string staircaseDirection=GetPlayerStaircaseDirection();
vf2d newAimingAngle{};
if(RightHeld()){
- player->SetX(player->GetX()+fElapsedTime*"Player.MoveSpd"_F*player->GetMoveSpdMult());
- player->movementVelocity.x="Player.MoveSpd"_F;
+ float moveAmt="Player.MoveSpd"_F;
+ if(Input::UsingGamepad()&&KEY_SCROLLHORZ_L.Analog()>=0.2f){
+ float controllerAmt=abs(KEY_SCROLLHORZ_L.Analog());
+ if(controllerAmt>=0.6f)controllerAmt=1.f; //Edge zone.
+ if(controllerAmt>animationSpd){
+ animationSpd=controllerAmt;
+ }
+ moveAmt*=controllerAmt;
+ }else animationSpd=1.f;
+ player->SetX(player->GetX()+fElapsedTime*moveAmt*player->GetMoveSpdMult());
+ player->movementVelocity.x=moveAmt*fElapsedTime*player->GetMoveSpdMult();
if(staircaseDirection=="RIGHT"){
player->SetY(player->GetY()-"Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult());
- player->movementVelocity.y=-"Player.StaircaseClimbSpd"_F;
+ player->movementVelocity.y=-"Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult();
} else
if(staircaseDirection=="LEFT"){
player->SetY(player->GetY()+"Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult());
- player->movementVelocity.y="Player.StaircaseClimbSpd"_F;
+ player->movementVelocity.y="Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult();
}
player->SetFacingDirection(RIGHT);
- if(player->GetState()==State::NORMAL||player->GetState()==State::PREP_CAST){
- player->UpdateWalkingAnimation(RIGHT);
- }
newAimingAngle+=vf2d{1,0};
@@ -483,21 +497,25 @@ void AiL::HandleUserInput(float fElapsedTime){
heldDownMovementKey=true;
}
if(LeftHeld()){
- player->SetX(player->GetX()-fElapsedTime*"Player.MoveSpd"_F*player->GetMoveSpdMult());
- player->movementVelocity.x=-"Player.MoveSpd"_F;
+ float moveAmt="Player.MoveSpd"_F;
+ if(Input::UsingGamepad()&&KEY_SCROLLHORZ_L.Analog()<=-0.2f){
+ float controllerAmt=abs(KEY_SCROLLHORZ_L.Analog());
+ controllerAmt+=0.2f;
+ if(controllerAmt>=0.6f)controllerAmt=1.f; //Edge zone.
+ if(controllerAmt>animationSpd){
+ animationSpd=controllerAmt;
+ }
+ moveAmt*=controllerAmt;
+ }else animationSpd=1.f;
+ player->SetX(player->GetX()-fElapsedTime*moveAmt*player->GetMoveSpdMult());
+ player->movementVelocity.x=-moveAmt*fElapsedTime*player->GetMoveSpdMult();
if(staircaseDirection=="RIGHT"){
player->SetY(player->GetY()+"Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult());
- player->movementVelocity.y="Player.StaircaseClimbSpd"_F;
+ player->movementVelocity.y="Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult();
} else
if(staircaseDirection=="LEFT"){
player->SetY(player->GetY()-"Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult());
- player->movementVelocity.y=-"Player.StaircaseClimbSpd"_F;
- }
- if(setIdleAnimation){
- player->SetFacingDirection(LEFT);
- if(player->GetState()==State::NORMAL||player->GetState()==State::PREP_CAST){
- player->UpdateWalkingAnimation(LEFT);
- }
+ player->movementVelocity.y=-"Player.StaircaseClimbSpd"_F*fElapsedTime*player->GetMoveSpdMult();
}
newAimingAngle-=vf2d{1,0};
@@ -506,14 +524,18 @@ void AiL::HandleUserInput(float fElapsedTime){
heldDownMovementKey=true;
}
if(UpHeld()){
- player->SetY(player->GetY()-fElapsedTime*"Player.MoveSpd"_F*player->GetMoveSpdMult());
- player->movementVelocity.y=-"Player.MoveSpd"_F*fElapsedTime;
- if(setIdleAnimation){
- player->SetFacingDirection(UP);
- if(player->GetState()==State::NORMAL||player->GetState()==State::PREP_CAST){
- player->UpdateWalkingAnimation(UP);
- }
- }
+ float moveAmt="Player.MoveSpd"_F;
+ if(Input::UsingGamepad()&&KEY_SCROLLVERT_L.Analog()<=-0.2f){
+ float controllerAmt=abs(KEY_SCROLLVERT_L.Analog());
+ controllerAmt+=0.2f;
+ if(controllerAmt>=0.6f)controllerAmt=1.f; //Edge zone.
+ if(controllerAmt>animationSpd){
+ animationSpd=controllerAmt;
+ }
+ moveAmt*=controllerAmt;
+ }else animationSpd=1.f;
+ player->SetY(player->GetY()-fElapsedTime*moveAmt*player->GetMoveSpdMult());
+ player->movementVelocity.y=-moveAmt*fElapsedTime*player->GetMoveSpdMult();
newAimingAngle-=vf2d{0,1};
@@ -521,14 +543,18 @@ void AiL::HandleUserInput(float fElapsedTime){
heldDownMovementKey=true;
}
if(DownHeld()){
- player->SetY(player->GetY()+fElapsedTime*"Player.MoveSpd"_F*player->GetMoveSpdMult());
- player->movementVelocity.y="Player.MoveSpd"_F*fElapsedTime;
- if(setIdleAnimation){
- player->SetFacingDirection(DOWN);
- if(player->GetState()==State::NORMAL||player->GetState()==State::PREP_CAST){
- player->UpdateWalkingAnimation(DOWN);
- }
- }
+ float moveAmt="Player.MoveSpd"_F;
+ if(Input::UsingGamepad()&&KEY_SCROLLVERT_L.Analog()>=0.2f){
+ float controllerAmt=abs(KEY_SCROLLVERT_L.Analog());
+ controllerAmt+=0.2f;
+ if(controllerAmt>=0.6f)controllerAmt=1.f; //Edge zone.
+ if(controllerAmt>animationSpd){
+ animationSpd=controllerAmt;
+ }
+ moveAmt*=controllerAmt;
+ }else animationSpd=1.f;
+ player->SetY(player->GetY()+fElapsedTime*moveAmt*player->GetMoveSpdMult());
+ player->movementVelocity.y=moveAmt*fElapsedTime*player->GetMoveSpdMult();
newAimingAngle+=vf2d{0,1};
@@ -539,6 +565,23 @@ void AiL::HandleUserInput(float fElapsedTime){
player->aimingAngle=newAimingAngle.norm().polar();
}
}
+ if(heldDownMovementKey){
+ if(abs(player->movementVelocity.x)>abs(player->movementVelocity.y)){ //Greater Horizontal movement.
+ if(player->movementVelocity.x!=0.f){
+ player->SetFacingDirection(player->movementVelocity.x>0?RIGHT:LEFT);
+ if(player->GetState()==State::NORMAL||player->GetState()==State::PREP_CAST){
+ player->UpdateWalkingAnimation(player->GetFacingDirection(),animationSpd);
+ }
+ }
+ }else{ //Greater Vertical movement.
+ if(player->movementVelocity.y!=0.f){
+ player->SetFacingDirection(player->movementVelocity.y>0?DOWN:UP);
+ if(player->GetState()==State::NORMAL||player->GetState()==State::PREP_CAST){
+ player->UpdateWalkingAnimation(player->GetFacingDirection(),animationSpd);
+ }
+ }
+ }
+ }
if(UpReleased()){
player->SetLastReleasedMovementKey(UP);
player->movementVelocity.y=0;
@@ -605,7 +648,7 @@ void AiL::HandleUserInput(float fElapsedTime){
}
if(heldDownMovementKey){
- player->footstepTimer+=GetElapsedTime();
+ player->footstepTimer+=GetElapsedTime()*animationSpd;
if(player->footstepTimer>"Player.Footstep Timer"_F){
player->footstepTimer-="Player.Footstep Timer"_F;
@@ -2461,6 +2504,9 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
});
LoadingScreen::AddPhase([&](){
+ STEAMUSERSTATS(
+ SteamUserStats()->StoreStats();
+ )
ClearGarbage();
return true;
});
@@ -2693,6 +2739,9 @@ bool Steam_Init(){
LOG(std::format("STEAM[{}]: {}",severity,std::string(message)));
});
}
+ STEAMUSERSTATS(
+ SteamUserStats()->RequestCurrentStats();
+ )
return true;
}
#endif
@@ -3705,7 +3754,6 @@ void AiL::ResetGame(bool changeToMainMenu){
for(int i=0;iClearLoadoutItem(i);
}
- Unlock::unlocks.clear();
Unlock::Initialize();
State_OverworldMap::SetStageMarker("Story I");
State_OverworldMap::UpdateCurrentConnectionPoint(*State_OverworldMap::currentConnectionPoint);
@@ -3885,4 +3933,8 @@ void AiL::ActivateActionSetForAllControllers(InputActionSetHandle_t actionSetHan
SteamInput()->ActivateActionSet(Input::steamControllers[i],actionSetHandle);
}
)
+}
+
+const float AiL::GetEncounterDuration()const{
+ return encounterDuration;
}
\ No newline at end of file
diff --git a/Adventures in Lestoria/AdventuresInLestoria.h b/Adventures in Lestoria/AdventuresInLestoria.h
index 9ee0fd58..e1ed7e99 100644
--- a/Adventures in Lestoria/AdventuresInLestoria.h
+++ b/Adventures in Lestoria/AdventuresInLestoria.h
@@ -59,6 +59,7 @@ All rights reserved.
class SteamKeyboardCallbackHandler;
+class SteamStatsReceivedHandler;
#define CreateBullet(type) BULLET_LIST.push_back(std::make_unique(type
#define EndBullet ));
@@ -189,6 +190,7 @@ private:
#endif
Audio audioEngine;
SteamKeyboardCallbackHandler*steamKeyboardCallbackListener=nullptr;
+ SteamStatsReceivedHandler*steamStatsReceivedHandlerListener=nullptr;
public:
AiL();
bool OnUserCreate() override;
@@ -316,6 +318,7 @@ public:
rcode LoadResource(Renderable&renderable,std::string_view imgPath,bool filter=false,bool clamp=true);
void UpdateMonsters();
void ActivateActionSetForAllControllers(InputActionSetHandle_t actionSetHandle);
+ const float GetEncounterDuration()const;
struct TileGroupData{
vi2d tilePos;
diff --git a/Adventures in Lestoria/Animation.cpp b/Adventures in Lestoria/Animation.cpp
index 5e8e7bbd..34164081 100644
--- a/Adventures in Lestoria/Animation.cpp
+++ b/Adventures in Lestoria/Animation.cpp
@@ -61,25 +61,25 @@ void sig::Animation::InitializeAnimations(){
};
auto SetupClassWalkIdleAnimations=[&](Renderable&sheet,std::string className){
- Animate2D::FrameSequence pl_walk_s{0.2f};
+ Animate2D::FrameSequence pl_walk_s{"Player.WalkingFrameSpd"_F};
pl_walk_s.AddFrame({&sheet,{vi2d{0,0}*24,{24,24}}});
pl_walk_s.AddFrame({&sheet,{vi2d{1,0}*24,{24,24}}});
pl_walk_s.AddFrame({&sheet,{vi2d{0,0}*24,{24,24}}});
pl_walk_s.AddFrame({&sheet,{vi2d{2,0}*24,{24,24}}});
ANIMATION_DATA[className+"_WALK_S"]=pl_walk_s;
- Animate2D::FrameSequence pl_walk_e{0.2f};
+ Animate2D::FrameSequence pl_walk_e{"Player.WalkingFrameSpd"_F};
pl_walk_e.AddFrame({&sheet,{vi2d{0,3}*24,{24,24}}});
pl_walk_e.AddFrame({&sheet,{vi2d{1,3}*24,{24,24}}});
pl_walk_e.AddFrame({&sheet,{vi2d{0,3}*24,{24,24}}});
pl_walk_e.AddFrame({&sheet,{vi2d{2,3}*24,{24,24}}});
ANIMATION_DATA[className+"_WALK_E"]=pl_walk_e;
- Animate2D::FrameSequence pl_walk_w{0.2f};
+ Animate2D::FrameSequence pl_walk_w{"Player.WalkingFrameSpd"_F};
pl_walk_w.AddFrame({&sheet,{vi2d{0,2}*24,{24,24}}});
pl_walk_w.AddFrame({&sheet,{vi2d{1,2}*24,{24,24}}});
pl_walk_w.AddFrame({&sheet,{vi2d{0,2}*24,{24,24}}});
pl_walk_w.AddFrame({&sheet,{vi2d{2,2}*24,{24,24}}});
ANIMATION_DATA[className+"_WALK_W"]=pl_walk_w;
- Animate2D::FrameSequence pl_walk_n{0.2f};
+ Animate2D::FrameSequence pl_walk_n{"Player.WalkingFrameSpd"_F};
pl_walk_n.AddFrame({&sheet,{vi2d{0,1}*24,{24,24}}});
pl_walk_n.AddFrame({&sheet,{vi2d{1,1}*24,{24,24}}});
pl_walk_n.AddFrame({&sheet,{vi2d{0,1}*24,{24,24}}});
diff --git a/Adventures in Lestoria/CharacterMenuWindow.cpp b/Adventures in Lestoria/CharacterMenuWindow.cpp
index 4c5ca8d6..e26707e3 100644
--- a/Adventures in Lestoria/CharacterMenuWindow.cpp
+++ b/Adventures in Lestoria/CharacterMenuWindow.cpp
@@ -50,6 +50,9 @@ All rights reserved.
#include "SoundEffect.h"
#include "ProgressBar.h"
#include "MenuItemLabel.h"
+#ifndef __EMSCRIPTEN__
+ #include "steam/isteamuserstats.h"
+#endif
INCLUDE_game
INCLUDE_GFX
@@ -99,6 +102,17 @@ namespace CharacterMenuWindow{
if(!comp.expired()){
if(SelectedEquipIsDifferent(comp)){ //If we find that the opposite ring slot is equipped to us, this would be an item swap or the exact same ring, therefore no stat calculations apply.
Inventory::EquipItem(comp.lock()->GetItem(),EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE)));
+
+ #pragma region Fully Decked Out Achievement
+ STEAMUSERSTATS(
+ datafile&unlock=DATA.GetProperty("Achievement.Equip Unlocks.Fully Decked Out");
+ if(Inventory::EquipsFullyMaxedOut(unlock["Weapon Max Level"].GetInt(),unlock["Armor Max Level"].GetInt())){
+ SteamUserStats()->SetAchievement(unlock["API Name"].GetString().c_str());
+ SteamUserStats()->StoreStats();
+ }
+ )
+ #pragma endregion
+
if(Menu::IsCurrentlyActive(data.menu.GetType())){
SoundEffect::PlaySFX(comp.lock()->GetItem().lock()->UseSound(),SoundEffect::CENTERED);
}
diff --git a/Adventures in Lestoria/Class.h b/Adventures in Lestoria/Class.h
index 39f059aa..680ee6e1 100644
--- a/Adventures in Lestoria/Class.h
+++ b/Adventures in Lestoria/Class.h
@@ -36,12 +36,15 @@ All rights reserved.
*/
#pragma endregion
#pragma once
-#include "Ability.h"
-#include "Animation.h"
+#include
+#include
+#include "DEFINES.h"
+#include "olcUTIL_DataFile.h"
#undef GetClassInfo
-//Classes have bit-wise operator capabilities.
+INCLUDE_DATA
+
enum Class{
ANY=0,
WARRIOR=1,
@@ -49,5 +52,15 @@ enum Class{
RANGER=4,
TRAPPER=8,
WIZARD=16,
- WITCH=32
+ WITCH=32,
+};
+
+namespace classutils{//Classes have bit-wise operator capabilities.
+ static inline Class StringToClass(std::string className){
+ const std::vector&classList=DATA["class_list"].GetValues();
+ auto it=std::find(classList.begin(),classList.end(),className);
+ if(it==classList.end())ERR(std::format("WARNING! Class {} does not exist!",className));
+ int element=int(std::distance(classList.begin(),it));
+ return Class(1<&classList=DATA["class_list"].GetValues();
- auto it=std::find(classList.begin(),classList.end(),className);
- if(it==classList.end())ERR(std::format("WARNING! Class {} does not exist!",className));
- int element=int(std::distance(classList.begin(),it));
- return Class(1<it,EquipSlot slot){
if(equippedSlot!=EquipSlot::NONE)UnequipItem(equippedSlot);
if(!GetEquip(slot).expired())UnequipItem(slot);
Inventory::equipment[slot]=it;
+
game->GetPlayer()->RecalculateEquipStats();
};
+
void Inventory::UnequipItem(EquipSlot slot){
Inventory::equipment[slot]=Item::BLANK;
game->GetPlayer()->RecalculateEquipStats();
@@ -923,6 +928,33 @@ void Item::EnhanceItem(uint8_t qty){
if(enhancementLevel+1>"Item.Item Max Enhancement Level"_I)ERR("WARNING! Attempted to enhance "<SetAchievement(unlock["API Name"].GetString().c_str());
+ SteamUserStats()->StoreStats();
+ }
+ // Equipment achievement unlocks
+ for(auto&[key,size]:DATA.GetProperty("Achievement.Equip Unlocks")){
+ datafile&unlock=DATA.GetProperty(std::format("Achievement.Equip Unlocks.{}",key));
+ if(!(unlock.HasProperty("Upgrade Requirement")&&unlock.HasProperty("Equip Slot")))continue; //Ignore any achievements that don't have an upgrade requirement/equipment slot defined.
+ if(EnhancementLevel()>=unlock["Upgrade Requirement"].GetInt()){
+ EquipSlot validSlots=EquipSlot::NONE;
+ for(const std::string&slot:unlock["Equip Slot"].GetValues()){
+ validSlots|=ItemInfo::StringToEquipSlot(slot); //Collect all the bits that this equipment can fall under.
+ }
+ if(GetEquipSlot()&validSlots){
+ //This piece of gear matches one of the provided slots.
+ SteamUserStats()->SetAchievement(unlock["API Name"].GetString().c_str());
+ SteamUserStats()->StoreStats();
+ }
+ }
+ }
+ )
+ #pragma endregion
const CraftingRequirement&consumedResources=GetEnhancementInfo()[EnhancementLevel()].craftingRequirement;
@@ -1284,4 +1316,25 @@ void Item::Lock(){
}
void Item::Unlock(){
locked=false;
+}
+
+//Specifically for the "Fully Decked Out" achievement.
+const bool Inventory::EquipsFullyMaxedOut(int maxWeaponLevel,int maxArmorLevel){
+ for(int i=int(EquipSlot::HELMET);i<=int(EquipSlot::RING2);i<<=1){
+ EquipSlot slot=EquipSlot(i);
+ if(!ISBLANK(Inventory::GetEquip(slot))){
+ std::shared_ptr- equip=Inventory::GetEquip(slot).lock();
+ if(!(equip->IsAccessory()||
+ (equip->IsWeapon()&&equip->EnhancementLevel()>=maxWeaponLevel)||
+ (equip->IsArmor()&&equip->EnhancementLevel()>=maxArmorLevel))
+ ){
+ return false;
+ }
+ }else return false;
+ }
+ return true;
+}
+
+const EquipSlot ItemInfo::StringToEquipSlot(std::string_view slotName){
+ return nameToEquipSlot[std::string(slotName)];
}
\ No newline at end of file
diff --git a/Adventures in Lestoria/Item.h b/Adventures in Lestoria/Item.h
index a6ee1974..66673a37 100644
--- a/Adventures in Lestoria/Item.h
+++ b/Adventures in Lestoria/Item.h
@@ -269,6 +269,7 @@ public:
static void AddLoadoutItemUsed(IT item,int slot);
static void ResetLoadoutItemsUsed();
static void GivePlayerLoadoutItemsUsed();
+ static const bool EquipsFullyMaxedOut(int maxWeaponLevel=10,int maxArmorLevel=10);
static bool SwapItems(ITCategory itemCategory,uint32_t slot1,uint32_t slot2);
//Makes sure this is a valid category. Will error out if it doesn't exist! Use for ERROR HANDLING!
@@ -338,6 +339,7 @@ public:
const std::string&Description()const;
const ITCategory Category()const;
const::Decal*const Decal()const;
+ static const EquipSlot StringToEquipSlot(std::string_view slotName);
/*
For the useFunc, return true if the item can be used, false otherwise.
*/
diff --git a/Adventures in Lestoria/LevelCompleteWindow.cpp b/Adventures in Lestoria/LevelCompleteWindow.cpp
index 36707f03..ae987e50 100644
--- a/Adventures in Lestoria/LevelCompleteWindow.cpp
+++ b/Adventures in Lestoria/LevelCompleteWindow.cpp
@@ -79,8 +79,10 @@ void Menu::InitializeLevelCompleteWindow(){
};
auto nextButtonAction=[](MenuFuncData data){
- Unlock::UnlockArea(State_OverworldMap::GetCurrentConnectionPoint().map);
- Merchant::RandomizeTravelingMerchant();
+ if(Component(LEVEL_COMPLETE,"Stage Complete Label")->GetLabel()!="Stage Summary"){ //If the label says stage summary, we didn't actually complete the level. Don't unlock anything new for the player.
+ Unlock::UnlockArea(State_OverworldMap::GetCurrentConnectionPoint().map);
+ Merchant::RandomizeTravelingMerchant();
+ }
State_LevelComplete::TurnOffXPSound();
GameState::ChangeState(States::GAME_HUB,0.25f);
return true;
diff --git a/Adventures in Lestoria/Monster.cpp b/Adventures in Lestoria/Monster.cpp
index a008269b..3c63d982 100644
--- a/Adventures in Lestoria/Monster.cpp
+++ b/Adventures in Lestoria/Monster.cpp
@@ -47,6 +47,10 @@ All rights reserved.
#include "MonsterAttribute.h"
#include "ItemDrop.h"
#include "SoundEffect.h"
+#include "Unlock.h"
+#ifndef __EMSCRIPTEN__
+ #include "steam/isteamuserstats.h"
+#endif
INCLUDE_ANIMATION_DATA
INCLUDE_MONSTER_DATA
@@ -723,6 +727,27 @@ void Monster::OnDeath(){
}
}
+ Unlock::IncreaseKillCount();
+
+ STEAMUSERSTATS(
+ for(auto&[key,size]:DATA.GetProperty("Achievement.Kill Unlocks")){
+ //Monster-specific achievement unlocks.
+ datafile&unlock=DATA.GetProperty(std::format("Achievement.Kill Unlocks.{}",key));
+ if(unlock.HasProperty("Monster Name")){
+ if(unlock["Monster Name"].GetString()!=GetName())continue;
+ if(unlock.HasProperty("Time Limit")&&isBoss){
+ if(game->GetEncounterDuration()<=unlock["Time Limit"].GetReal()){
+ SteamUserStats()->SetAchievement(unlock["API Name"].GetString().c_str());
+ SteamUserStats()->StoreStats();
+ }
+ }else{
+ SteamUserStats()->SetAchievement(unlock["API Name"].GetString().c_str());
+ SteamUserStats()->StoreStats();
+ }
+ }
+ }
+ )
+
if(hasStrategyDeathFunction){
GameEvent::AddEvent(std::make_unique(strategyDeathFunc,*this,MONSTER_DATA[name].GetAIStrategy()));
}
diff --git a/Adventures in Lestoria/Player.cpp b/Adventures in Lestoria/Player.cpp
index 98170254..3e6cb36f 100644
--- a/Adventures in Lestoria/Player.cpp
+++ b/Adventures in Lestoria/Player.cpp
@@ -57,6 +57,9 @@ All rights reserved.
#include "GameSettings.h"
#include "Unlock.h"
#include "Tutorial.h"
+#ifndef __EMSCRIPTEN__
+ #include "steam/isteamuserstats.h"
+#endif
INCLUDE_MONSTER_DATA
INCLUDE_MONSTER_LIST
@@ -777,9 +780,9 @@ void Player::AddAnimation(std::string state){
animation.AddState(state,ANIMATION_DATA.at(state));
}
-void Player::UpdateAnimation(std::string animState,int specificClass){
+void Player::UpdateAnimation(std::string animState,int specificClass, const float frameMult){
if(specificClass==ANY||specificClass&GetClass()){
- animation.ChangeState(internal_animState,animState);
+ animation.ChangeState(internal_animState,animState,frameMult);
}
}
@@ -868,7 +871,7 @@ void Player::Spin(float duration,float spinSpd){
spin_angle=0;
}
-void Player::UpdateWalkingAnimation(Key direction){
+void Player::UpdateWalkingAnimation(Key direction, const float frameMult){
std::string anim;
switch(direction){
case UP:anim=GetWalkNAnimation();break;
@@ -876,7 +879,7 @@ void Player::UpdateWalkingAnimation(Key direction){
case DOWN:anim=GetWalkSAnimation();break;
case LEFT:anim=GetWalkWAnimation();break;
}
- UpdateAnimation(anim);
+ UpdateAnimation(anim,0,frameMult);
}
void Player::UpdateIdleAnimation(Key direction){
@@ -1301,9 +1304,8 @@ void Player::AddXP(const uint32_t xpGain){
currentLevelXP+=xpGain;
totalXPEarned+=xpGain;
if(Level()=nextLevelXP){
- currentLevelXP-=nextLevelXP;
+ while(currentLevelXP>=NextLevelXPRequired()){
+ currentLevelXP-=NextLevelXPRequired();
SetLevel(Level()+1);
OnLevelUp();
}
@@ -1324,6 +1326,18 @@ void Player::OnLevelUp(){
stats.SetBaseStat("Health",GetBaseStat("Health")+hpGrowthRate);
stats.SetBaseStat("Attack",GetBaseStat("Attack")+atkGrowthRate);
Heal(GetBaseStat("Health"));
+
+ STEAMUSERSTATS(
+ for(auto&[key,size]:DATA.GetProperty("Achievement.Class Unlocks")){
+ datafile&unlock=DATA.GetProperty(std::format("Achievement.Class Unlocks.{}",key));
+ if(classutils::StringToClass(unlock["Class Requirement"].GetString())==GetClass()&&
+ Level()-1SetAchievement(unlock["API Name"].GetString().c_str());
+ SteamUserStats()->StoreStats();
+ }
+ }
+ )
}
const uint8_t Player::LevelCap()const{
return levelCap;
@@ -1429,17 +1443,18 @@ const vf2d Player::GetAimingLocation(bool useWalkDir,bool invert){
vf2d closestPoint={std::numeric_limits::max(),std::numeric_limits::max()};
for(Monster&m:MONSTER_LIST){
if(m.IsAlive()){
- float distToMonster=geom2d::line(GetPos(),m.GetPos()).length();
geom2d::lineaimingLine=geom2d::line(GetPos(),m.GetPos());
- vf2d aimingPoint=aimingLine.rpoint((invert?-1.f:1.f)*operator""_Pixels("Player.Aiming Cursor Max Distance"_F));
+ float distToMonster=aimingLine.length();
float distToClosestPoint=geom2d::line(GetPos(),closestPoint).length();
if(distToClosestPoint>distToMonster&&distToMonster<=operator""_Pixels("Player.Auto Aim Detection Distance"_F)){
- closestPoint=aimingPoint;
+ closestPoint=m.GetPos();
}
}
}
if(closestPoint!=vf2d{std::numeric_limits::max(),std::numeric_limits::max()}){
- return game->GetScreenSize()/2+closestPoint-GetPos();
+ geom2d::lineaimingLine=geom2d::line(GetPos(),closestPoint);
+ vf2d aimingPoint=aimingLine.rpoint(invert?(-operator""_Pixels("Player.Aiming Cursor Max Distance"_F)):std::min(aimingLine.length()+24.f,float(operator""_Pixels("Player.Aiming Cursor Max Distance"_F))));
+ return game->GetScreenSize()/2+aimingPoint-GetPos();
}else
return game->GetScreenSize()/2+vf2d{float(operator""_Pixels("Player.Aiming Cursor Max Distance"_F)),aimingAngle.y}.cart();
}
diff --git a/Adventures in Lestoria/Player.h b/Adventures in Lestoria/Player.h
index 6b2b008e..734fc402 100644
--- a/Adventures in Lestoria/Player.h
+++ b/Adventures in Lestoria/Player.h
@@ -133,7 +133,7 @@ public:
vf2d GetVelocity();
bool HasIframes();
void Update(float fElapsedTime);
- void UpdateWalkingAnimation(Key direction);
+ void UpdateWalkingAnimation(Key direction, const float frameMult=1.f);
void UpdateIdleAnimation(Key direction);
//The range is the search range in tiles.
bool CanPathfindTo(vf2d pos,vf2d targetPos,float range=8);
@@ -176,9 +176,8 @@ public:
bool Hurt(int damage,bool onUpperLevel,float z);
//Return false if healing was not possible.
bool Heal(int damage,bool suppressDamageNumber=false);
- //specificClass is a bitwise-combination of classes from the Class enum. It makes sure certain animations only play if you are a certain class.
- //Set force to true to force the animation to restart evne if the animation were already playing.
- void UpdateAnimation(std::string animState,int specificClass=ANY);
+ //specificClass is a bitwise-combination of classes from the Class enum. It makes sure certain animations only play if you are a certain class.=
+ void UpdateAnimation(std::string animState,int specificClass=ANY,const float frameMult=1.f);
Animate2D::Frame GetFrame();
Key GetLastReleasedMovementKey();
float GetSwordSwingTimer();
diff --git a/Adventures in Lestoria/SaveFile.cpp b/Adventures in Lestoria/SaveFile.cpp
index a21d5785..708477fd 100644
--- a/Adventures in Lestoria/SaveFile.cpp
+++ b/Adventures in Lestoria/SaveFile.cpp
@@ -178,7 +178,14 @@ const void SaveFile::SaveGame(){
saveSystemFile["Fullscreen"].SetBool(game->IsFullscreen());
#pragma endregion
+ saveFile["Hash"].SetString("");
+
utils::datafile::Write(saveFile,"save_file_path"_S+std::format("save.{:04}",saveFileID));
+
+ std::string fileHash=util::GetHash("save_file_path"_S+std::format("save.{:04}",saveFileID));
+ saveFile["Hash"].SetString(fileHash);
+
+ utils::datafile::Write(saveFile,"save_file_path"_S+std::format("save.{:04}",saveFileID)); //Once the hash has been computed and added, save the file a second time.
utils::datafile::Write(saveSystemFile,"save_file_path"_S+"system.conf");
utils::datafile metadata;
if(onlineMode){
@@ -279,6 +286,24 @@ void SaveFile::LoadFile(){
if(std::filesystem::exists(loadFilename)){
utils::datafile::Read(loadFile,"save_file_path"_S+std::format("save.{:04}",saveFileID));
+ if(!loadFile.HasProperty("Hash")){
+ LOG(std::format("WARNING! Filehash for file {} does not exist!","save_file_path"_S+std::format("save.{:04}",saveFileID)));
+ return;
+ }
+ if(loadFile.HasProperty("Hash")){
+ std::string expectedFileHash=loadFile["Hash"].GetString();
+ loadFile["Hash"].SetString("");
+ utils::datafile::Write(loadFile,"save_file_path"_S+std::format("save.{:04}",saveFileID));
+ std::string fileHash=util::GetHash("save_file_path"_S+std::format("save.{:04}",saveFileID));
+
+ if(expectedFileHash!=fileHash){
+ LOG(std::format("WARNING! Filehash for file {} was not identified as proper! Will not load this file!","save_file_path"_S+std::format("save.{:04}",saveFileID)));
+ return;
+ }
+
+ loadFile["Hash"].SetString(expectedFileHash); //Now write the hash back into the file since we tampered with it.
+ utils::datafile::Write(loadFile,"save_file_path"_S+std::format("save.{:04}",saveFileID));
+ }
game->ResetGame();
for(auto&[key,data]:loadFile["Items"].GetOrderedKeys()){
std::weak_ptr
- newItem=Inventory::AddItem(data["Item Name"].GetString(),data["Amt"].GetInt());
diff --git a/Adventures in Lestoria/SettingsWindow.cpp b/Adventures in Lestoria/SettingsWindow.cpp
index 9911b0c8..428a4efb 100644
--- a/Adventures in Lestoria/SettingsWindow.cpp
+++ b/Adventures in Lestoria/SettingsWindow.cpp
@@ -249,7 +249,7 @@ void Menu::InitializeSettingsWindow(){
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
}
,{ //Button Navigation Rules
- {"BGM Slider",{
+ {"BGM Slider",{
.up="Go Back",
.down="SFX Slider",}},
{"SFX Slider",{
@@ -260,7 +260,7 @@ void Menu::InitializeSettingsWindow(){
.down="Screen Shake Checkbox",
.left="Show Max Mana Checkbox",
.right="Show Max Mana Checkbox",}},
- {"Show Max Mana Checkbox",{
+ {"Show Max Mana Checkbox",{
.up="SFX Slider",
.down="Terrain Collision Boxes Checkbox",
.left="Show Max HP Checkbox",
diff --git a/Adventures in Lestoria/ShermanWindow.cpp b/Adventures in Lestoria/ShermanWindow.cpp
index f7425b78..48117038 100644
--- a/Adventures in Lestoria/ShermanWindow.cpp
+++ b/Adventures in Lestoria/ShermanWindow.cpp
@@ -41,6 +41,7 @@ All rights reserved.
#include "GameState.h"
#include "AdventuresInLestoria.h"
#include "Unlock.h"
+#include "MenuLabel.h"
INCLUDE_game
@@ -48,7 +49,9 @@ void Menu::InitializeShermanWindow(){
Menu*shermanWindow=CreateMenu(SHERMAN,CENTERED,vi2d{144,88});
shermanWindow->ADD("Leave Button",MenuComponent)(geom2d::rect{{0.f,4.f},{144.f,24.f}},"Leave",[](MenuFuncData data){
- Unlock::UnlockCurrentMap();
+ if(Component(LEVEL_COMPLETE,"Stage Complete Label")->GetLabel()!="Stage Summary"){ //If the label says stage summary, we didn't actually complete the level. Don't unlock anything new for the player.
+ Unlock::UnlockCurrentMap();
+ }
GameState::ChangeState(States::OVERWORLD_MAP,0.3f);
return true;
},vf2d{2.f,2.f},ButtonAttr::FIT_TO_LABEL)END;
diff --git a/Adventures in Lestoria/State_GameHub.cpp b/Adventures in Lestoria/State_GameHub.cpp
index f1219ed5..b64029b6 100644
--- a/Adventures in Lestoria/State_GameHub.cpp
+++ b/Adventures in Lestoria/State_GameHub.cpp
@@ -77,6 +77,7 @@ void State_GameHub::OnLevelLoad(){
}
void State_GameHub::OnUserUpdate(AiL*game){
State_GameRun::OnUserUpdate(game);
+
game->ClearTimedOutGarbage();
}
void State_GameHub::Draw(AiL*game){
diff --git a/Adventures in Lestoria/State_MainMenu.cpp b/Adventures in Lestoria/State_MainMenu.cpp
index 0fe0480e..1005bff9 100644
--- a/Adventures in Lestoria/State_MainMenu.cpp
+++ b/Adventures in Lestoria/State_MainMenu.cpp
@@ -42,6 +42,9 @@ All rights reserved.
#include "Key.h"
#include "ItemDrop.h"
#include "util.h"
+#ifndef __EMSCRIPTEN__
+ #include "steam/isteamuserstats.h"
+#endif
INCLUDE_game
diff --git a/Adventures in Lestoria/SteamStatsReceivedHandler.cpp b/Adventures in Lestoria/SteamStatsReceivedHandler.cpp
new file mode 100644
index 00000000..e2a55c30
--- /dev/null
+++ b/Adventures in Lestoria/SteamStatsReceivedHandler.cpp
@@ -0,0 +1,54 @@
+#pragma region License
+/*
+License (OLC-3)
+~~~~~~~~~~~~~~~
+
+Copyright 2024 Joshua Sigona
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions or derivations of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+2. Redistributions or derivative works in binary form must reproduce the above
+copyright notice. This list of conditions and the following disclaimer must be
+reproduced in the documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors may
+be used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+Portions of this software are copyright © 2024 The FreeType
+Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
+All rights reserved.
+*/
+#pragma endregion
+#include "SteamStatsReceivedHandler.h"
+#include "olcUTIL_DataFile.h"
+#include "DEFINES.h"
+#include "Unlock.h"
+#include "config.h"
+#include "Error.h"
+
+INCLUDE_DATA
+
+#ifndef __EMSCRIPTEN__
+ void SteamStatsReceivedHandler::SteamStatsReceived( UserStatsReceived_t* pCallback ){
+ if(pCallback->m_eResult==k_EResultOK){
+ SteamUserStats()->GetStat("Achievement.Kill Unlocks.Total Kill API Name"_S.c_str(),&Unlock::monsterKillCount);
+ LOG(std::format("Retrieved monster kill count: {}",Unlock::monsterKillCount));
+ }
+ }
+#endif
\ No newline at end of file
diff --git a/Adventures in Lestoria/SteamStatsReceivedHandler.h b/Adventures in Lestoria/SteamStatsReceivedHandler.h
new file mode 100644
index 00000000..f5f7f9ee
--- /dev/null
+++ b/Adventures in Lestoria/SteamStatsReceivedHandler.h
@@ -0,0 +1,47 @@
+#pragma region License
+/*
+License (OLC-3)
+~~~~~~~~~~~~~~~
+
+Copyright 2024 Joshua Sigona
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions or derivations of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+2. Redistributions or derivative works in binary form must reproduce the above
+copyright notice. This list of conditions and the following disclaimer must be
+reproduced in the documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors may
+be used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+Portions of this software are copyright © 2024 The FreeType
+Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
+All rights reserved.
+*/
+#pragma endregion
+#pragma once
+#ifndef __EMSCRIPTEN__
+ #include "steam/steam_api.h"
+#endif
+
+#ifndef __EMSCRIPTEN__
+class SteamStatsReceivedHandler{
+ STEAM_CALLBACK(SteamStatsReceivedHandler,SteamStatsReceived,UserStatsReceived_t);
+};
+#endif
\ No newline at end of file
diff --git a/Adventures in Lestoria/TMXParser.h b/Adventures in Lestoria/TMXParser.h
index cae908d5..ccc590ef 100644
--- a/Adventures in Lestoria/TMXParser.h
+++ b/Adventures in Lestoria/TMXParser.h
@@ -44,6 +44,8 @@ All rights reserved.
#include "DEFINES.h"
#include "safemap.h"
#include "ItemMapData.h"
+#include "Class.h"
+
using MapName=std::string;
using namespace olc;
@@ -126,6 +128,7 @@ private:
std::vectornpcs;
std::string mapType="";
std::string bgmSongName="";
+ std::unordered_mapdevCompletionTrialTime;
BackdropName backdrop="";
std::setspawns;
std::map SpawnerData; //Spawn groups have IDs, mobs associate which spawner they are tied to via this ID.
@@ -138,6 +141,7 @@ public:
const std::vector&GetStageLoot()const;
const std::vector&GetLayers()const;
const std::vector&GetEnvironmentalAudio()const;
+ const float GetDevCompletionTime(Class cl)const;
const MapName&GetMapName()const;
const std::string_view GetMapDisplayName()const;
std::string FormatLayerData(std::ostream& os, std::vectortiles);
@@ -464,6 +468,13 @@ class TMXParser{
parsedMapInfo.bgmSongName=newTag.data["value"];
}
} else
+ if (newTag.tag=="property"&&newTag.data["name"].starts_with("Dev Completion Time")) {
+ if(newTag.data.count("value")){ //None is a default value that we ignore.
+ size_t classStartPos="Dev Completion Time - "s.length();
+ std::string className=newTag.data["name"].substr(classStartPos,newTag.data["name"].find(' ',classStartPos)-classStartPos);
+ parsedMapInfo.devCompletionTrialTime[classutils::StringToClass(className)]=newTag.GetFloat("value");
+ }
+ } else
if (newTag.tag=="property"&&newTag.data["name"]=="Backdrop") {
if(newTag.data["value"]!="None"){ //None is a default value that we ignore.
parsedMapInfo.backdrop=newTag.data["value"];
diff --git a/Adventures in Lestoria/TODO.txt b/Adventures in Lestoria/TODO.txt
index 2be30110..c8c3e0d0 100644
--- a/Adventures in Lestoria/TODO.txt
+++ b/Adventures in Lestoria/TODO.txt
@@ -1,3 +1,18 @@
-============================================
+Time Trial Idea
+
+Upon completing a stage for the first time, the player is shown the completion time and a record time.
+ On first clears, the player will always obtain a new record. Make it apparent to the player they obtained a new record.
+
+The overworld map will show a new section that says "Completion Time" for any previous completed stages.
+Upon the second time entering a stage, the game will spawn a timer that the player can run into to begin a time trial-like mode.
+ During the Time Trial mode the player can defeat monsters to freeze the timer by 1 second per mob killed.
+Upon completion of a stage in time trial mode if the player beat their previous time (which they likely will) the record will update.
+ For each class and stage combination there will be a "dev time"
+
+Settings menu doesn't scroll back up properly while the scrollbar does?
+Merchant descriptions have no newlines.
+
+============================================
+Consider a "killed by player" / "marked by player" flag for monsters to determine if a player gets credit for a monster kill (for achievements)
Make another actions config file for the main build (The app # is different)
\ No newline at end of file
diff --git a/Adventures in Lestoria/Unlock.cpp b/Adventures in Lestoria/Unlock.cpp
index eeae723c..cd5a550e 100644
--- a/Adventures in Lestoria/Unlock.cpp
+++ b/Adventures in Lestoria/Unlock.cpp
@@ -38,16 +38,33 @@ All rights reserved.
#include "Unlock.h"
#include "State_OverworldMap.h"
#include "config.h"
+#include "olcUTIL_DataFile.h"
+#include "DEFINES.h"
+#include "emscripten_compat.h"
+#ifndef __EMSCRIPTEN__
+ #include "steam/isteamuserstats.h"
+#endif
+
+INCLUDE_DATA
std::setUnlock::unlocks;
+int Unlock::monsterKillCount=0;
void Unlock::Initialize(){
+ unlocks.clear();
UnlockArea("WORLD_MAP");
}
void Unlock::UnlockArea(std::string mapName){
if(mapName=="NPCs.Sherman.Potion Crafting Unlock Condition"_S&& //When we beat the bonus chapter 1 fight, before sherman's potion crafting is unlocked, if the current map we just unlocked for is the bonus boss stage we will notify the Hub connection point and reset it so the player has a notification to go there again.
!Unlock::IsUnlocked("NPCs.Sherman.Potion Crafting Unlock Condition"_S))State_OverworldMap::ConnectionPointFromString("HUB").value()->ResetVisitedFlag();
+ STEAMUSERSTATS(
+ datafile&areaUnlocks=DATA.GetProperty("Achievement.Area Unlocks");
+ for(auto&[key,size]:areaUnlocks){
+ datafile&unlock = areaUnlocks[key];
+ if(mapName==unlock["Unlock Name"].GetString())SteamUserStats()->SetAchievement(unlock["API Name"].GetString().c_str());
+ }
+ )
unlocks.insert(mapName);
}
bool Unlock::IsUnlocked(std::string mapName){
@@ -60,4 +77,21 @@ bool Unlock::IsUnlocked(ConnectionPoint&cp){
void Unlock::UnlockCurrentMap(){
UnlockArea(State_OverworldMap::GetCurrentConnectionPoint().map);
+}
+
+void Unlock::IncreaseKillCount(){
+ monsterKillCount++;
+ STEAMUSERSTATS(
+ SteamUserStats()->SetStat("Achievement.Kill Unlocks.Total Kill API Name"_S.c_str(),Unlock::monsterKillCount);
+ datafile&killUnlocks=DATA.GetProperty("Achievement.Kill Unlocks");
+ for(auto&[key,size]:killUnlocks){
+ if(key.starts_with("Kill Monsters")){
+ int killRequirement=killUnlocks[key]["Monster Kill Count"].GetInt();
+ if(monsterKillCount-1StoreStats();
+ }
+ }
+ }
+ )
}
\ No newline at end of file
diff --git a/Adventures in Lestoria/Unlock.h b/Adventures in Lestoria/Unlock.h
index ced8fafb..0f4f8fee 100644
--- a/Adventures in Lestoria/Unlock.h
+++ b/Adventures in Lestoria/Unlock.h
@@ -43,8 +43,10 @@ All rights reserved.
class Unlock{
friend class AiL;
friend class SaveFile;
+ friend class SteamStatsReceivedHandler;
static std::setunlocks;
static void Initialize();
+ static int monsterKillCount;
public:
//Provide a map's actual name to trigger unlocks for all connected areas. You can get the current map you are on via State_OverworldMap::GetCurrentConnectionPoint().map
static void UnlockArea(std::string mapName);
@@ -52,4 +54,5 @@ public:
static void UnlockCurrentMap();
static bool IsUnlocked(std::string mapName);
static bool IsUnlocked(ConnectionPoint&cp);
+ static void IncreaseKillCount();
};
\ No newline at end of file
diff --git a/Adventures in Lestoria/Version.h b/Adventures in Lestoria/Version.h
index eeaeb521..745b8d2d 100644
--- a/Adventures in Lestoria/Version.h
+++ b/Adventures in Lestoria/Version.h
@@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1
#define VERSION_MINOR 0
#define VERSION_PATCH 0
-#define VERSION_BUILD 8477
+#define VERSION_BUILD 8598
#define stringify(a) stringify_(a)
#define stringify_(a) #a
diff --git a/Adventures in Lestoria/assets/Achievements/BronzeBorder.xcf b/Adventures in Lestoria/assets/Achievements/BronzeBorder.xcf
new file mode 100644
index 00000000..9a79ef04
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/BronzeBorder.xcf differ
diff --git a/Adventures in Lestoria/assets/Achievements/GoldBorder.xcf b/Adventures in Lestoria/assets/Achievements/GoldBorder.xcf
new file mode 100644
index 00000000..6a318620
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/GoldBorder.xcf differ
diff --git a/Adventures in Lestoria/assets/Achievements/SilverBorder.xcf b/Adventures in Lestoria/assets/Achievements/SilverBorder.xcf
new file mode 100644
index 00000000..2bc187b5
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/SilverBorder.xcf differ
diff --git a/Adventures in Lestoria/assets/Achievements/armor_10.png b/Adventures in Lestoria/assets/Achievements/armor_10.png
new file mode 100644
index 00000000..198e35f6
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/armor_10.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/armor_10_locked.png b/Adventures in Lestoria/assets/Achievements/armor_10_locked.png
new file mode 100644
index 00000000..4c6c7554
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/armor_10_locked.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/armor_5.png b/Adventures in Lestoria/assets/Achievements/armor_5.png
new file mode 100644
index 00000000..d38de0a7
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/armor_5.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/armor_5_locked.png b/Adventures in Lestoria/assets/Achievements/armor_5_locked.png
new file mode 100644
index 00000000..dd045478
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/armor_5_locked.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/blacksmith_unlock.png b/Adventures in Lestoria/assets/Achievements/blacksmith_unlock.png
new file mode 100644
index 00000000..d7c02891
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/blacksmith_unlock.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/blacksmith_unlock_locked.png b/Adventures in Lestoria/assets/Achievements/blacksmith_unlock_locked.png
new file mode 100644
index 00000000..6a28f3cc
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/blacksmith_unlock_locked.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/camp_unlock.png b/Adventures in Lestoria/assets/Achievements/camp_unlock.png
new file mode 100644
index 00000000..37621091
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/camp_unlock.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/camp_unlock_locked.png b/Adventures in Lestoria/assets/Achievements/camp_unlock_locked.png
new file mode 100644
index 00000000..af5a5f77
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/camp_unlock_locked.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/chapter1_complete.png b/Adventures in Lestoria/assets/Achievements/chapter1_complete.png
new file mode 100644
index 00000000..c8a5a5d9
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/chapter1_complete.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/chapter1_complete_locked.png b/Adventures in Lestoria/assets/Achievements/chapter1_complete_locked.png
new file mode 100644
index 00000000..8d0fa7fb
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/chapter1_complete_locked.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/fully_decked_out.png b/Adventures in Lestoria/assets/Achievements/fully_decked_out.png
new file mode 100644
index 00000000..d4ffdb78
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/fully_decked_out.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/fully_decked_out_locked.png b/Adventures in Lestoria/assets/Achievements/fully_decked_out_locked.png
new file mode 100644
index 00000000..d5c1f33b
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/fully_decked_out_locked.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/kill_slime_1.png b/Adventures in Lestoria/assets/Achievements/kill_slime_1.png
new file mode 100644
index 00000000..21d1de3d
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/kill_slime_1.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/kill_slime_1_locked.png b/Adventures in Lestoria/assets/Achievements/kill_slime_1_locked.png
new file mode 100644
index 00000000..05c0366c
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/kill_slime_1_locked.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/kill_slime_2.png b/Adventures in Lestoria/assets/Achievements/kill_slime_2.png
new file mode 100644
index 00000000..b1fdc373
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/kill_slime_2.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/kill_slime_2_locked.png b/Adventures in Lestoria/assets/Achievements/kill_slime_2_locked.png
new file mode 100644
index 00000000..e75156b5
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/kill_slime_2_locked.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/kill_slime_3.png b/Adventures in Lestoria/assets/Achievements/kill_slime_3.png
new file mode 100644
index 00000000..2a4d68b7
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/kill_slime_3.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/kill_slime_3_locked.png b/Adventures in Lestoria/assets/Achievements/kill_slime_3_locked.png
new file mode 100644
index 00000000..806513c5
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/kill_slime_3_locked.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/ranger_level5.png b/Adventures in Lestoria/assets/Achievements/ranger_level5.png
new file mode 100644
index 00000000..dc632a51
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/ranger_level5.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/ranger_level5_locked.png b/Adventures in Lestoria/assets/Achievements/ranger_level5_locked.png
new file mode 100644
index 00000000..bc23197b
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/ranger_level5_locked.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/slime_king.png b/Adventures in Lestoria/assets/Achievements/slime_king.png
new file mode 100644
index 00000000..3f8a277b
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/slime_king.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/slime_king_destroyer.png b/Adventures in Lestoria/assets/Achievements/slime_king_destroyer.png
new file mode 100644
index 00000000..b9d1ac43
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/slime_king_destroyer.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/slime_king_destroyer_locked.png b/Adventures in Lestoria/assets/Achievements/slime_king_destroyer_locked.png
new file mode 100644
index 00000000..613e9763
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/slime_king_destroyer_locked.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/slime_king_locked.png b/Adventures in Lestoria/assets/Achievements/slime_king_locked.png
new file mode 100644
index 00000000..a9e24e13
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/slime_king_locked.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/ursule_destroyer_unlock.png b/Adventures in Lestoria/assets/Achievements/ursule_destroyer_unlock.png
new file mode 100644
index 00000000..9e6ad733
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/ursule_destroyer_unlock.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/ursule_destroyer_unlock_locked.png b/Adventures in Lestoria/assets/Achievements/ursule_destroyer_unlock_locked.png
new file mode 100644
index 00000000..7fce6f7f
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/ursule_destroyer_unlock_locked.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/ursule_unlock.png b/Adventures in Lestoria/assets/Achievements/ursule_unlock.png
new file mode 100644
index 00000000..2a3c863e
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/ursule_unlock.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/ursule_unlock_locked.png b/Adventures in Lestoria/assets/Achievements/ursule_unlock_locked.png
new file mode 100644
index 00000000..00a95bd5
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/ursule_unlock_locked.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/warrior_level5.png b/Adventures in Lestoria/assets/Achievements/warrior_level5.png
new file mode 100644
index 00000000..c808f4da
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/warrior_level5.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/warrior_level5_locked.png b/Adventures in Lestoria/assets/Achievements/warrior_level5_locked.png
new file mode 100644
index 00000000..46f55fe6
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/warrior_level5_locked.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/weapon_5.png b/Adventures in Lestoria/assets/Achievements/weapon_5.png
new file mode 100644
index 00000000..981decdd
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/weapon_5.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/weapon_5_locked.png b/Adventures in Lestoria/assets/Achievements/weapon_5_locked.png
new file mode 100644
index 00000000..ae832ab3
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/weapon_5_locked.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/wizard_level5.png b/Adventures in Lestoria/assets/Achievements/wizard_level5.png
new file mode 100644
index 00000000..19cbe917
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/wizard_level5.png differ
diff --git a/Adventures in Lestoria/assets/Achievements/wizard_level5_locked.png b/Adventures in Lestoria/assets/Achievements/wizard_level5_locked.png
new file mode 100644
index 00000000..a8ec4ff3
Binary files /dev/null and b/Adventures in Lestoria/assets/Achievements/wizard_level5_locked.png differ
diff --git a/Adventures in Lestoria/assets/AdventuresInLestoria_DemoAvailable.png b/Adventures in Lestoria/assets/AdventuresInLestoria_DemoAvailable.png
new file mode 100644
index 00000000..511d6f49
Binary files /dev/null and b/Adventures in Lestoria/assets/AdventuresInLestoria_DemoAvailable.png differ
diff --git a/Adventures in Lestoria/assets/Campaigns/1_1_v2.tmx b/Adventures in Lestoria/assets/Campaigns/1_1_v2.tmx
index 6ba387e0..b77c725f 100644
--- a/Adventures in Lestoria/assets/Campaigns/1_1_v2.tmx
+++ b/Adventures in Lestoria/assets/Campaigns/1_1_v2.tmx
@@ -1,8 +1,11 @@
-