Switch controller auto-targeting to use logic from Hurt function. This means the auto targeting tries to ensure it hits some target, but prefers a currently vulnerable target over an invulnerable / unhittable one. Cleanup the code to use std::optional. Add in a helper function to get nearest monster. Apply Mark to nearest selected target when Trapper Mark Target ability is used. Release Build 10275.

mac-build
sigonasr2 4 months ago
parent 60b45cf6b1
commit b3c5894be7
  1. 36
      Adventures in Lestoria/Effect.cpp
  2. 6
      Adventures in Lestoria/Effect.h
  3. 2
      Adventures in Lestoria/ForegroundEffect.cpp
  4. 2
      Adventures in Lestoria/Meteor.cpp
  5. 67
      Adventures in Lestoria/Monster.cpp
  6. 3
      Adventures in Lestoria/Monster.h
  7. 36
      Adventures in Lestoria/Player.cpp
  8. 2
      Adventures in Lestoria/PulsatingFire.cpp
  9. 14
      Adventures in Lestoria/Trapper.cpp
  10. 2
      Adventures in Lestoria/Version.h
  11. 2
      Adventures in Lestoria/assets/config/classes/Trapper.txt
  12. 1
      Adventures in Lestoria/assets/config/gfx/gfx.txt
  13. BIN
      Adventures in Lestoria/assets/gamepack.pak
  14. BIN
      Adventures in Lestoria/assets/mark_trail.png
  15. BIN
      x64/Release/Adventures in Lestoria.exe

@ -44,22 +44,32 @@ INCLUDE_ANIMATION_DATA
INCLUDE_game 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(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){ :Effect::Effect(pos,lifetime,imgFile,upperLevel,0.f,fadeout,vf2d{size,size},spd,col,rotation,rotationSpd,additiveBlending){
this->animation.AddState(imgFile,ANIMATION_DATA.at(imgFile)); this->animation.AddState(imgFile,ANIMATION_DATA.at(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) 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){ :pos(pos),lifetime(lifetime),upperLevel(upperLevel),size(size),original_fadeInTime(fadein),fadeout(fadeout),original_fadeOutTime(fadeout),spd(spd),col(col),rotation(rotation),rotationSpd(rotationSpd),additiveBlending(additiveBlending){
this->animation.AddState(imgFile,ANIMATION_DATA.at(imgFile));
}
Effect::Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,float fadein,float fadeout,vf2d size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:pos(pos),lifetime(lifetime),upperLevel(upperLevel),size(size),fadein(fadein),original_fadeInTime(fadein),fadeout(fadeout),original_fadeOutTime(fadeout),spd(spd),col(col),rotation(rotation),rotationSpd(rotationSpd),additiveBlending(additiveBlending){
this->animation.AddState(imgFile,ANIMATION_DATA.at(imgFile)); this->animation.AddState(imgFile,ANIMATION_DATA.at(imgFile));
} }
bool Effect::Update(float fElapsedTime){ bool Effect::Update(float fElapsedTime){
lifetime-=fElapsedTime; aliveTime+=fElapsedTime;
if(lifetime<=0){ if(fadein<original_fadeInTime){
fadeout-=fElapsedTime; fadein=std::min(original_fadeInTime,fadein+fElapsedTime);
if(fadeout<=0){ }else{
dead=true; lifetime-=fElapsedTime;
return false; if(lifetime<=0){
fadeout-=fElapsedTime;
if(fadeout<=0){
dead=true;
return false;
}
} }
} }
rotation+=rotationSpd*fElapsedTime; rotation+=rotationSpd*fElapsedTime;
@ -70,10 +80,14 @@ bool Effect::Update(float fElapsedTime){
void Effect::Draw()const{ void Effect::Draw()const{
if(additiveBlending)game->SetDecalMode(DecalMode::ADDITIVE); if(additiveBlending)game->SetDecalMode(DecalMode::ADDITIVE);
if(fadeout==0){ if(fadeout==0&&fadein==original_fadeInTime){
game->view.DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,col); game->view.DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,col);
} else { }else
game->view.DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,{col.r,col.g,col.b,uint8_t(fadeout/original_fadeoutTime*col.a)}); if(fadein==original_fadeInTime){
game->view.DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,{col.r,col.g,col.b,uint8_t(fadein/original_fadeInTime*col.a)});
}else
if(fadeout==0){
game->view.DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,{col.r,col.g,col.b,uint8_t(fadeout/original_fadeOutTime*col.a)});
} }
game->SetDecalMode(DecalMode::NORMAL); game->SetDecalMode(DecalMode::NORMAL);
} }

@ -54,6 +54,7 @@ struct Effect{
vf2d pos={0,0}; vf2d pos={0,0};
float lifetime=0; float lifetime=0;
float fadeout=0; float fadeout=0;
float fadein=0;
vf2d size={1,1}; vf2d size={1,1};
Pixel col=WHITE; Pixel col=WHITE;
vf2d spd={}; vf2d spd={};
@ -65,18 +66,21 @@ private:
public: public:
Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,float size=1.0f,float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false); Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,float size=1.0f,float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false); Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,float fadein,float fadeout,vf2d size,vf2d spd,Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
virtual bool Update(float fElapsedTime); virtual bool Update(float fElapsedTime);
Animate2D::Frame GetFrame()const; Animate2D::Frame GetFrame()const;
virtual void Draw()const; virtual void Draw()const;
bool OnUpperLevel(); bool OnUpperLevel();
const EffectType GetType()const; const EffectType GetType()const;
protected: protected:
float original_fadeoutTime; float original_fadeOutTime;
float original_fadeInTime;
EffectType type{EffectType::NONE}; EffectType type{EffectType::NONE};
private: private:
Animate2D::Animation<std::string>animation; Animate2D::Animation<std::string>animation;
Animate2D::AnimationState internal_animState; Animate2D::AnimationState internal_animState;
bool upperLevel=false; bool upperLevel=false;
double aliveTime{};
}; };
struct Meteor:Effect{ struct Meteor:Effect{

@ -52,7 +52,7 @@ void ForegroundEffect::Draw()const{
if(fadeout==0){ if(fadeout==0){
game->DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,col); game->DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,col);
} else { } else {
game->DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,{col.r,col.g,col.b,uint8_t(fadeout/original_fadeoutTime*255)}); game->DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,{col.r,col.g,col.b,uint8_t(fadeout/original_fadeOutTime*255)});
} }
game->SetDecalMode(DecalMode::NORMAL); game->SetDecalMode(DecalMode::NORMAL);
} }

@ -81,5 +81,5 @@ void Meteor::Draw()const{
if(lifetime<=0){ if(lifetime<=0){
meteorOffset=pos-vf2d{0,GetFrame().GetSourceRect().size.y/4.f}*size; meteorOffset=pos-vf2d{0,GetFrame().GetSourceRect().size.y/4.f}*size;
} }
game->view.DrawPartialRotatedDecal(meteorOffset,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,{col.r,col.g,col.b,uint8_t(fadeout/original_fadeoutTime*255)}); game->view.DrawPartialRotatedDecal(meteorOffset,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,{col.r,col.g,col.b,uint8_t(fadeout/original_fadeOutTime*255)});
} }

@ -473,23 +473,23 @@ void Monster::Draw()const{
} }
#pragma region Render Trapper Marked Targets #pragma region Render Trapper Marked Targets
const uint8_t markStackCount{GetMarkStacks()}; const uint8_t markStackCount{GetMarkStacks()};
if(markStackCount>0){ if(markStackCount>0){
float markRotation{-util::lerp(0.f,10.f,markApplicationTimer/0.5f)*sin(PI*markApplicationTimer)}; float markRotation{-util::lerp(0.f,10.f,markApplicationTimer/0.5f)*sin(PI*markApplicationTimer)};
vf2d markScale{vf2d{}.lerp(vf2d{GetSizeMult(),GetSizeMult()},(0.5f-markApplicationTimer)/0.5f)}; vf2d markScale{vf2d{}.lerp(vf2d{GetSizeMult(),GetSizeMult()},(0.5f-markApplicationTimer)/0.5f)};
const Animate2D::Frame&markImg{ANIMATION_DATA["target.png"].GetFrame(game->GetRunTime())}; const Animate2D::Frame&markImg{ANIMATION_DATA["target.png"].GetFrame(game->GetRunTime())};
Pixel markCol{markStackCount>1?WHITE:RED}; Pixel markCol{markStackCount>1?WHITE:RED};
const std::vector<Buff>&buffList{GetBuffs(BuffType::TRAPPER_MARK)}; const std::vector<Buff>&buffList{GetBuffs(BuffType::TRAPPER_MARK)};
float remainingStackDuration{}; float remainingStackDuration{};
for(const Buff&b:buffList){ for(const Buff&b:buffList){
if(b.type==BuffType::TRAPPER_MARK){ if(b.type==BuffType::TRAPPER_MARK){
remainingStackDuration=b.duration; remainingStackDuration=b.duration;
break; break;
}
} }
if(remainingStackDuration<1.f)markCol.a*=remainingStackDuration;
game->view.DrawPartialRotatedDecal(drawPos,markImg.GetSourceImage()->Decal(),markRotation,markImg.GetSourceRect().size/2.f,markImg.GetSourceRect().pos,markImg.GetSourceRect().size,markScale,markCol);
} }
if(remainingStackDuration<1.f)markCol.a*=remainingStackDuration;
game->view.DrawPartialRotatedDecal(drawPos,markImg.GetSourceImage()->Decal(),markRotation,markImg.GetSourceRect().size/2.f,markImg.GetSourceRect().pos,markImg.GetSourceRect().size,markScale,markCol);
}
#pragma endregion #pragma endregion
if(GameSettings::TerrainCollisionBoxesEnabled()&&IsSolid()&&solidFadeTimer>0.f){ if(GameSettings::TerrainCollisionBoxesEnabled()&&IsSolid()&&solidFadeTimer>0.f){
@ -640,7 +640,7 @@ bool Monster::_Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag da
const bool NormalDamageCalculationRequired{!IsDOT&&!TrueDamage}; const bool NormalDamageCalculationRequired{!IsDOT&&!TrueDamage};
const bool PlayHitSoundEffect{!IsDOT}; const bool PlayHitSoundEffect{!IsDOT};
if(!TrueDamage&&!IsDOT&&(Invulnerable()||!IsAlive()||onUpperLevel!=OnUpperLevel()||AttackAvoided(z)))return false; if(!TrueDamage&&!IsDOT&&InUndamageableState(onUpperLevel,z))return false;
if(game->InBossEncounter()){ if(game->InBossEncounter()){
game->StartBossEncounter(); game->StartBossEncounter();
} }
@ -674,6 +674,7 @@ bool Monster::_Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag da
dotNumberPtr.get()->RecalculateSize(); dotNumberPtr.get()->RecalculateSize();
}else{ }else{
dotNumberPtr=std::make_shared<DamageNumber>(pos-vf2d{0,GetCollisionRadius()/2.f},int(mod_dmg),false,DamageNumberType::DOT); dotNumberPtr=std::make_shared<DamageNumber>(pos-vf2d{0,GetCollisionRadius()/2.f},int(mod_dmg),false,DamageNumberType::DOT);
dotNumberPtr->riseSpd=dotNumberPtr->originalRiseSpd=-10.f;
DAMAGENUMBER_LIST.push_back(dotNumberPtr); DAMAGENUMBER_LIST.push_back(dotNumberPtr);
} }
lastDotTimer=0.05f; lastDotTimer=0.05f;
@ -1261,3 +1262,37 @@ void Monster::ApplyMark(float time,uint8_t stackCount){
}else AddBuff(BuffType::TRAPPER_MARK,time,stackCount); }else AddBuff(BuffType::TRAPPER_MARK,time,stackCount);
markApplicationTimer=0.5f; markApplicationTimer=0.5f;
} }
std::optional<Monster*>Monster::GetNearestMonster(const vf2d point,const float maxDistance,const bool onUpperLevel,const float z){
std::optional<Monster*>closestMonster;
std::optional<Monster*>closestGenericMonster;
for(std::unique_ptr<Monster>&m:MONSTER_LIST){
geom2d::line<float>aimingLine=geom2d::line<float>(point,m->GetPos());
float distToMonster=aimingLine.length();
float distToClosestPoint,distToClosestGenericPoint;
if(closestMonster.has_value())distToClosestPoint=geom2d::line<float>(point,closestMonster.value()->GetPos()).length();
else distToClosestPoint=std::numeric_limits<float>::max();
if(closestGenericMonster.has_value())distToClosestGenericPoint=geom2d::line<float>(point,closestGenericMonster.value()->GetPos()).length();
else distToClosestGenericPoint=std::numeric_limits<float>::max();
if(!m->InUndamageableState(onUpperLevel,z)){
if(distToClosestPoint>distToMonster&&distToMonster<=maxDistance){
closestMonster=&*m;
}
}
if(m->IsAlive()&&distToClosestGenericPoint>distToMonster&&distToMonster<=maxDistance){
closestGenericMonster=&*m;
}
}
if(closestMonster.has_value()){
return closestMonster;
}else
if(closestGenericMonster.has_value()){
return closestGenericMonster;
}
return {};
}
const bool Monster::InUndamageableState(const bool onUpperLevel,const float z)const{
return Invulnerable()||!IsAlive()||onUpperLevel!=OnUpperLevel()||AttackAvoided(z);
}

@ -202,6 +202,9 @@ public:
const uint8_t GetMarkStacks()const; //Number of Trapper marks on this target. const uint8_t GetMarkStacks()const; //Number of Trapper marks on this target.
void TriggerMark(); //Deals no damage, but causes a mark proc to occur. void TriggerMark(); //Deals no damage, but causes a mark proc to occur.
void ApplyMark(float time,uint8_t stackCount); //Adds stackCount mark stacks to the target, refreshing the buff to time time. void ApplyMark(float time,uint8_t stackCount); //Adds stackCount mark stacks to the target, refreshing the buff to time time.
//Gets the nearest target that can be immediately targeted
static std::optional<Monster*>GetNearestMonster(const vf2d point,const float maxDistance,const bool onUpperLevel,const float z);
const bool InUndamageableState(const bool onUpperLevel,const float z)const;
private: private:
//NOTE: Marking a monster for deletion does not trigger any death events. It just simply removes the monster from the field!! //NOTE: Marking a monster for deletion does not trigger any death events. It just simply removes the monster from the field!!
// The way this works is that monsters marked for deletion will cause the monster update loop to detect there's at least one or more monsters that must be deleted and will call erase_if on the list at the end of the iteration loop. // The way this works is that monsters marked for deletion will cause the monster update loop to detect there's at least one or more monsters that must be deleted and will call erase_if on the list at the end of the iteration loop.

@ -1575,33 +1575,45 @@ const vf2d Player::GetAimingLocation(bool useWalkDir,bool invert){
if(xAxis!=0.f||yAxis!=0.f){ if(xAxis!=0.f||yAxis!=0.f){
return {(game->ScreenWidth()*xAxis)/2+game->ScreenWidth()/2,(game->ScreenHeight()*yAxis)/2+game->ScreenHeight()/2}; return {(game->ScreenWidth()*xAxis)/2+game->ScreenWidth()/2,(game->ScreenHeight()*yAxis)/2+game->ScreenHeight()/2};
}else{ }else{
if(useWalkDir&&movementVelocity!=vf2d{0,0}){ if(useWalkDir&&movementVelocity!=vf2d{0,0}){
xAxis=aimingAngle.cart().x; xAxis=aimingAngle.cart().x;
yAxis=aimingAngle.cart().y; yAxis=aimingAngle.cart().y;
} }
const vf2d MAX{std::numeric_limits<float>::max(),std::numeric_limits<float>::max()};
if(xAxis!=0.f||yAxis!=0.f){ if(xAxis!=0.f||yAxis!=0.f){
return {(game->ScreenWidth()*xAxis)/2+game->ScreenWidth()/2,(game->ScreenHeight()*yAxis)/2+game->ScreenHeight()/2}; return {(game->ScreenWidth()*xAxis)/2+game->ScreenWidth()/2,(game->ScreenHeight()*yAxis)/2+game->ScreenHeight()/2};
}else{ }else{
//Find the closest monster target. //Find the closest monster target. Provide a "Generic" target in case a target that is invulnerable is the only target that is available (and alive).
vf2d closestPoint={std::numeric_limits<float>::max(),std::numeric_limits<float>::max()}; std::optional<vf2d>closestPoint;
std::optional<vf2d>closestGenericPoint; //Even if the monster is invulnerable, it might be worth targeting if a normal target is not found.
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::unique_ptr<Monster>&m:MONSTER_LIST){
if(m->IsAlive()&&!m->Invulnerable()){ geom2d::line<float>aimingLine=geom2d::line<float>(GetPos(),m->GetPos());
geom2d::line<float>aimingLine=geom2d::line<float>(GetPos(),m->GetPos()); float distToMonster=aimingLine.length();
float distToMonster=aimingLine.length(); float distToClosestPoint=geom2d::line<float>(GetPos(),closestPoint.value_or(MAX)).length();
float distToClosestPoint=geom2d::line<float>(GetPos(),closestPoint).length(); float distToClosestGenericPoint=geom2d::line<float>(GetPos(),closestGenericPoint.value_or(MAX)).length();
if(!m->InUndamageableState(OnUpperLevel(),GetZ())){
if(distToClosestPoint>distToMonster&&distToMonster<=operator""_Pixels("Player.Auto Aim Detection Distance"_F)){ if(distToClosestPoint>distToMonster&&distToMonster<=operator""_Pixels("Player.Auto Aim Detection Distance"_F)){
closestPoint=m->GetPos(); closestPoint=m->GetPos();
} }
} }
if(m->IsAlive()&&distToClosestGenericPoint>distToMonster&&distToMonster<=operator""_Pixels("Player.Auto Aim Detection Distance"_F)){
closestGenericPoint=m->GetPos();
}
} }
if(closestPoint!=vf2d{std::numeric_limits<float>::max(),std::numeric_limits<float>::max()}){ std::optional<geom2d::line<float>>aimingLine;
geom2d::line<float>aimingLine=geom2d::line<float>(GetPos(),closestPoint); if(closestPoint.has_value()){
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)))); aimingLine=geom2d::line<float>(GetPos(),closestPoint.value());
return game->GetScreenSize()/2+aimingPoint-GetPos();
}else }else
return game->GetScreenSize()/2+vf2d{float(operator""_Pixels("Player.Aiming Cursor Max Distance"_F)),aimingAngle.y}.cart(); if(closestGenericPoint.has_value()){
aimingLine=geom2d::line<float>(GetPos(),closestGenericPoint.value());
}
if(aimingLine.has_value()){
vf2d aimingPoint=aimingLine.value().rpoint(invert?(-operator""_Pixels("Player.Aiming Cursor Max Distance"_F)):std::min(aimingLine.value().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();
} }
} }
}else{ }else{

@ -98,6 +98,6 @@ void PulsatingFire::Draw()const{
effectSpr=&ANIMATION_DATA["fire_ring0.png"]; effectSpr=&ANIMATION_DATA["fire_ring0.png"];
} }
const Renderable*img=effectSpr->GetFrame(0).GetSourceImage(); const Renderable*img=effectSpr->GetFrame(0).GetSourceImage();
game->view.DrawPartialDecal(pos-effectSpr->GetFrame(0).GetSourceRect().size/2*size,img->Decal(),effectSpr->GetFrame(0).GetSourceRect().pos,effectSpr->GetFrame(0).GetSourceRect().size,size,{255,uint8_t(pulsatingFireValues[i]*256),0,uint8_t((63*(sin("Wizard.Ability 3.FireRingOscillatingFrequency"_F*lifetime+PI*pulsatingFireValues[i]))+63)*(fadeout/original_fadeoutTime))}); game->view.DrawPartialDecal(pos-effectSpr->GetFrame(0).GetSourceRect().size/2*size,img->Decal(),effectSpr->GetFrame(0).GetSourceRect().pos,effectSpr->GetFrame(0).GetSourceRect().size,size,{255,uint8_t(pulsatingFireValues[i]*256),0,uint8_t((63*(sin("Wizard.Ability 3.FireRingOscillatingFrequency"_F*lifetime+PI*pulsatingFireValues[i]))+63)*(fadeout/original_fadeOutTime))});
} }
} }

@ -96,7 +96,19 @@ void Trapper::InitializeClassAbilities(){
#pragma region Trapper Ability 1 (Mark Target) #pragma region Trapper Ability 1 (Mark Target)
Trapper::ability1.action= Trapper::ability1.action=
[](Player*p,vf2d pos={}){ [](Player*p,vf2d pos={}){
std::optional<Monster*>nearestMonster{Monster::GetNearestMonster(pos,Trapper::ability1.precastInfo.range,p->OnUpperLevel(),p->GetZ())};
vf2d targetPos{pos};
if(nearestMonster.has_value()){
targetPos=nearestMonster.value()->GetPos();
nearestMonster.value()->ApplyMark("Trapper.Ability 1.Duration"_F,"Trapper.Ability 1.Stack Count"_I);
}
for(int i:std::ranges::iota_view(0,int(util::distance(p->GetPos(),targetPos)/16))){
float drawDist{i*16.f};
float fadeInTime{i*0.05f};
float fadeOutTime{0.5f+i*0.05f};
float effectSize{util::random(0.4f)};
game->AddEffect(std::make_unique<Effect>(geom2d::line<float>(p->GetPos(),targetPos).rpoint(drawDist),0.f,"mark_trail.png",p->OnUpperLevel(),fadeInTime,fadeOutTime,vf2d{effectSize,effectSize},vf2d{},Pixel{255,255,255,uint8_t(util::random_range(60,150))},0.f,0.f,true),true);
}
return true; return true;
}; };
#pragma endregion #pragma endregion

@ -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 10264 #define VERSION_BUILD 10275
#define stringify(a) stringify_(a) #define stringify(a) stringify_(a)
#define stringify_(a) #a #define stringify_(a) #a

@ -59,7 +59,7 @@ Trapper
CancelCast = 0 CancelCast = 0
Duration = 7s Duration = 7s
Hit Count = 5 Stack Count = 5
Damage Increase Bonus = 60% Damage Increase Bonus = 60%
#RGB Values. Color 1 is the circle at full cooldown, Color 2 is the color at empty cooldown. #RGB Values. Color 1 is the circle at full cooldown, Color 2 is the color at empty cooldown.

@ -110,6 +110,7 @@ Images
GFX_Dagger = dagger.png GFX_Dagger = dagger.png
GFX_Shine = shine.png GFX_Shine = shine.png
GFX_TargetMark = target.png GFX_TargetMark = target.png
GFX_MarkTrail = mark_trail.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: 941 B

Loading…
Cancel
Save