Refactor ability use skills to be testable. Add in basic player damage check and ability use unit tests.

mac-build
sigonasr2 5 months ago
parent 6cb3cb4e6d
commit 6274dcefb1
  1. 71
      Adventures in Lestoria Tests/PlayerTests.cpp
  2. 5
      Adventures in Lestoria/AdventuresInLestoria.h
  3. 2
      Adventures in Lestoria/Effect.cpp
  4. 5
      Adventures in Lestoria/Key.h
  5. 82
      Adventures in Lestoria/Player.cpp
  6. 8
      Adventures in Lestoria/Player.h
  7. 1
      Adventures in Lestoria/Tutorial.h
  8. 2
      Adventures in Lestoria/Version.h
  9. 6
      Adventures in Lestoria/olcPGEX_TTF.h
  10. 14
      Adventures in Lestoria/olcPixelGameEngine.h

@ -37,6 +37,7 @@ All rights reserved.
#pragma endregion
#include "CppUnitTest.h"
#include "AdventuresInLestoria.h"
#include "Tutorial.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
@ -46,6 +47,76 @@ namespace PlayerTests
TEST_CLASS(PlayerTest)
{
public:
std::unique_ptr<AiL>testGame;
InputGroup testKeyboardInput;
Player*player;
HWButton*testKey;
TEST_METHOD_INITIALIZE(PlayerInitialize){
testGame.reset(new AiL());
testGame->EnableTestingMode();
testGame->InitializeClasses();
testGame->InitializeDefaultKeybinds();
Tutorial::Initialize();
ItemAttribute::Initialize();
testGame->InitializePlayer();
Stats::InitializeDamageReductionTable();
player=testGame->GetPlayer();
//Setup key "0" as a test input
testKeyboardInput.AddKeybind(Input{InputType::KEY,0});
testKey=&testGame->pKeyboardState[0];
testGame->olc_UpdateKeyFocus(true); //Force the game to be "focused" for tests. Required if we want keyboard inputs to work.
}
TEST_METHOD(PlayerAlive){
Assert::IsTrue(player->IsAlive());
}
TEST_METHOD(PlayerTakesDamage){
player->Hurt(30,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(70,player->GetHealth());
}
TEST_METHOD(PlayerBlockingTakesNoDamage){
player->SetState(State::BLOCK);
player->Hurt(30,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(100,player->GetHealth());
}
TEST_METHOD(PlayerIframesPreventsDamage){
player->ApplyIframes(0.5f);
player->Hurt(30,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(100,player->GetHealth());
}
TEST_METHOD(PlayerConsumesManaOnRightClickAbilityUse){
int abilityManaCost{player->GetRightClickAbility().manaCost};
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetRightClickAbility(),testKeyboardInput);
Assert::AreEqual(player->GetMaxMana()-abilityManaCost,player->GetMana());
}
TEST_METHOD(PlayerConsumesManaOnAbility1Use){
int abilityManaCost{player->GetAbility1().manaCost};
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetAbility1(),testKeyboardInput);
Assert::AreEqual(player->GetMaxMana()-abilityManaCost,player->GetMana());
}
TEST_METHOD(PlayerConsumesManaOnAbility2Use){
int abilityManaCost{player->GetAbility2().manaCost};
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetAbility2(),testKeyboardInput);
Assert::AreEqual(player->GetMaxMana()-abilityManaCost,player->GetMana());
}
TEST_METHOD(PlayerConsumesManaOnAbility3Use){
int abilityManaCost{player->GetAbility3().manaCost};
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
Assert::AreEqual(player->GetMaxMana()-abilityManaCost,player->GetMana());
}
TEST_METHOD(PlayerConsumesManaOnAbility4Use){
int abilityManaCost{player->GetAbility4().manaCost};
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetAbility4(),testKeyboardInput);
Assert::AreEqual(player->GetMaxMana()-abilityManaCost,player->GetMana());
}
TEST_METHOD(WarriorBlockActivatesBlock){
player->GetRightClickAbility().action(player,player->GetPos());
Assert::AreEqual(int(State::BLOCK),int(player->GetState()));
}
};
}

@ -75,6 +75,10 @@ enum class HurtType{
MONSTER=0b10,
};
namespace PlayerTests{
class PlayerTest;
}
class AiL : public olc::PixelGameEngine
{
friend class GameState;
@ -83,6 +87,7 @@ class AiL : public olc::PixelGameEngine
friend class sig::Animation;
friend class Audio;
friend class Minimap;
friend class PlayerTests::PlayerTest;
std::unique_ptr<Player>player;
SplashScreen splash;
public:

@ -45,11 +45,13 @@ INCLUDE_game
Effect::Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,float size,float fadeout,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:Effect::Effect(pos,lifetime,imgFile,upperLevel,vf2d{size,size},fadeout,spd,col,rotation,rotationSpd,additiveBlending){
if(game->TestingModeEnabled())return;
this->animation.AddState(imgFile,ANIMATION_DATA[imgFile]);
}
Effect::Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,vf2d size,float fadeout,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:pos(pos),lifetime(lifetime),upperLevel(upperLevel),size(size),fadeout(fadeout),original_fadeoutTime(fadeout),spd(spd),col(col),rotation(rotation),rotationSpd(rotationSpd),additiveBlending(additiveBlending){
if(game->TestingModeEnabled())return;
this->animation.AddState(imgFile,ANIMATION_DATA[imgFile]);
}

@ -149,6 +149,10 @@ public:
static uint64_t ingameControlsHandle;
};
namespace PlayerTests{
class PlayerTest;
}
class InputGroup{
friend class AiL;
friend class Menu;
@ -156,6 +160,7 @@ class InputGroup{
friend class InputListener;
friend class SaveFile;
friend class State_MainMenu;
friend class PlayerTests::PlayerTest;
std::set<Input>keys;
std::vector<Input>keyOrder; //A list of keys inserted in order, for primary key mapping.
static safemap<std::string,InputGroup*>menuNamesToInputGroups;

@ -586,46 +586,6 @@ void Player::Update(float fElapsedTime){
AutoAttack();
}
auto AllowedToCast=[&](Ability&ability){return !ability.precastInfo.precastTargetingRequired&&GetState()!=State::ANIMATION_LOCK;};
auto HasEnoughOfItem=[&](Ability&ability){
if(!ability.itemAbility)return true;
if(&ability==&item1&&game->GetLoadoutItem(0).lock()->Amt()>0)return true;
if(&ability==&item2&&game->GetLoadoutItem(1).lock()->Amt()>0)return true;
if(&ability==&item3&&game->GetLoadoutItem(2).lock()->Amt()>0)return true;
return false;
};
//If pressed is set to false, uses held instead.
auto CheckAndPerformAbility=[&](Ability&ability,InputGroup key){
if(ability.name!="???"){
if(CanAct(ability)){
if(ability.cooldown==0&&GetMana()>=ability.manaCost){
if(key.Held()||key.Released()&&&ability==castPrepAbility&&GetState()==State::PREP_CAST){
#pragma region Tutorial Defensive/Use Ability Tasks
if(&ability==&rightClickAbility){
Tutorial::GetTask(TutorialTaskName::USE_DEFENSIVE).I(A::DEFENSIVE_COUNT)++;
}else{
Tutorial::GetTask(TutorialTaskName::USE_ABILITIES).I(A::ABILITY_COUNT)++;
}
#pragma endregion
if(AllowedToCast(ability)&&ability.action(this,{})){
bool allowed=ability.actionPerformedDuringCast;
ability.cooldown=ability.GetCooldownTime();
CancelCast();
ConsumeMana(ability.manaCost);
}else
if(ability.precastInfo.precastTargetingRequired&&GetState()==State::NORMAL
&&HasEnoughOfItem(ability)){
PrepareCast(ability);
}
}
} else
if(ability.cooldown==0&&GetMana()<ability.manaCost&&key.Pressed()){
notEnoughManaDisplay={ability.name,1.f};
}
}else
if(key.Released()||!key.Held())ability.waitForRelease=false;
}
};
CheckAndPerformAbility(rightClickAbility,Player::KEY_DEFENSIVE);
CheckAndPerformAbility(ability,Player::KEY_ABILITY1);
CheckAndPerformAbility(ability2,Player::KEY_ABILITY2);
@ -766,7 +726,7 @@ bool Player::CanAct(){
return CanAct(dummyAbility);
}
bool Player::CanAct(Ability&ability){
const bool Player::CanAct(const Ability&ability){
return knockUpTimer==0&&!ability.waitForRelease&&(ability.canCancelCast||state!=State::CASTING)&&state!=State::ANIMATION_LOCK&&
(GameState::STATE==GameState::states[States::GAME_RUN]
||GameState::STATE==GameState::states[States::GAME_HUB]&&!ability.itemAbility);
@ -823,7 +783,7 @@ bool Player::Hurt(int damage,bool onUpperLevel,float z){
damageNumberPtr.get()->damage+=int(mod_dmg);
damageNumberPtr.get()->pauseTime=0.4f;
damageNumberPtr.get()->RecalculateSize();
} else {
}else{
damageNumberPtr=std::make_shared<DamageNumber>(pos,int(mod_dmg),true);
DAMAGENUMBER_LIST.push_back(damageNumberPtr);
}
@ -851,6 +811,7 @@ void Player::AddAnimation(std::string state){
}
void Player::UpdateAnimation(std::string animState,int specificClass, const float frameMult){
if(game->TestingModeEnabled())return;
if(specificClass==ANY||specificClass&GetClass()){
animation.ChangeState(internal_animState,animState,frameMult);
}
@ -1601,3 +1562,40 @@ const float Player::GetHealthRatio()const{
const bool Player::IsAlive()const{
return GetHealth()>0;
}
void Player::CheckAndPerformAbility(Ability&ability,InputGroup key){
const bool AllowedToCast=!ability.precastInfo.precastTargetingRequired&&GetState()!=State::ANIMATION_LOCK;
const bool HasEnoughOfItem=!ability.itemAbility||
&ability==&useItem1&&game->GetLoadoutItem(0).lock()->Amt()>0||
&ability==&useItem2&&game->GetLoadoutItem(1).lock()->Amt()>0||
&ability==&useItem3&&game->GetLoadoutItem(2).lock()->Amt()>0;
if(ability.name!="???"){
if(CanAct(ability)){
if(ability.cooldown==0&&GetMana()>=ability.manaCost){
if(key.Held()||key.Released()&&&ability==castPrepAbility&&GetState()==State::PREP_CAST){
#pragma region Tutorial Defensive/Use Ability Tasks
if(&ability==&GetRightClickAbility()){
Tutorial::GetTask(TutorialTaskName::USE_DEFENSIVE).I(A::DEFENSIVE_COUNT)++;
}else{
Tutorial::GetTask(TutorialTaskName::USE_ABILITIES).I(A::ABILITY_COUNT)++;
}
#pragma endregion
if(AllowedToCast&&ability.action(this,{})){
bool allowed=ability.actionPerformedDuringCast;
ability.cooldown=ability.GetCooldownTime();
CancelCast();
ConsumeMana(ability.manaCost);
}else
if(ability.precastInfo.precastTargetingRequired&&GetState()==State::NORMAL
&&HasEnoughOfItem){
PrepareCast(ability);
}
}
} else
if(ability.cooldown==0&&GetMana()<ability.manaCost&&key.Pressed()){
notEnoughManaDisplay={ability.name,1.f};
}
}else
if(key.Released()||!key.Held())ability.waitForRelease=false;
}
}

@ -69,6 +69,10 @@ namespace MoveFlag{
};
};
namespace PlayerTest{
class PlayerTests;
}
class EntityStats{
friend class Inventory;
ItemAttributable equipStats; //The stats after gear calculations are applied.
@ -98,6 +102,7 @@ class Player{
friend class State_GameRun;
friend class Inventory;
friend void ItemOverlay::Draw();
friend class PlayerTests::PlayerTest;
public:
Player();
//So this is rather fascinating and only exists because we have the ability to change classes which means we need to initialize a class
@ -146,7 +151,7 @@ public:
bool CanPathfindTo(vf2d pos,vf2d targetPos,float range=8);
bool CanMove();
bool CanAct();
bool CanAct(Ability&ability);
const bool CanAct(const Ability&ability);
void Knockback(vf2d vel);
void ProximityKnockback(const vf2d centerPoint,const float knockbackFactor);
void ApplyIframes(float duration);
@ -332,6 +337,7 @@ private:
float lowHealthSoundPlayedTimer=0.f;
float rangerShootAnimationTimer=0.f;
Renderable minimapImg; //An image of the character represented on a minimap. Should be 12x12 and generally be a circle.
void CheckAndPerformAbility(Ability&ability,InputGroup key);
protected:
const float ATTACK_COOLDOWN="Warrior.Auto Attack.Cooldown"_F;
const float MAGIC_ATTACK_COOLDOWN="Wizard.Auto Attack.Cooldown"_F;

@ -109,6 +109,7 @@ public:
inline SetLoadoutItemTask():TutorialTask(){};
private:
virtual inline void Initialize()override final{
if(game->TestingModeEnabled())return;
Component<MenuComponent>(ITEM_LOADOUT,"Start Level Button")->SetGrayedOut(true);
}
virtual inline bool CompleteCondition()override final{

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1
#define VERSION_MINOR 2
#define VERSION_PATCH 3
#define VERSION_BUILD 9798
#define VERSION_BUILD 9814
#define stringify(a) stringify_(a)
#define stringify_(a) #a

@ -45,7 +45,11 @@ All rights reserved.
#include <ft2build.h>
#pragma comment(lib, "freetype.lib")
#else
#include <freetype2/ft2build.h>
#ifdef OLC_PGE_HEADLESS
#include <ft2build.h>
#else
#include <freetype2/ft2build.h>
#endif
#endif
#include <iostream>

@ -572,6 +572,11 @@ namespace X11
#endif
#pragma endregion
namespace PlayerTests{
class PlayerTest;
}
// O------------------------------------------------------------------------------O
// | olcPixelGameEngine INTERFACE DECLARATION |
// O------------------------------------------------------------------------------O
@ -626,7 +631,6 @@ namespace olc
bool bHeld = false; // Set true for all frames between pressed and released events
};
#define OLC_IGNORE_VEC2D
// O------------------------------------------------------------------------------O
@ -937,6 +941,7 @@ namespace olc
class PixelGameEngine
{
friend class ViewPort;
friend class PlayerTests::PlayerTest;
struct StringDecalData{
char c;
vf2d sourcePos;
@ -1468,6 +1473,7 @@ namespace olc
typedef char GLchar;
typedef ptrdiff_t GLsizeiptr;
#ifndef OLC_GFX_HEADLESS
typedef GLuint CALLSTYLE locCreateShader_t(GLenum type);
typedef GLuint CALLSTYLE locCreateProgram_t(void);
typedef void CALLSTYLE locDeleteShader_t(GLuint shader);
@ -1497,7 +1503,7 @@ namespace olc
typedef void CALLSTYLE locFrameBufferTexture2D_t(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
typedef void CALLSTYLE locDrawBuffers_t(GLsizei n, const GLenum* bufs);
typedef void CALLSTYLE locBlendFuncSeparate_t(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha);
#endif
#if defined(OLC_PLATFORM_WINAPI)
typedef void __stdcall locSwapInterval_t(GLsizei n);
#endif
@ -1510,7 +1516,9 @@ namespace olc
typedef void CALLSTYLE locShaderSource_t(GLuint shader, GLsizei count, const GLchar* const* string, const GLint* length);
typedef EGLBoolean(locSwapInterval_t)(EGLDisplay display, EGLint interval);
#else
typedef void CALLSTYLE locShaderSource_t(GLuint shader, GLsizei count, const GLchar** string, const GLint* length);
#ifndef OLC_GFX_HEADLESS
typedef void CALLSTYLE locShaderSource_t(GLuint shader, GLsizei count, const GLchar** string, const GLint* length);
#endif
#endif
} // olc namespace

Loading…
Cancel
Save