Added ghost saber expanding behavior. Fix Prediction bombardment pattern to properly use player's previous position. Release Build 12029.

master
sigonasr2 1 week ago
parent fdef405ed8
commit 071e98aecd
  1. 3
      Adventures in Lestoria/AdventuresInLestoria.cpp
  2. 5
      Adventures in Lestoria/BulletTypes.h
  3. 12
      Adventures in Lestoria/GhostOfPirateCaptain.cpp
  4. 5
      Adventures in Lestoria/GhostSaber.cpp
  5. 3
      Adventures in Lestoria/Monster.cpp
  6. 2
      Adventures in Lestoria/Monster.h
  7. 5
      Adventures in Lestoria/MonsterData.cpp
  8. 2
      Adventures in Lestoria/MonsterData.h
  9. 6
      Adventures in Lestoria/Player.cpp
  10. 2
      Adventures in Lestoria/Version.h
  11. 7
      Adventures in Lestoria/assets/config/MonsterStrategies.txt
  12. 3
      Adventures in Lestoria/assets/config/Monsters.txt
  13. BIN
      x64/Release/Adventures in Lestoria.exe

@ -539,6 +539,9 @@ void AiL::HandleUserInput(float fElapsedTime){
else Menu::OpenMenu(MenuType::PAUSE); else Menu::OpenMenu(MenuType::PAUSE);
} }
float animationSpd=0.f; float animationSpd=0.f;
if(player->GetPreviousPos()!=player->GetPos()){
player->previousPos=player->GetPos();
}
player->movementVelocity={}; player->movementVelocity={};
if((player->GetVelocity().mag()<"Player.Move Allowed Velocity Lower Limit"_F&&player->CanMove())||(player->GetState()==State::ROLL&&"Thief.Right Click Ability.Roll Time"_F-player->rolling_timer>=0.2f)){ if((player->GetVelocity().mag()<"Player.Move Allowed Velocity Lower Limit"_F&&player->CanMove())||(player->GetState()==State::ROLL&&"Thief.Right Click Ability.Roll Time"_F-player->rolling_timer>=0.2f)){
auto GetPlayerStaircaseDirection=[&](){ auto GetPlayerStaircaseDirection=[&](){

@ -445,7 +445,7 @@ private:
}; };
struct GhostSaber:public Bullet{ struct GhostSaber:public Bullet{
GhostSaber(const vf2d pos,const std::weak_ptr<Monster>target,const float lifetime,const float distFromTarget,const float knockbackAmt,const float initialRot,const float radius,const int damage,const bool upperLevel,const float rotSpd,const bool friendly=false,const Pixel col=WHITE,const vf2d scale={1,1},const float image_angle=0.f); GhostSaber(const vf2d pos,const std::weak_ptr<Monster>target,const float lifetime,const float distFromTarget,const float knockbackAmt,const float initialRot,const float radius,const float expandSpd,const int damage,const bool upperLevel,const float rotSpd,const bool friendly=false,const Pixel col=WHITE,const vf2d scale={1,1},const float image_angle=0.f);
void Update(float fElapsedTime)override; void Update(float fElapsedTime)override;
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()!!
@ -453,10 +453,11 @@ struct GhostSaber:public Bullet{
private: private:
const std::weak_ptr<Monster>attachedMonster; const std::weak_ptr<Monster>attachedMonster;
const float rotSpd; const float rotSpd;
const float distFromTarget; float distFromTarget;
float rot; float rot;
const float knockbackAmt; const float knockbackAmt;
float particleTimer{}; float particleTimer{};
float aliveTime{}; float aliveTime{};
float expandSpd{};
Oscillator<uint8_t>alphaOscillator{128U,255U,0.6f}; Oscillator<uint8_t>alphaOscillator{128U,255U,0.6f};
}; };

@ -68,7 +68,7 @@ void Monster::STRATEGY::GHOST_OF_PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std
const auto AdvanceCannonPhase{[&m,&strategy](){ const auto AdvanceCannonPhase{[&m,&strategy](){
m.GetFloat(A::CANNON_TIMER)=0.f; m.GetFloat(A::CANNON_TIMER)=0.f;
//if(m.GetInt(A::CANNON_PHASE)+1>=PHASE_COUNT)m.GetInt(A::CANNON_SHOT_TYPE)=util::random()%5; if(m.GetInt(A::CANNON_PHASE)+1>=PHASE_COUNT)m.GetInt(A::CANNON_SHOT_TYPE)=util::random()%5;
const int prevCannonPhase{m.I(A::CANNON_PHASE)}; const int prevCannonPhase{m.I(A::CANNON_PHASE)};
m.I(A::CANNON_PHASE)=(m.I(A::CANNON_PHASE)+1)%PHASE_COUNT; m.I(A::CANNON_PHASE)=(m.I(A::CANNON_PHASE)+1)%PHASE_COUNT;
if(prevCannonPhase>m.I(A::CANNON_PHASE)){//Phase has wrapped around, reset cannon shot count. if(prevCannonPhase>m.I(A::CANNON_PHASE)){//Phase has wrapped around, reset cannon shot count.
@ -84,7 +84,7 @@ void Monster::STRATEGY::GHOST_OF_PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std
} }
} }
}}; }};
if(m.F(A::SHRAPNEL_SHOT_FALL_TIMER)>0.f){ if(m.F(A::SHRAPNEL_SHOT_FALL_TIMER)>0.f){
m.F(A::SHRAPNEL_SHOT_FALL_TIMER)-=fElapsedTime; m.F(A::SHRAPNEL_SHOT_FALL_TIMER)-=fElapsedTime;
while(m.I(A::SHRAPNEL_SHOT_COUNT)&&m.F(A::SHRAPNEL_SHOT_FALL_TIMER)<=0.f){ while(m.I(A::SHRAPNEL_SHOT_COUNT)&&m.F(A::SHRAPNEL_SHOT_FALL_TIMER)<=0.f){
@ -101,10 +101,11 @@ void Monster::STRATEGY::GHOST_OF_PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std
if(m.B(A::FIRST_WAVE_COMPLETE)){ if(m.B(A::FIRST_WAVE_COMPLETE)){
m.SetVelocity(util::pointTo(m.GetPos(),game->GetPlayer()->GetPos())); m.SetVelocity(util::pointTo(m.GetPos(),game->GetPlayer()->GetPos()));
if(m.F(A::GHOST_SABER_TIMER)<=0.f){ const float distToPlayer{util::distance(m.GetPos(),game->GetPlayer()->GetPos())};
if(m.F(A::GHOST_SABER_TIMER)<=0.f&&distToPlayer<=ConfigPixels("Ghost Saber Activation Range")){
m.F(A::GHOST_SABER_TIMER)=ConfigFloat("Ghost Saber Cooldown"); m.F(A::GHOST_SABER_TIMER)=ConfigFloat("Ghost Saber Cooldown");
const float playerToMonsterAngle{util::pointTo(game->GetPlayer()->GetPos(),m.GetPos()).polar().y}; const float playerToMonsterAngle{util::pointTo(game->GetPlayer()->GetPos(),m.GetPos()).polar().y};
CreateBullet(GhostSaber)(m.GetPos(),m.GetWeakPointer(),ConfigFloat("Ghost Saber Lifetime"),ConfigFloat("Ghost Saber Distance"),ConfigFloat("Ghost Saber Knockback Amt"),playerToMonsterAngle,ConfigFloat("Ghost Saber Radius"),ConfigInt("Ghost Saber Damage"),m.OnUpperLevel(),util::degToRad(ConfigFloat("Ghost Saber Rotation Spd")))EndBullet; CreateBullet(GhostSaber)(m.GetPos(),m.GetWeakPointer(),ConfigFloat("Ghost Saber Lifetime"),ConfigFloat("Ghost Saber Distance"),ConfigFloat("Ghost Saber Knockback Amt"),playerToMonsterAngle,ConfigFloat("Ghost Saber Radius"),ConfigFloat("Ghost Saber Expand Spd"),ConfigInt("Ghost Saber Damage"),m.OnUpperLevel(),util::degToRad(ConfigFloat("Ghost Saber Rotation Spd")))EndBullet;
} }
} }
@ -121,7 +122,7 @@ void Monster::STRATEGY::GHOST_OF_PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std
if(cannonCycle==CANNON_SHOT)TOTAL_CANNON_SHOTS++; if(cannonCycle==CANNON_SHOT)TOTAL_CANNON_SHOTS++;
} }
m.B(A::FIRST_WAVE_COMPLETE)=false; m.B(A::FIRST_WAVE_COMPLETE)=false;
m.I(A::CANNON_SHOT_TYPE)=PREDICTION; m.I(A::CANNON_SHOT_TYPE)=BOMBARDMENT;
m.ForceSetPos({-400.f,-400.f}); m.ForceSetPos({-400.f,-400.f});
SETPHASE(NORMAL); SETPHASE(NORMAL);
}break; }break;
@ -157,6 +158,7 @@ void Monster::STRATEGY::GHOST_OF_PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std
LOG(std::format("Previous Pos: {} Current: {}",game->GetPlayer()->GetPreviousPos().str(),game->GetPlayer()->GetPos().str())); LOG(std::format("Previous Pos: {} Current: {}",game->GetPlayer()->GetPreviousPos().str(),game->GetPlayer()->GetPos().str()));
const float angle{util::angleTo(game->GetPlayer()->GetPreviousPos(),game->GetPlayer()->GetPos())}; const float angle{util::angleTo(game->GetPlayer()->GetPreviousPos(),game->GetPlayer()->GetPos())};
const float range{util::random_range(0,100.f*game->GetPlayer()->GetMoveSpdMult())*ConfigFloat("Cannon Shot Impact Time")}; const float range{util::random_range(0,100.f*game->GetPlayer()->GetMoveSpdMult())*ConfigFloat("Cannon Shot Impact Time")};
LOG(std::format("Range/Angle: {}",vf2d{range,angle}.str()));
const vf2d targetPos{game->GetPlayer()->GetPos()+vf2d{range,angle}.cart()}; const vf2d targetPos{game->GetPlayer()->GetPos()+vf2d{range,angle}.cart()};
CreateBullet(FallingBullet)("cannonball.png",targetPos,ConfigVec("Cannon Vel"),ConfigFloatArr("Cannon Vel",2),ConfigFloat("Indicator Time"),ConfigPixels("Cannon Radius"),ConfigInt("Cannon Damage"),m.OnUpperLevel(),false,ConfigFloat("Cannon Knockback Amt"),ConfigFloat("Cannon Shot Impact Time"),false,ConfigPixel("Cannon Spell Circle Color"),vf2d{ConfigFloat("Cannon Radius")/100.f*1.75f,ConfigFloat("Cannon Radius")/100.f*1.75f},util::random(2*PI),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Circle Rotation Spd")),ConfigPixel("Cannon Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Insignia Rotation Spd")))EndBullet; CreateBullet(FallingBullet)("cannonball.png",targetPos,ConfigVec("Cannon Vel"),ConfigFloatArr("Cannon Vel",2),ConfigFloat("Indicator Time"),ConfigPixels("Cannon Radius"),ConfigInt("Cannon Damage"),m.OnUpperLevel(),false,ConfigFloat("Cannon Knockback Amt"),ConfigFloat("Cannon Shot Impact Time"),false,ConfigPixel("Cannon Spell Circle Color"),vf2d{ConfigFloat("Cannon Radius")/100.f*1.75f,ConfigFloat("Cannon Radius")/100.f*1.75f},util::random(2*PI),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Circle Rotation Spd")),ConfigPixel("Cannon Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Insignia Rotation Spd")))EndBullet;
}break; }break;

@ -38,12 +38,13 @@ All rights reserved.
#include "BulletTypes.h" #include "BulletTypes.h"
GhostSaber::GhostSaber(const vf2d pos,const std::weak_ptr<Monster>target,const float lifetime,const float distFromTarget,const float knockbackAmt,const float initialRot,const float radius,const int damage,const bool upperLevel,const float rotSpd,const bool friendly,const Pixel col,const vf2d scale,const float image_angle) GhostSaber::GhostSaber(const vf2d pos,const std::weak_ptr<Monster>target,const float lifetime,const float distFromTarget,const float knockbackAmt,const float initialRot,const float radius,const float expandSpd,const int damage,const bool upperLevel,const float rotSpd,const bool friendly,const Pixel col,const vf2d scale,const float image_angle)
:Bullet(target.lock()->GetPos()+vf2d{distFromTarget,initialRot}.cart(),{},radius,damage,"ghost_dagger.png",upperLevel,false,INFINITE,false,friendly,col,scale,image_angle),attachedMonster(target),rotSpd(rotSpd),distFromTarget(distFromTarget),rot(initialRot),aliveTime(lifetime),knockbackAmt(knockbackAmt){} :Bullet(target.lock()->GetPos()+vf2d{distFromTarget,initialRot}.cart(),{},radius,damage,"ghost_dagger.png",upperLevel,false,INFINITE,false,friendly,col,scale,image_angle),attachedMonster(target),rotSpd(rotSpd),distFromTarget(distFromTarget),rot(initialRot),aliveTime(lifetime),knockbackAmt(knockbackAmt),expandSpd(expandSpd){}
void GhostSaber::Update(float fElapsedTime){ void GhostSaber::Update(float fElapsedTime){
alphaOscillator.Update(fElapsedTime); alphaOscillator.Update(fElapsedTime);
particleTimer-=fElapsedTime; particleTimer-=fElapsedTime;
aliveTime-=fElapsedTime; aliveTime-=fElapsedTime;
distFromTarget+=expandSpd*fElapsedTime;
if(particleTimer<=0.f){ if(particleTimer<=0.f){
particleTimer+=0.05f; particleTimer+=0.05f;
game->AddEffect(std::make_unique<ShineEffect>(pos,0.1f,0.1f,"pixel.png",2.f,vf2d{},Pixel{239,215,98,192},util::random(2*PI),0.f,true)); game->AddEffect(std::make_unique<ShineEffect>(pos,0.1f,0.1f,"pixel.png",2.f,vf2d{},Pixel{239,215,98,192},util::random(2*PI),0.f,true));

@ -282,6 +282,7 @@ void Monster::Update(const float fElapsedTime){
specialMarkApplicationTimer=std::max(0.f,specialMarkApplicationTimer-fElapsedTime); specialMarkApplicationTimer=std::max(0.f,specialMarkApplicationTimer-fElapsedTime);
lastFacingDirectionChange+=fElapsedTime; lastFacingDirectionChange+=fElapsedTime;
timeSpentAlive+=fElapsedTime; timeSpentAlive+=fElapsedTime;
floatOscillator.Update(fElapsedTime);
if(IsUnconscious()){ if(IsUnconscious()){
unconsciousTimer=std::max(0.f,unconsciousTimer-fElapsedTime); unconsciousTimer=std::max(0.f,unconsciousTimer-fElapsedTime);
@ -501,7 +502,7 @@ void Monster::Draw()const{
else if(glowPurpleBuff.has_value())blendCol=Pixel{uint8_t(255*abs(sin(1.4*glowPurpleBuff.value().duration))),uint8_t(255*abs(sin(1.4*glowPurpleBuff.value().duration))),uint8_t(128+127*abs(sin(1.4*glowPurpleBuff.value().duration)))}; else if(glowPurpleBuff.has_value())blendCol=Pixel{uint8_t(255*abs(sin(1.4*glowPurpleBuff.value().duration))),uint8_t(255*abs(sin(1.4*glowPurpleBuff.value().duration))),uint8_t(128+127*abs(sin(1.4*glowPurpleBuff.value().duration)))};
const vf2d hitTimerOffset=vf2d{sin(20*PI*lastHitTimer+randomFrameOffset),0.f}*2.f*GetSizeMult(); const vf2d hitTimerOffset=vf2d{sin(20*PI*lastHitTimer+randomFrameOffset),0.f}*2.f*GetSizeMult();
const vf2d zOffset=-vf2d{0,GetZ()}; const vf2d zOffset=-vf2d{0,GetZ()}+vf2d{0,(MONSTER_DATA[GetName()].IsFloating()?floatOscillator.get():0.f)};
const vf2d drawPos=GetPos()+zOffset+hitTimerOffset; const vf2d drawPos=GetPos()+zOffset+hitTimerOffset;

@ -49,6 +49,7 @@ All rights reserved.
#include "MonsterData.h" #include "MonsterData.h"
#include "Direction.h" #include "Direction.h"
#include "HurtDamageInfo.h" #include "HurtDamageInfo.h"
#include "Oscillator.h"
INCLUDE_ITEM_DATA INCLUDE_ITEM_DATA
INCLUDE_MONSTER_DATA INCLUDE_MONSTER_DATA
@ -344,6 +345,7 @@ private:
const float TIME_BETWEEN_LINE_REMOVALS{0.025f}; const float TIME_BETWEEN_LINE_REMOVALS{0.025f};
uint8_t scanLine{24}; uint8_t scanLine{24};
vf2d afterImagePos{}; vf2d afterImagePos{};
Oscillator<float>floatOscillator{0.f,8.f,0.5f};
struct STRATEGY{ struct STRATEGY{
static std::string ERR; static std::string ERR;

@ -202,6 +202,7 @@ void MonsterData::InitializeMonsterData(){
if(DATA["Monsters"][MonsterName].HasProperty("ShowBossIndicator"))monster.hasArrowIndicator=DATA["Monsters"][MonsterName]["ShowBossIndicator"].GetBool(); if(DATA["Monsters"][MonsterName].HasProperty("ShowBossIndicator"))monster.hasArrowIndicator=DATA["Monsters"][MonsterName]["ShowBossIndicator"].GetBool();
if(DATA["Monsters"][MonsterName].HasProperty("Unconscious Time"))monster.totalUnconsciousTime=DATA["Monsters"][MonsterName]["Unconscious Time"].GetReal(); if(DATA["Monsters"][MonsterName].HasProperty("Unconscious Time"))monster.totalUnconsciousTime=DATA["Monsters"][MonsterName]["Unconscious Time"].GetReal();
if(DATA["Monsters"][MonsterName].HasProperty("Floating"))monster.floating=DATA["Monsters"][MonsterName]["Floating"].GetBool();
if(DATA["Monsters"][MonsterName].HasProperty("Rectangle Collision")){ if(DATA["Monsters"][MonsterName].HasProperty("Rectangle Collision")){
const datafile&rectData{DATA["Monsters"][MonsterName]["Rectangle Collision"]}; const datafile&rectData{DATA["Monsters"][MonsterName]["Rectangle Collision"]};
@ -481,4 +482,8 @@ const bool MonsterData::FaceTarget()const{
} }
const float MonsterData::UnconsciousTime()const{ const float MonsterData::UnconsciousTime()const{
return totalUnconsciousTime; return totalUnconsciousTime;
}
const bool MonsterData::IsFloating()const{
return floating;
} }

@ -109,6 +109,7 @@ public:
const bool FadeoutWhenStandingBehind()const; const bool FadeoutWhenStandingBehind()const;
const bool FaceTarget()const; const bool FaceTarget()const;
const float UnconsciousTime()const; const float UnconsciousTime()const;
const bool IsFloating()const; //A floating monster oscillates back and forth.
private: private:
std::string name; std::string name;
int hp; int hp;
@ -142,4 +143,5 @@ private:
bool hasArrowIndicator{false}; bool hasArrowIndicator{false};
std::optional<geom2d::rect<float>>rectCollision; std::optional<geom2d::rect<float>>rectCollision;
float totalUnconsciousTime{}; float totalUnconsciousTime{};
bool floating{false};
}; };

@ -399,7 +399,7 @@ void Player::Update(float fElapsedTime){
hpRecoveryTimer=std::max(0.f,hpRecoveryTimer-fElapsedTime); hpRecoveryTimer=std::max(0.f,hpRecoveryTimer-fElapsedTime);
hp6RecoveryTimer=std::max(0.f,hp6RecoveryTimer-fElapsedTime); hp6RecoveryTimer=std::max(0.f,hp6RecoveryTimer-fElapsedTime);
hp4RecoveryTimer=std::max(0.f,hp4RecoveryTimer-fElapsedTime); hp4RecoveryTimer=std::max(0.f,hp4RecoveryTimer-fElapsedTime);
const vf2d previousPosTemp{GetPos()}; //The logic here is that the previous position must be a different value from the actual position and should only change when the position of the player actually changes, this means we must wait for the entire frame to resolve to see if any movement has occurred to update to the "new previous position". This variable is resolved at the bottom of this Update function.
RunTimers(); RunTimers();
PerformHPRecovery(); PerformHPRecovery();
@ -856,10 +856,6 @@ void Player::Update(float fElapsedTime){
} }
} }
#pragma endregion #pragma endregion
if(previousPosTemp!=GetPos()){
previousPos=previousPosTemp; //The logic here is that the previous position must be a different value from the actual position and should only change when the position of the player actually changes, this means we must wait for the entire frame to resolve to see if any movement has occurred to update to the "new previous position". The previous position temp variable is set at the top of this Update function.
}
} }
float Player::GetSwordSwingTimer(){ float Player::GetSwordSwingTimer(){

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1 #define VERSION_MAJOR 1
#define VERSION_MINOR 3 #define VERSION_MINOR 3
#define VERSION_PATCH 0 #define VERSION_PATCH 0
#define VERSION_BUILD 12016 #define VERSION_BUILD 12029
#define stringify(a) stringify_(a) #define stringify(a) stringify_(a)
#define stringify_(a) #a #define stringify_(a) #a

@ -1333,13 +1333,16 @@ MonsterStrategy
# Degrees/sec. Positive is CW, Negative is CCW. # Degrees/sec. Positive is CW, Negative is CCW.
Cannon Spell Insignia Rotation Spd = 50 Cannon Spell Insignia Rotation Spd = 50
Ghost Saber Activation Range = 600
Ghost Saber Cooldown = 4s Ghost Saber Cooldown = 4s
Ghost Saber Lifetime = 4s Ghost Saber Lifetime = 4s
# Distance from the boss the ghost sabers should spin around. # Distance from the boss the ghost sabers should spin around.
Ghost Saber Distance = 56px Ghost Saber Distance = 48px
Ghost Saber Radius = 8px Ghost Saber Radius = 8px
Ghost Saber Damage = 75 Ghost Saber Damage = 75
Ghost Saber Rotation Spd = 180deg/s Ghost Saber Rotation Spd = 210deg/s
Ghost Saber Knockback Amt = 100 Ghost Saber Knockback Amt = 100
# Amount of pixels/sec the ghost saber circle expands outwards.
Ghost Saber Expand Spd = 14px
} }
} }

@ -1827,6 +1827,9 @@ Monsters
# A flag to show an arrow indicator when the boss is off-screen. # A flag to show an arrow indicator when the boss is off-screen.
ShowBossIndicator = True ShowBossIndicator = True
# Adds a floating up-and-down animation. Only a visual effect.
Floating = True
Ignore Collisions = True Ignore Collisions = True
Strategy = Ghost of Pirate Captain Strategy = Ghost of Pirate Captain

Loading…
Cancel
Save