Implemented Poisonous Arrow enchant. Include unit tests. Release Build 10813.

mac-build
sigonasr2 4 months ago
parent 8ac625660d
commit 3d1d14ea2d
  1. 15
      Adventures in Lestoria Tests/EnchantTests.cpp
  2. 7
      Adventures in Lestoria/AdventuresInLestoria.cpp
  3. 8
      Adventures in Lestoria/AdventuresInLestoria.h
  4. 14
      Adventures in Lestoria/Arrow.cpp
  5. 2
      Adventures in Lestoria/BulletTypes.h
  6. 29
      Adventures in Lestoria/Player.cpp
  7. 6
      Adventures in Lestoria/Player.h
  8. 6
      Adventures in Lestoria/Ranger.cpp
  9. 2
      Adventures in Lestoria/Version.h
  10. 1
      Adventures in Lestoria/assets/config/gfx/gfx.txt
  11. BIN
      Adventures in Lestoria/assets/gamepack.pak
  12. BIN
      Adventures in Lestoria/assets/glow.png
  13. BIN
      x64/Release/Adventures in Lestoria.exe

@ -538,7 +538,20 @@ namespace EnchantTests
nullRing.lock()->EnchantItem("Stealthy Retreat"); nullRing.lock()->EnchantItem("Stealthy Retreat");
player->CheckAndPerformAbility(player->GetRightClickAbility(),testKeyboardInput); player->CheckAndPerformAbility(player->GetRightClickAbility(),testKeyboardInput);
Assert::AreEqual("Ranger.Right Click Ability.RetreatTime"_F+"Stealthy Retreat"_ENC["INVULNERABILITY INCREASE"],player->GetIframeTime(),L"Ranger's retreat iframe time is much greater."); Assert::AreEqual("Ranger.Right Click Ability.RetreatTime"_F+"Stealthy Retreat"_ENC["INVULNERABILITY INCREASE"],player->GetIframeTime(),L"Ranger's retreat iframe time is much greater.");
}
TEST_METHOD(PoisonousArrowCheck){
game->ChangePlayerClass(RANGER);
player=game->GetPlayer();
Assert::AreEqual(false,player->PoisonArrowAutoAttackReady(),L"Poison arrow auto attack should not be ready immediately.");
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
Inventory::EquipItem(nullRing,EquipSlot::RING1);
nullRing.lock()->EnchantItem("Poisonous Arrow");
Assert::AreEqual(true,player->PoisonArrowAutoAttackReady(),L"Poison arrow auto attack should now be ready.");
player->AutoAttack();
Assert::AreEqual(false,player->PoisonArrowAutoAttackReady(),L"Poison arrow auto attack should now be on cooldown.");
game->SetElapsedTime("Poisonous Arrow"_ENC["POISON ARROW RESET FREQUENCY"]);
game->OnUserUpdate("Poisonous Arrow"_ENC["POISON ARROW RESET FREQUENCY"]);
Assert::AreEqual(true,player->PoisonArrowAutoAttackReady(),L"Poison arrow auto attack should be ready again.");
} }
}; };
} }

@ -1127,6 +1127,13 @@ void AiL::RenderWorld(float fElapsedTime){
if(player->GetState()==State::BLOCK){ if(player->GetState()==State::BLOCK){
view.DrawDecal(player->GetPos()+vf2d{0,-player->GetZ()*(std::signbit(scale.y)?-1:1)}-vf2d{12,12},GFX["block.png"].Decal()); view.DrawDecal(player->GetPos()+vf2d{0,-player->GetZ()*(std::signbit(scale.y)?-1:1)}-vf2d{12,12},GFX["block.png"].Decal());
} }
if(player->PoisonArrowAutoAttackReady()&&player->poisonArrowLastParticleTimer==0.f){
const float particleSize{fmod(game->GetRunTime(),0.4f)<0.2f?1.f:1.5f};
game->AddEffect(std::make_unique<Effect>(player->GetPos()-vf2d{0,4.f}-player->GetFacingDirVector()*6.f,0.2f,"energy_particle.png",player->OnUpperLevel(),vf2d{particleSize,particleSize}*1.2f,0.05f,vf2d{},Pixel{0,255,255,120},util::random(2*PI),0.f,true));
game->AddEffect(std::make_unique<Effect>(player->GetPos()-vf2d{0,4.f}-player->GetFacingDirVector()*6.f,0.2f,"energy_particle.png",player->OnUpperLevel(),vf2d{particleSize,particleSize},0.05f,vf2d{},DARK_GREEN,util::random(2*PI)));
player->poisonArrowLastParticleTimer=0.15f;
}
}; };
auto RenderZone=[&](geom2d::rect<int>&zone){ auto RenderZone=[&](geom2d::rect<int>&zone){

@ -63,9 +63,11 @@ All rights reserved.
class SteamKeyboardCallbackHandler; class SteamKeyboardCallbackHandler;
class SteamStatsReceivedHandler; class SteamStatsReceivedHandler;
#define CreateBullet(type) INCLUDE_BULLET_LIST \ INCLUDE_BULLET_LIST
BULLET_LIST.push_back(std::make_unique<type>(type
#define EndBullet )); #define CreateBullet(type) \
*(type*const)(BULLET_LIST.emplace_back(std::make_unique<type>(type
#define EndBullet )).get())
using HurtReturnValue=bool; using HurtReturnValue=bool;
using HurtList=std::vector<std::pair<std::variant<Monster*,Player*>,HurtReturnValue>>; using HurtList=std::vector<std::pair<std::variant<Monster*,Player*>,HurtReturnValue>>;

@ -43,6 +43,7 @@ All rights reserved.
#include "olcUTIL_Geometry2D.h" #include "olcUTIL_Geometry2D.h"
INCLUDE_game INCLUDE_game
INCLUDE_GFX
Arrow::Arrow(vf2d pos,vf2d targetPos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col) Arrow::Arrow(vf2d pos,vf2d targetPos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col)
:finalDistance(geom2d::line(pos,targetPos).length()*1.2f),acc(PI/2*250),targetPos(targetPos), :finalDistance(geom2d::line(pos,targetPos).length()*1.2f),acc(PI/2*250),targetPos(targetPos),
@ -94,6 +95,7 @@ BulletDestroyState Arrow::PlayerHit(Player*player)
{ {
fadeOutTime=0.2f; fadeOutTime=0.2f;
game->AddEffect(std::make_unique<Effect>(player->GetPos(),0,"splash_effect.png",upperLevel,player->GetSizeMult(),0.25)); game->AddEffect(std::make_unique<Effect>(player->GetPos(),0,"splash_effect.png",upperLevel,player->GetSizeMult(),0.25));
if(poisonArrow)player->AddBuff(BuffRestorationType::OVER_TIME,BuffOverTimeType::HP_DAMAGE_OVER_TIME,"Poisonous Arrow"_ENC["POISON DURATION"],damage*("Poisonous Arrow"_ENC["POISON TICK DAMAGE"]/100.f),1.f);
return BulletDestroyState::KEEP_ALIVE; return BulletDestroyState::KEEP_ALIVE;
} }
@ -101,9 +103,21 @@ BulletDestroyState Arrow::MonsterHit(Monster&monster,const uint8_t markStacksBef
{ {
fadeOutTime=0.2f; fadeOutTime=0.2f;
game->AddEffect(std::make_unique<Effect>(monster.GetPos(),0,"splash_effect.png",upperLevel,monster.GetSizeMult(),0.25)); game->AddEffect(std::make_unique<Effect>(monster.GetPos(),0,"splash_effect.png",upperLevel,monster.GetSizeMult(),0.25));
if(poisonArrow)monster.AddBuff(BuffRestorationType::OVER_TIME,BuffOverTimeType::HP_DAMAGE_OVER_TIME,"Poisonous Arrow"_ENC["POISON DURATION"],damage*("Poisonous Arrow"_ENC["POISON TICK DAMAGE"]/100.f),1.f);
return BulletDestroyState::KEEP_ALIVE; return BulletDestroyState::KEEP_ALIVE;
} }
void Arrow::ModifyOutgoingDamageData(HurtDamageInfo&data){ void Arrow::ModifyOutgoingDamageData(HurtDamageInfo&data){
if(friendly)data.hurtFlags|=HurtFlag::PLAYER_ABILITY; if(friendly)data.hurtFlags|=HurtFlag::PLAYER_ABILITY;
} }
void Arrow::Draw(const Pixel blendCol)const{
if(poisonArrow){
game->SetDecalMode(DecalMode::ADDITIVE);
game->view.DrawRotatedDecal(pos,GFX["glow.png"].Decal(),image_angle,GFX["glow.png"].Sprite()->Size()/2,scale,{255,255,0,120});
game->SetDecalMode(DecalMode::NORMAL);
Bullet::Draw({128,192,0,blendCol.a});
}else{
Bullet::Draw(blendCol);
}
}

@ -72,6 +72,7 @@ struct Arrow:public Bullet{
float finalDistance=0; float finalDistance=0;
float acc=PI/2*250; float acc=PI/2*250;
vf2d targetPos; vf2d targetPos;
bool poisonArrow{false};
Arrow(vf2d pos,vf2d targetPos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE); Arrow(vf2d pos,vf2d targetPos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE);
Arrow(vf2d pos,vf2d targetPos,vf2d vel,const std::string_view gfx,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE); Arrow(vf2d pos,vf2d targetPos,vf2d vel,const std::string_view gfx,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE);
void Update(float fElapsedTime)override; void Update(float fElapsedTime)override;
@ -81,6 +82,7 @@ struct Arrow:public Bullet{
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data); void ModifyOutgoingDamageData(HurtDamageInfo&data);
void Draw(const Pixel blendCol)const override;
}; };
struct ChargedArrow:public Bullet{ struct ChargedArrow:public Bullet{

@ -348,6 +348,8 @@ void Player::Update(float fElapsedTime){
lastDotTimer=std::max(0.f,lastDotTimer-fElapsedTime); lastDotTimer=std::max(0.f,lastDotTimer-fElapsedTime);
lastPathfindingCooldown=std::max(0.f,lastPathfindingCooldown-fElapsedTime); lastPathfindingCooldown=std::max(0.f,lastPathfindingCooldown-fElapsedTime);
lowHealthSoundPlayedTimer=std::max(0.f,lowHealthSoundPlayedTimer-fElapsedTime); lowHealthSoundPlayedTimer=std::max(0.f,lowHealthSoundPlayedTimer-fElapsedTime);
poisonArrowReadyTimer=std::max(0.f,poisonArrowReadyTimer-fElapsedTime);
poisonArrowLastParticleTimer=std::max(0.f,poisonArrowLastParticleTimer-fElapsedTime);
if(hurtRumbleTime>0.f){ if(hurtRumbleTime>0.f){
hurtRumbleTime=std::max(0.f,hurtRumbleTime-fElapsedTime); hurtRumbleTime=std::max(0.f,hurtRumbleTime-fElapsedTime);
if(hurtRumbleTime==0.f){ if(hurtRumbleTime==0.f){
@ -983,7 +985,7 @@ void Player::SetFacingDirection(Key direction){
facingDirection=direction; facingDirection=direction;
} }
Key Player::GetFacingDirection(){ Key Player::GetFacingDirection()const{
return facingDirection; return facingDirection;
} }
@ -1959,3 +1961,28 @@ void Player::ReduceAutoAttackTimer(const float reduceAmt){
const float&Player::GetAutoAttackTimer()const{ const float&Player::GetAutoAttackTimer()const{
return attack_cooldown_timer; return attack_cooldown_timer;
} }
const bool Player::PoisonArrowAutoAttackReady()const{
return HasEnchant("Poisonous Arrow")&&poisonArrowReadyTimer==0.f;
}
const vf2d Player::GetFacingDirVector()const{
switch(GetFacingDirection()){
case UP:{
return {0,-1};
}break;
case DOWN:{
return {0,1};
}break;
case LEFT:{
return {-1,0};
}break;
case RIGHT:{
return {1,0};
}break;
default:{
ERR(std::format("WARNING! Somehow got an invalid facing direction: {}. THIS SHOULD NOT BE HAPPENING!",int(GetFacingDirection())));
return {};
}
}
}

@ -144,7 +144,7 @@ public:
float GetAttackRangeMult(); float GetAttackRangeMult();
float GetSpinAngle(); float GetSpinAngle();
State::State GetState(); State::State GetState();
Key GetFacingDirection(); Key GetFacingDirection()const;
vf2d GetVelocity(); vf2d GetVelocity();
bool HasIframes(); bool HasIframes();
void Update(float fElapsedTime); void Update(float fElapsedTime);
@ -305,6 +305,8 @@ public:
void CheckAndPerformAbility(Ability&ability,InputGroup key); void CheckAndPerformAbility(Ability&ability,InputGroup key);
void ReduceAutoAttackTimer(const float reduceAmt); void ReduceAutoAttackTimer(const float reduceAmt);
const float&GetAutoAttackTimer()const; const float&GetAutoAttackTimer()const;
const vf2d GetFacingDirVector()const; //Returns a normalized vector based on the facing direction of the character. Ex. {0,-1} for north and {1,0} for east.
const bool PoisonArrowAutoAttackReady()const; //NOTE: Also checks to make sure we have the enchant built-in...
private: private:
int hp="Warrior.BaseHealth"_I; int hp="Warrior.BaseHealth"_I;
int mana="Player.BaseMana"_I; int mana="Player.BaseMana"_I;
@ -384,6 +386,7 @@ private:
std::unordered_set<std::string>enchantList; std::unordered_set<std::string>enchantList;
void OnAbilityUse(const Ability&ability); //Callback when an ability successfully is used and has gone on cooldown. void OnAbilityUse(const Ability&ability); //Callback when an ability successfully is used and has gone on cooldown.
const bool LastReserveEnchantConditionsMet()const; const bool LastReserveEnchantConditionsMet()const;
float poisonArrowLastParticleTimer{};
protected: protected:
const float ATTACK_COOLDOWN="Warrior.Auto Attack.Cooldown"_F; const float ATTACK_COOLDOWN="Warrior.Auto Attack.Cooldown"_F;
const float MAGIC_ATTACK_COOLDOWN="Wizard.Auto Attack.Cooldown"_F; const float MAGIC_ATTACK_COOLDOWN="Wizard.Auto Attack.Cooldown"_F;
@ -436,6 +439,7 @@ protected:
float leapTimer{}; float leapTimer{};
float totalLeapTime{}; float totalLeapTime{};
vf2d leapStartingPos{}; vf2d leapStartingPos{};
float poisonArrowReadyTimer{};
}; };
#pragma region Warrior #pragma region Warrior

@ -71,7 +71,11 @@ bool Ranger::AutoAttack(){
vf2d extendedLine=pointTowardsCursor.upoint(1.1f); vf2d extendedLine=pointTowardsCursor.upoint(1.1f);
float angleToCursor=atan2(extendedLine.y-GetPos().y,extendedLine.x-GetPos().x); float angleToCursor=atan2(extendedLine.y-GetPos().y,extendedLine.x-GetPos().x);
attack_cooldown_timer=ARROW_ATTACK_COOLDOWN-GetAttackRecoveryRateReduction(); attack_cooldown_timer=ARROW_ATTACK_COOLDOWN-GetAttackRecoveryRateReduction();
CreateBullet(Arrow)(GetPos(),extendedLine,vf2d{cos(angleToCursor)*"Ranger.Auto Attack.ArrowSpd"_F,float(sin(angleToCursor)*"Ranger.Auto Attack.ArrowSpd"_F-PI/8*"Ranger.Auto Attack.ArrowSpd"_F)}+movementVelocity/1.5f,"Ranger.Auto Attack.Radius"_F,int(GetAttack()*"Ranger.Auto Attack.DamageMult"_F),OnUpperLevel(),true)EndBullet; Arrow&arrow{CreateBullet(Arrow)(GetPos(),extendedLine,vf2d{cos(angleToCursor)*"Ranger.Auto Attack.ArrowSpd"_F,float(sin(angleToCursor)*"Ranger.Auto Attack.ArrowSpd"_F-PI/8*"Ranger.Auto Attack.ArrowSpd"_F)}+movementVelocity/1.5f,"Ranger.Auto Attack.Radius"_F,int(GetAttack()*"Ranger.Auto Attack.DamageMult"_F),OnUpperLevel(),true)EndBullet};
if(PoisonArrowAutoAttackReady()){
arrow.poisonArrow=true;
poisonArrowReadyTimer="Poisonous Arrow"_ENC["POISON ARROW RESET FREQUENCY"];
}
BULLET_LIST.back()->SetIsPlayerAutoAttackProjectile(); BULLET_LIST.back()->SetIsPlayerAutoAttackProjectile();
SetState(State::SHOOT_ARROW); SetState(State::SHOOT_ARROW);
SetAnimationBasedOnTargetingDirection("SHOOT",angleToCursor); SetAnimationBasedOnTargetingDirection("SHOOT",angleToCursor);

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

@ -121,6 +121,7 @@ Images
GFX_Fragment = items/Fragment.png GFX_Fragment = items/Fragment.png
GFX_MonsterSoul = monstersoul.png GFX_MonsterSoul = monstersoul.png
GFX_MonsterSoulGlow = monstersoulglow.png GFX_MonsterSoulGlow = monstersoulglow.png
GFX_Glow = glow.png
GFX_Thief_Sheet = nico-thief.png GFX_Thief_Sheet = nico-thief.png
GFX_Trapper_Sheet = nico-trapper.png GFX_Trapper_Sheet = nico-trapper.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1017 B

Loading…
Cancel
Save