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

master
sigonasr2 6 days 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);
}
float animationSpd=0.f;
if(player->GetPreviousPos()!=player->GetPos()){
player->previousPos=player->GetPos();
}
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)){
auto GetPlayerStaircaseDirection=[&](){

@ -445,7 +445,7 @@ private:
};
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;
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()!!
@ -453,10 +453,11 @@ struct GhostSaber:public Bullet{
private:
const std::weak_ptr<Monster>attachedMonster;
const float rotSpd;
const float distFromTarget;
float distFromTarget;
float rot;
const float knockbackAmt;
float particleTimer{};
float aliveTime{};
float expandSpd{};
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](){
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)};
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.
@ -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){
m.F(A::SHRAPNEL_SHOT_FALL_TIMER)-=fElapsedTime;
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)){
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");
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++;
}
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});
SETPHASE(NORMAL);
}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()));
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")};
LOG(std::format("Range/Angle: {}",vf2d{range,angle}.str()));
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;
}break;

@ -38,12 +38,13 @@ All rights reserved.
#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)
: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){}
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),expandSpd(expandSpd){}
void GhostSaber::Update(float fElapsedTime){
alphaOscillator.Update(fElapsedTime);
particleTimer-=fElapsedTime;
aliveTime-=fElapsedTime;
distFromTarget+=expandSpd*fElapsedTime;
if(particleTimer<=0.f){
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));

@ -282,6 +282,7 @@ void Monster::Update(const float fElapsedTime){
specialMarkApplicationTimer=std::max(0.f,specialMarkApplicationTimer-fElapsedTime);
lastFacingDirectionChange+=fElapsedTime;
timeSpentAlive+=fElapsedTime;
floatOscillator.Update(fElapsedTime);
if(IsUnconscious()){
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)))};
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;

@ -49,6 +49,7 @@ All rights reserved.
#include "MonsterData.h"
#include "Direction.h"
#include "HurtDamageInfo.h"
#include "Oscillator.h"
INCLUDE_ITEM_DATA
INCLUDE_MONSTER_DATA
@ -344,6 +345,7 @@ private:
const float TIME_BETWEEN_LINE_REMOVALS{0.025f};
uint8_t scanLine{24};
vf2d afterImagePos{};
Oscillator<float>floatOscillator{0.f,8.f,0.5f};
struct STRATEGY{
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("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")){
const datafile&rectData{DATA["Monsters"][MonsterName]["Rectangle Collision"]};
@ -481,4 +482,8 @@ const bool MonsterData::FaceTarget()const{
}
const float MonsterData::UnconsciousTime()const{
return totalUnconsciousTime;
}
const bool MonsterData::IsFloating()const{
return floating;
}

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

@ -399,7 +399,7 @@ void Player::Update(float fElapsedTime){
hpRecoveryTimer=std::max(0.f,hpRecoveryTimer-fElapsedTime);
hp6RecoveryTimer=std::max(0.f,hp6RecoveryTimer-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();
PerformHPRecovery();
@ -856,10 +856,6 @@ void Player::Update(float fElapsedTime){
}
}
#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(){

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

@ -1333,13 +1333,16 @@ MonsterStrategy
# Degrees/sec. Positive is CW, Negative is CCW.
Cannon Spell Insignia Rotation Spd = 50
Ghost Saber Activation Range = 600
Ghost Saber Cooldown = 4s
Ghost Saber Lifetime = 4s
# Distance from the boss the ghost sabers should spin around.
Ghost Saber Distance = 56px
Ghost Saber Distance = 48px
Ghost Saber Radius = 8px
Ghost Saber Damage = 75
Ghost Saber Rotation Spd = 180deg/s
Ghost Saber Rotation Spd = 210deg/s
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.
ShowBossIndicator = True
# Adds a floating up-and-down animation. Only a visual effect.
Floating = True
Ignore Collisions = True
Strategy = Ghost of Pirate Captain

Loading…
Cancel
Save