Casting range clamping. Refactored hurt function to accept an upperlevel check so there's no requirement to do it manually anymore. Converted AoE damage functions for Meteor to use AoE hurt functions built-in. Fix upper bridge targeting indicator rendering.

This commit is contained in:
sigonasr2 2023-07-22 03:19:52 -05:00
parent 73d29a2ab5
commit 1faf121bb9
17 changed files with 1479 additions and 243 deletions

View File

@ -285,7 +285,7 @@ void Crawler::HandleUserInput(float fElapsedTime){
}
}
if(player->GetState()!=State::NORMAL){
if(player->GetState()!=State::NORMAL&&player->GetState()!=State::PREP_CAST){
setIdleAnimation=false;
}
@ -313,8 +313,9 @@ void Crawler::HandleUserInput(float fElapsedTime){
void Crawler::UpdateCamera(float fElapsedTime){
lastWorldShakeAdjust=std::max(0.f,lastWorldShakeAdjust-fElapsedTime);
if(worldShakeTime-fElapsedTime>0){
worldShakeVel={1000,-1000};
if(lastWorldShakeAdjust==0){
lastWorldShakeAdjust=0.02;
lastWorldShakeAdjust=0.04;
worldShakeVel.x*=-1;
worldShakeVel.y*=-1;
}
@ -375,8 +376,8 @@ void Crawler::UpdateBullets(float fElapsedTime){
if(!b->deactivated){
if(b->friendly){
for(Monster&m:MONSTER_LIST){
if(b->OnUpperLevel()==m.OnUpperLevel()&&geom2d::overlaps(geom2d::circle(m.GetPos(),12*m.GetSizeMult()),geom2d::circle(b->pos,b->radius))){
if(b->hitList.find(&m)==b->hitList.end()&&m.Hurt(b->damage)){
if(geom2d::overlaps(geom2d::circle(m.GetPos(),12*m.GetSizeMult()),geom2d::circle(b->pos,b->radius))){
if(b->hitList.find(&m)==b->hitList.end()&&m.Hurt(b->damage,b->OnUpperLevel())){
if(!b->hitsMultiple){
if(b->MonsterHit(m)){
it=BULLET_LIST.erase(it);
@ -391,8 +392,8 @@ void Crawler::UpdateBullets(float fElapsedTime){
}
}
} else {
if(b->OnUpperLevel()==player->OnUpperLevel()&&geom2d::overlaps(geom2d::circle(player->GetPos(),12*player->GetSizeMult()/2),geom2d::circle(b->pos,b->radius))){
if(player->Hurt(b->damage)){
if(geom2d::overlaps(geom2d::circle(player->GetPos(),12*player->GetSizeMult()/2),geom2d::circle(b->pos,b->radius))){
if(player->Hurt(b->damage,b->OnUpperLevel())){
if(b->PlayerHit(GetPlayer())){
it=BULLET_LIST.erase(it);
if(it==BULLET_LIST.end()){
@ -427,8 +428,8 @@ void Crawler::UpdateBullets(float fElapsedTime){
}
void Crawler::HurtEnemies(vf2d pos,float radius,int damage,bool upperLevel){
for(Monster&m:MONSTER_LIST){
if(m.OnUpperLevel()==upperLevel&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m.GetPos(),12*m.GetSizeMult()))){
m.Hurt(damage);
if(geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m.GetPos(),12*m.GetSizeMult()))){
m.Hurt(damage,upperLevel);
}
}
}
@ -575,11 +576,25 @@ void Crawler::RenderWorld(float fElapsedTime){
for(Bullet*b:bulletsLower){
b->Draw();
}
if(player->GetState()==State::PREP_CAST){
float precastSize=GetPlayer()->castPrepAbility->precastInfo.size;
vf2d scale=vf2d{precastSize,precastSize}/3.f;
vf2d centerPoint=GetWorldMousePos()-vf2d{game->GFX_Circle.Sprite()->width*scale.x/2,game->GFX_Circle.Sprite()->height*scale.y/2};
view.DrawDecal(centerPoint,GFX_Circle.Decal(),scale,{255,0,0,96});
auto RenderPrecastTargetingIndicator=[&](){
if(player->GetState()==State::PREP_CAST){
float precastSize=GetPlayer()->castPrepAbility->precastInfo.size;
float precastRange=GetPlayer()->castPrepAbility->precastInfo.range;
vf2d scale=vf2d{precastSize,precastSize}*2/3.f;
vf2d centerPoint=GetWorldMousePos()-vf2d{game->GFX_Circle.Sprite()->width*scale.x/2,game->GFX_Circle.Sprite()->height*scale.y/2};
float distance=sqrt(pow(player->GetX()-GetWorldMousePos().x,2)+pow(player->GetY()-GetWorldMousePos().y,2));
if(distance>precastRange){//Clamp the distance.
vf2d pointToCursor = {GetWorldMousePos().x-player->GetX(),GetWorldMousePos().y-player->GetY()};
pointToCursor=pointToCursor.norm()*precastRange;
vf2d centerPoint=player->GetPos()+pointToCursor-vf2d{game->GFX_Circle.Sprite()->width*scale.x/2,game->GFX_Circle.Sprite()->height*scale.y/2};
view.DrawDecal(centerPoint,GFX_Circle.Decal(),scale,{255,0,0,96});
} else {
view.DrawDecal(centerPoint,GFX_Circle.Decal(),scale,{255,0,0,96});
}
}
};
if(!player->OnUpperLevel()){
RenderPrecastTargetingIndicator();
}
#pragma region Foreground Rendering
for(TileGroup&group:foregroundTileGroups){
@ -642,6 +657,9 @@ void Crawler::RenderWorld(float fElapsedTime){
for(Bullet*b:bulletsUpper){
b->Draw();
}
if(player->OnUpperLevel()){
RenderPrecastTargetingIndicator();
}
#pragma region Upper Foreground Rendering
for(TileGroup&group:upperForegroundTileGroups){
if(geom2d::overlaps(group.GetFadeRange(),player->pos)){

View File

@ -39,6 +39,7 @@ struct PulsatingFire:Effect{
PulsatingFire(vf2d pos,float lifetime,AnimationState animation,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
std::vector<float>pulsatingFireValues;
float lastParticleTimer=0;
float lastDamageTimer=0;
bool Update(float fElapsedTime)override;
void Draw()override;
};

View File

@ -34,11 +34,7 @@ bool FireBolt::MonsterHit(Monster& monster)
game->AddEffect(std::make_unique<Effect>(monster.GetPos(),util::random(0.5),AnimationState::DOT_PARTICLE,upperLevel,util::random(2),util::random(0.4),vf2d{util::random(300)-150,util::random(300)-150},Pixel{255,uint8_t(util::random(190)+60),60}));
}
game->SetupWorldShake(0.25);
for(Monster&m:MONSTER_LIST){
if(geom2d::line(monster.GetPos(),m.GetPos()).length()<=2.5*24){
m.Hurt(3*damage);
}
}
game->HurtEnemies(monster.GetPos(),2.5*24,3*damage,OnUpperLevel());
game->AddEffect(std::make_unique<Effect>(monster.GetPos(),0,AnimationState::SPLASH_EFFECT,upperLevel,5,0.25,vf2d{},Pixel{240,120,60}));
return false;
}

View File

@ -52,10 +52,8 @@ bool LightningBolt::MonsterHit(Monster& monster)
if(&m==&monster||monster.OnUpperLevel()!=m.OnUpperLevel())continue;
geom2d::line<float>lineToTarget=geom2d::line<float>(monster.GetPos(),m.GetPos());
float dist=lineToTarget.length();
vf2d vec;
vec.norm();
if(dist<=72){
if(m.Hurt(game->GetPlayer()->GetAttack()*2)){
if(m.Hurt(game->GetPlayer()->GetAttack()*2,OnUpperLevel())){
EMITTER_LIST.push_back(std::make_unique<LightningBoltEmitter>(LightningBoltEmitter(monster.GetPos(),m.GetPos(),0.05,0.25,upperLevel)));
game->AddEffect(std::make_unique<Effect>(m.GetPos(),0.5,AnimationState::LIGHTNING_SPLASH,upperLevel,monster.GetSizeMult(),0.25,vf2d{},WHITE,util::random(PI)));
targetsHit++;

View File

@ -24,12 +24,7 @@ bool Meteor::Update(float fElapsedTime){
vf2d effectPos=vf2d{cos(randomAngle),sin(randomAngle)}*randomRange+meteorOffset;
game->AddEffect(std::make_unique<Effect>(effectPos,0,AnimationState::DOT_PARTICLE,OnUpperLevel(),vf2d{util::random(2)+1,util::random(3)+1},util::random(3)+1,vf2d{util::random(10)-5,-util::random(20)-5},Pixel{255,uint8_t(randomColorTintG),uint8_t(randomColorTint),uint8_t(util::random(128)+128)},0,0,true),effectPos.y<meteorOffset.y);
}
for(Monster&m:MONSTER_LIST){
float dist=sqrt(pow(pos.y-m.GetPos().y,2)+pow(pos.x-m.GetPos().x,2));
if(dist<=4*24){
m.Hurt(game->GetPlayer()->GetAttack()*9);
}
}
game->HurtEnemies(pos,4*24,game->GetPlayer()->GetAttack()*9,OnUpperLevel());
game->AddEffect(std::make_unique<PulsatingFire>(pos,3,AnimationState::FIRE_RING1,OnUpperLevel(),vf2d{8,8},1),true);
}
return Effect::Update(fElapsedTime);

View File

@ -288,7 +288,7 @@ void Monster::Draw(){
void Monster::Collision(Player*p){
if(MONSTER_DATA[type].GetCollisionDmg()>0&&!hasHitPlayer){
hasHitPlayer=true;
p->Hurt(MONSTER_DATA[type].GetCollisionDmg());
p->Hurt(MONSTER_DATA[type].GetCollisionDmg(),OnUpperLevel());
}
Collision();
}
@ -327,8 +327,8 @@ void Monster::Moved(){
AnimationState Monster::GetDeathAnimationName(){
return MONSTER_DATA[type].GetDeathAnimation();
}
bool Monster::Hurt(int damage){
if(hp<=0) return false;
bool Monster::Hurt(int damage,bool onUpperLevel){
if(hp<=0||onUpperLevel!=OnUpperLevel()) return false;
float mod_dmg=damage;
for(Buff&b:GetBuffs(BuffType::DAMAGE_REDUCTION)){
mod_dmg-=damage*b.intensity;

View File

@ -97,8 +97,9 @@ protected:
Animate2D::Frame GetFrame();
void UpdateAnimation(AnimationState state);
bool Update(float fElapsedTime);
//Returns true when damage is actually dealt (there is a death check here.)
bool Hurt(int damage);
//Returns true when damage is actually dealt. Provide whether or not the attack is on the upper level or not. Monsters must be on the same level to get hit by it. (there is a death check and level check here.)
//If you need to hurt multiple enemies try Crawler::HurtEnemies()
bool Hurt(int damage,bool onUpperLevel);
bool IsAlive();
vf2d&GetTargetPos();
Key GetFacingDirection();

View File

@ -304,15 +304,16 @@ void Player::Update(float fElapsedTime){
auto AllowedToCast=[&](Ability&ability){return !ability.precastInfo.precastTargetingRequired;};
//If pressed is set to false, uses held instead.
auto CheckAndPerformAbility=[&](Ability&ability,HWButton key){
if(ability.name!="???"){
if(ability.cooldown==0&&GetMana()>=ability.manaCost){
if(key.bPressed||key.bReleased&&&ability==castPrepAbility&&GetState()==State::PREP_CAST){
if(key.bHeld||key.bReleased&&&ability==castPrepAbility&&GetState()==State::PREP_CAST){
if(AllowedToCast(ability)&&ability.action({})){
ability.cooldown=ability.COOLDOWN_TIME;
mana-=ability.manaCost;
}else
if(ability.precastInfo.precastTargetingRequired&&GetState()!=State::PREP_CAST){
if(ability.precastInfo.precastTargetingRequired&&GetState()==State::NORMAL){
PrepareCast(ability);
}
}
@ -392,8 +393,8 @@ bool Player::HasIframes(){
return iframe_time>0;
}
bool Player::Hurt(int damage){
if(hp<=0||iframe_time!=0) return false;
bool Player::Hurt(int damage,bool onUpperLevel){
if(hp<=0||iframe_time!=0||OnUpperLevel()!=onUpperLevel) return false;
if(state==State::BLOCK)damage=0;
float mod_dmg=damage;
for(Buff&b:GetBuffs(BuffType::DAMAGE_REDUCTION)){
@ -497,7 +498,14 @@ std::vector<Buff>Player::GetBuffs(BuffType buff){
}
void Player::CastSpell(Ability&ability){
castInfo={ability.name,ability.precastInfo.castTime,ability.precastInfo.castTime,game->GetWorldMousePos()};
vf2d castPosition=game->GetWorldMousePos();
float distance=sqrt(pow(GetX()-game->GetWorldMousePos().x,2)+pow(GetY()-game->GetWorldMousePos().y,2));
if(distance>ability.precastInfo.range){//Clamp the distance.
vf2d pointToCursor = {game->GetWorldMousePos().x-GetX(),game->GetWorldMousePos().y-GetY()};
pointToCursor=pointToCursor.norm()*ability.precastInfo.range;
castPosition=GetPos()+pointToCursor;
}
castInfo={ability.name,ability.precastInfo.castTime,ability.precastInfo.castTime,castPosition};
SetState(State::CASTING);
}

View File

@ -106,7 +106,7 @@ public:
void AddBuff(BuffType type,float duration,float intensity);
std::vector<Buff>GetBuffs(BuffType buff);
bool Hurt(int damage);
bool Hurt(int damage,bool onUpperLevel);
//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(AnimationState animState,int specificClass=ANY);
Animate2D::Frame GetFrame();

View File

@ -5,9 +5,10 @@
INCLUDE_game
INCLUDE_ANIMATION_DATA
INCLUDE_MONSTER_LIST
PulsatingFire::PulsatingFire(vf2d pos, float lifetime, AnimationState animation, bool upperLevel, vf2d size, float fadeout, vf2d spd, Pixel col, float rotation, float rotationSpd, bool additiveBlending)
:Effect(pos,lifetime,animation,upperLevel,size,fadeout,spd,col,rotation,rotationSpd,additiveBlending),lastParticleTimer(lifetime){
:Effect(pos,lifetime,animation,upperLevel,size,fadeout,spd,col,rotation,rotationSpd,additiveBlending){
for(int i=0;i<8;i++){
pulsatingFireValues.push_back(util::random(1));
}
@ -15,17 +16,22 @@ PulsatingFire::PulsatingFire(vf2d pos, float lifetime, AnimationState animation,
bool PulsatingFire::Update(float fElapsedTime){
lastParticleTimer-=fElapsedTime;
lastDamageTimer-=fElapsedTime;
if(lastParticleTimer<=0){
int particleCount=rand()%10+1;
int particleCount=rand()%5+1;
for(int i=0;i<particleCount;i++){
float randomAngle=util::random(2*PI);
float randomRange=100*size.x*(1-util::random(0.25))*(1-util::random(0.25));
float randomColorTintG=256-(util::random(128)+util::random(128));
float randomColorTint=util::random(128);
game->AddEffect(std::make_unique<Effect>(pos+vf2d{cos(randomAngle),sin(randomAngle)}*randomRange,0,AnimationState::DOT_PARTICLE,OnUpperLevel(),vf2d{util::random(2)+1,1},util::random(4)+2,vf2d{util::random(20)-5,-util::random(30)-10},Pixel{255,uint8_t(randomColorTintG),uint8_t(randomColorTint),uint8_t(util::random(128)+128)},0,0,true));
float randomRange=12*size.x*(1-util::random(0.25))*(1-util::random(0.25));
float randomColorTintG=128-(util::random(64)+util::random(64));
float randomColorTint=util::random(16);
game->AddEffect(std::make_unique<Effect>(pos+vf2d{cos(randomAngle),sin(randomAngle)}*randomRange,0,AnimationState::DOT_PARTICLE,OnUpperLevel(),vf2d{util::random(2)+1,1},util::random(4)+2,vf2d{util::random(10)-5,-util::random(15)-5},Pixel{128,uint8_t(randomColorTintG),uint8_t(randomColorTint),uint8_t(util::random(128)+128)}));
}
lastParticleTimer=util::random(0.2)+0.025;
}
if(lastDamageTimer<=0){
lastDamageTimer=0.99;
game->HurtEnemies(pos,4*24,game->GetPlayer()->GetAttack()*1,OnUpperLevel());
}
return Effect::Update(fElapsedTime);
}
@ -52,6 +58,6 @@ void PulsatingFire::Draw(){
effectSpr=&ANIMATION_DATA[AnimationState::FIRE_RING1];
}
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(3*lifetime+PI*pulsatingFireValues[i]))+64)});
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(3*lifetime+PI*pulsatingFireValues[i]))+64)*(fadeout/original_fadeoutTime))});
}
}

View File

@ -2,7 +2,7 @@
#define VERSION_MAJOR 0
#define VERSION_MINOR 2
#define VERSION_PATCH 0
#define VERSION_BUILD 712
#define VERSION_BUILD 747
#define stringify(a) stringify_(a)
#define stringify_(a) #a

View File

@ -45,7 +45,7 @@ bool Warrior::AutoAttack(){
closest=&m;
}
}
if(closest!=nullptr&&closest->Hurt(GetAttack())){
if(closest!=nullptr&&closest->Hurt(GetAttack(),OnUpperLevel())){
attack_cooldown_timer=ATTACK_COOLDOWN;
swordSwingTimer=0.2;
SetState(State::SWING_SWORD);

View File

@ -15,7 +15,7 @@ Class Wizard::cl=WIZARD;
Ability Wizard::rightClickAbility={"Teleport",8,5,VERY_DARK_BLUE,DARK_BLUE};
Ability Wizard::ability1={"Firebolt",6,30};
Ability Wizard::ability2={"Lightning Bolt",6,25};
Ability Wizard::ability3={"Meteor",40,75,VERY_DARK_RED,VERY_DARK_RED,{1.5,900,400}};
Ability Wizard::ability3={"Meteor",40,75,VERY_DARK_RED,VERY_DARK_RED,PrecastData(1.5,9*24,4*24)};
Ability Wizard::ability4={"???",0,0};
AnimationState Wizard::idle_n=WIZARD_IDLE_N;
AnimationState Wizard::idle_e=WIZARD_IDLE_E;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.