Implemented Poisonous Arrow enchant. Include unit tests. Release Build 10813.
This commit is contained in:
parent
8ac625660d
commit
3d1d14ea2d
@ -538,7 +538,20 @@ namespace EnchantTests
|
||||
nullRing.lock()->EnchantItem("Stealthy Retreat");
|
||||
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.");
|
||||
|
||||
}
|
||||
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){
|
||||
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){
|
||||
|
@ -63,9 +63,11 @@ All rights reserved.
|
||||
class SteamKeyboardCallbackHandler;
|
||||
class SteamStatsReceivedHandler;
|
||||
|
||||
#define CreateBullet(type) INCLUDE_BULLET_LIST \
|
||||
BULLET_LIST.push_back(std::make_unique<type>(type
|
||||
#define EndBullet ));
|
||||
INCLUDE_BULLET_LIST
|
||||
|
||||
#define CreateBullet(type) \
|
||||
*(type*const)(BULLET_LIST.emplace_back(std::make_unique<type>(type
|
||||
#define EndBullet )).get())
|
||||
|
||||
using HurtReturnValue=bool;
|
||||
using HurtList=std::vector<std::pair<std::variant<Monster*,Player*>,HurtReturnValue>>;
|
||||
|
@ -43,6 +43,7 @@ All rights reserved.
|
||||
#include "olcUTIL_Geometry2D.h"
|
||||
|
||||
INCLUDE_game
|
||||
INCLUDE_GFX
|
||||
|
||||
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),
|
||||
@ -94,6 +95,7 @@ BulletDestroyState Arrow::PlayerHit(Player*player)
|
||||
{
|
||||
fadeOutTime=0.2f;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -101,9 +103,21 @@ BulletDestroyState Arrow::MonsterHit(Monster&monster,const uint8_t markStacksBef
|
||||
{
|
||||
fadeOutTime=0.2f;
|
||||
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;
|
||||
}
|
||||
|
||||
void Arrow::ModifyOutgoingDamageData(HurtDamageInfo&data){
|
||||
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 acc=PI/2*250;
|
||||
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,const std::string_view gfx,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE);
|
||||
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 MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
|
||||
void ModifyOutgoingDamageData(HurtDamageInfo&data);
|
||||
void Draw(const Pixel blendCol)const override;
|
||||
};
|
||||
|
||||
struct ChargedArrow:public Bullet{
|
||||
|
@ -348,6 +348,8 @@ void Player::Update(float fElapsedTime){
|
||||
lastDotTimer=std::max(0.f,lastDotTimer-fElapsedTime);
|
||||
lastPathfindingCooldown=std::max(0.f,lastPathfindingCooldown-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){
|
||||
hurtRumbleTime=std::max(0.f,hurtRumbleTime-fElapsedTime);
|
||||
if(hurtRumbleTime==0.f){
|
||||
@ -983,7 +985,7 @@ void Player::SetFacingDirection(Key direction){
|
||||
facingDirection=direction;
|
||||
}
|
||||
|
||||
Key Player::GetFacingDirection(){
|
||||
Key Player::GetFacingDirection()const{
|
||||
return facingDirection;
|
||||
}
|
||||
|
||||
@ -1959,3 +1961,28 @@ void Player::ReduceAutoAttackTimer(const float reduceAmt){
|
||||
const float&Player::GetAutoAttackTimer()const{
|
||||
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 GetSpinAngle();
|
||||
State::State GetState();
|
||||
Key GetFacingDirection();
|
||||
Key GetFacingDirection()const;
|
||||
vf2d GetVelocity();
|
||||
bool HasIframes();
|
||||
void Update(float fElapsedTime);
|
||||
@ -305,6 +305,8 @@ public:
|
||||
void CheckAndPerformAbility(Ability&ability,InputGroup key);
|
||||
void ReduceAutoAttackTimer(const float reduceAmt);
|
||||
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:
|
||||
int hp="Warrior.BaseHealth"_I;
|
||||
int mana="Player.BaseMana"_I;
|
||||
@ -384,6 +386,7 @@ private:
|
||||
std::unordered_set<std::string>enchantList;
|
||||
void OnAbilityUse(const Ability&ability); //Callback when an ability successfully is used and has gone on cooldown.
|
||||
const bool LastReserveEnchantConditionsMet()const;
|
||||
float poisonArrowLastParticleTimer{};
|
||||
protected:
|
||||
const float ATTACK_COOLDOWN="Warrior.Auto Attack.Cooldown"_F;
|
||||
const float MAGIC_ATTACK_COOLDOWN="Wizard.Auto Attack.Cooldown"_F;
|
||||
@ -436,6 +439,7 @@ protected:
|
||||
float leapTimer{};
|
||||
float totalLeapTime{};
|
||||
vf2d leapStartingPos{};
|
||||
float poisonArrowReadyTimer{};
|
||||
};
|
||||
|
||||
#pragma region Warrior
|
||||
|
@ -71,7 +71,11 @@ bool Ranger::AutoAttack(){
|
||||
vf2d extendedLine=pointTowardsCursor.upoint(1.1f);
|
||||
float angleToCursor=atan2(extendedLine.y-GetPos().y,extendedLine.x-GetPos().x);
|
||||
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();
|
||||
SetState(State::SHOOT_ARROW);
|
||||
SetAnimationBasedOnTargetingDirection("SHOOT",angleToCursor);
|
||||
|
@ -39,7 +39,7 @@ All rights reserved.
|
||||
#define VERSION_MAJOR 1
|
||||
#define VERSION_MINOR 2
|
||||
#define VERSION_PATCH 3
|
||||
#define VERSION_BUILD 10803
|
||||
#define VERSION_BUILD 10813
|
||||
|
||||
#define stringify(a) stringify_(a)
|
||||
#define stringify_(a) #a
|
||||
|
@ -121,6 +121,7 @@ Images
|
||||
GFX_Fragment = items/Fragment.png
|
||||
GFX_MonsterSoul = monstersoul.png
|
||||
GFX_MonsterSoulGlow = monstersoulglow.png
|
||||
GFX_Glow = glow.png
|
||||
|
||||
GFX_Thief_Sheet = nico-thief.png
|
||||
GFX_Trapper_Sheet = nico-trapper.png
|
||||
|
Binary file not shown.
BIN
Adventures in Lestoria/assets/glow.png
Normal file
BIN
Adventures in Lestoria/assets/glow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1017 B |
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user