Merge pull request 'GhostOfPirateCaptain' (#95) from GhostOfPirateCaptain into master
All checks were successful
Emscripten Build / Build_and_Deploy_Web_Build (push) Successful in 7m16s
All checks were successful
Emscripten Build / Build_and_Deploy_Web_Build (push) Successful in 7m16s
Reviewed-on: #95
This commit is contained in:
commit
0230bb6b22
@ -47,6 +47,7 @@ using A=Attribute;
|
||||
|
||||
INCLUDE_game
|
||||
INCLUDE_MONSTER_LIST
|
||||
INCLUDE_BULLET_LIST
|
||||
|
||||
void Monster::STRATEGY::GHOST_OF_PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std::string strategy){
|
||||
enum PhaseName{
|
||||
@ -56,6 +57,7 @@ void Monster::STRATEGY::GHOST_OF_PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std
|
||||
GHOSTSABER_SLASH=999,
|
||||
TOSS_COIN,
|
||||
HIDING,
|
||||
FINAL, //The final phase of the fight.
|
||||
};
|
||||
|
||||
enum CannonShotType{
|
||||
@ -127,6 +129,7 @@ void Monster::STRATEGY::GHOST_OF_PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std
|
||||
case NORMAL:{
|
||||
m.F(A::CANNON_TIMER)+=fElapsedTime;
|
||||
m.F(A::SHRAPNEL_CANNON_TIMER)+=fElapsedTime;
|
||||
m.SetStrategyOnHitFunction({});
|
||||
const int phase{std::any_cast<int>(m.VEC(A::CANNON_PHASES)[m.I(A::CANNON_PHASE)])};
|
||||
switch(phase){
|
||||
case CANNON_SHOT:{//Normal Cannon Shot. Takes on one of five varieties.
|
||||
@ -154,10 +157,8 @@ void Monster::STRATEGY::GHOST_OF_PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std
|
||||
if(m.I(A::CANNON_SHOT_COUNT)%2==0)CreateBullet(FallingBullet)("cannonball.png",game->GetPlayer()->GetPos(),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;
|
||||
case PREDICTION:{
|
||||
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;
|
||||
@ -183,7 +184,8 @@ void Monster::STRATEGY::GHOST_OF_PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std
|
||||
m.I(A::CURSE_THRESHOLD_ARRAY_IND)++;
|
||||
m.F(A::TOSS_COIN_WAIT_TIMER)=ConfigFloat("Coin Toss Pause Time");
|
||||
m.V(A::TOSS_COIN_TARGET)=game->GetPlayer()->GetPos();
|
||||
game->AddEffect(std::make_unique<FlipCoinEffect>(Oscillator<vf2d>{m.GetPos(),m.V(A::TOSS_COIN_TARGET),1.f/m.F(A::TOSS_COIN_WAIT_TIMER)/2.f},ConfigFloat("Coin Toss Rise Amount"),m.F(A::TOSS_COIN_WAIT_TIMER),"coin.png",m.OnUpperLevel(),3.f));
|
||||
const bool OnLastCursePhase{Config("Curse Thresholds").GetValueCount()==m.I(A::CURSE_THRESHOLD_ARRAY_IND)};
|
||||
if(!OnLastCursePhase)game->AddEffect(std::make_unique<FlipCoinEffect>(Oscillator<vf2d>{m.GetPos(),m.V(A::TOSS_COIN_TARGET),1.f/m.F(A::TOSS_COIN_WAIT_TIMER)/2.f},ConfigFloat("Coin Toss Rise Amount"),m.F(A::TOSS_COIN_WAIT_TIMER),"coin.png",m.OnUpperLevel(),3.f));
|
||||
|
||||
#pragma region Determine a hiding spot
|
||||
const auto&hidingSpots{game->GetZones().at("Hiding Spot")};
|
||||
@ -234,19 +236,36 @@ void Monster::STRATEGY::GHOST_OF_PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std
|
||||
m.F(A::TOSS_COIN_WAIT_TIMER)-=fElapsedTime;
|
||||
if(m.F(A::TOSS_COIN_WAIT_TIMER)<=0.f){
|
||||
const float curseDmgPctOverTime{ConfigFloat("Curse Damage")};
|
||||
game->GetPlayer()->AddBuff(BuffType::PIRATE_GHOST_CAPTAIN_PRECURSE,ConfigFloat("Curse Damage Wait Time"),ceil(game->GetPlayer()->GetMaxHealth()*ConfigFloat("Curse Damage")/100.f)+1,[curseDmgPctOverTime](Player*attachedTarget,Buff&b){
|
||||
attachedTarget->AddBuff(BuffType::PIRATE_GHOST_CAPTAIN_CURSE_DOT,BuffRestorationType::OVER_TIME,BuffOverTimeType::HP_PCT_DAMAGE_OVER_TIME,INFINITY,curseDmgPctOverTime,1.f);
|
||||
});
|
||||
game->SpawnMonster(m.V(A::TOSS_COIN_TARGET),MONSTER_DATA["Pirate's Coin"],m.OnUpperLevel());
|
||||
const bool OnLastCursePhase{Config("Curse Thresholds").GetValueCount()==m.I(A::CURSE_THRESHOLD_ARRAY_IND)};
|
||||
if(OnLastCursePhase){ //Permanent Curse for the rest of the fight.
|
||||
game->GetPlayer()->AddBuff(BuffType::PIRATE_GHOST_CAPTAIN_CURSE_DOT,BuffRestorationType::OVER_TIME,BuffOverTimeType::HP_PCT_DAMAGE_OVER_TIME,INFINITY,curseDmgPctOverTime,1.f);
|
||||
}else{
|
||||
game->GetPlayer()->AddBuff(BuffType::PIRATE_GHOST_CAPTAIN_PRECURSE,ConfigFloat("Curse Damage Wait Time"),ceil(game->GetPlayer()->GetMaxHealth()*ConfigFloat("Curse Damage")/100.f)+1,[curseDmgPctOverTime](Player*attachedTarget,Buff&b){
|
||||
attachedTarget->AddBuff(BuffType::PIRATE_GHOST_CAPTAIN_CURSE_DOT,BuffRestorationType::OVER_TIME,BuffOverTimeType::HP_PCT_DAMAGE_OVER_TIME,INFINITY,curseDmgPctOverTime,1.f);
|
||||
});
|
||||
game->SpawnMonster(m.V(A::TOSS_COIN_TARGET),MONSTER_DATA["Pirate's Coin"],m.OnUpperLevel());
|
||||
}
|
||||
m.SetupAfterImage();
|
||||
m.afterImagePos=m.GetPos();
|
||||
m.SetPos(m.V(A::HIDING_POS));
|
||||
m.SetVelocity({});
|
||||
m.arrowIndicator=false; //While the boss is hiding, the indicator will not show up.
|
||||
m.SetStrategyOnHitFunction([&m](const HurtDamageInfo damageData,Monster&monster,const StrategyName&strategyName)->void{
|
||||
m.SetPhase(strategyName,NORMAL);
|
||||
m.arrowIndicator=true;
|
||||
m.SetStrategyOnHitFunction({});
|
||||
m.SetStrategyOnHitFunction([OnLastCursePhase,&m,strategy](const HurtDamageInfo damageData,Monster&monster,const StrategyName&strategyName)->void{
|
||||
monster.SetPhase(strategyName,OnLastCursePhase?FINAL:NORMAL);
|
||||
monster.arrowIndicator=true;
|
||||
if(OnLastCursePhase){
|
||||
const std::string_view PIRATES_TREASURE{"Pirate's Treasure"};
|
||||
for(std::shared_ptr<Monster>&m:MONSTER_LIST
|
||||
|std::views::filter([&PIRATES_TREASURE](std::shared_ptr<Monster>&m){return m->GetStrategyName()==PIRATES_TREASURE;})){
|
||||
m->SetPhase("Pirate's Treasure",1); //HARDCODED to the LOCKED phase for the Pirate's Treasure
|
||||
}
|
||||
for(int i:std::ranges::iota_view(0,ConfigInt("Final Ghost Saber Count"))){
|
||||
const float playerToMonsterAngle{util::pointTo(game->GetPlayer()->GetPos(),m.GetPos()).polar().y};
|
||||
const float rotationMult=((i%2==0)?1.f:-1.f);
|
||||
if(i>=ConfigInt("Final Ghost Saber Count")/2)CreateBullet(GhostSaber)(m.GetPos(),m.GetWeakPointer(),INFINITE,ConfigFloat("Ghost Saber Distance")+i*ConfigFloat("Final Ghost Saber Separation Amount"),ConfigFloat("Ghost Saber Knockback Amt"),playerToMonsterAngle,ConfigFloat("Ghost Saber Radius"),0.f,ConfigInt("Ghost Saber Damage"),m.OnUpperLevel(),util::degToRad(ConfigFloat("Ghost Saber Rotation Spd")*-rotationMult/sqrtf(i+util::random_range(1.f,i))))EndBullet;
|
||||
CreateBullet(GhostSaber)(m.GetPos(),m.GetWeakPointer(),INFINITE,ConfigFloat("Ghost Saber Distance")+i*ConfigFloat("Final Ghost Saber Separation Amount"),ConfigFloat("Ghost Saber Knockback Amt"),playerToMonsterAngle,ConfigFloat("Ghost Saber Radius"),0.f,ConfigInt("Ghost Saber Damage"),m.OnUpperLevel(),util::degToRad(ConfigFloat("Final Ghost Saber Rotation Spd")*rotationMult/sqrtf(i)))EndBullet;
|
||||
}
|
||||
}
|
||||
});
|
||||
SETPHASE(HIDING);
|
||||
}
|
||||
@ -277,10 +296,8 @@ void Monster::STRATEGY::GHOST_OF_PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std
|
||||
if(m.I(A::CANNON_SHOT_COUNT)%2==0)CreateBullet(FallingBullet)("cannonball.png",game->GetPlayer()->GetPos(),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;
|
||||
case PREDICTION:{
|
||||
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;
|
||||
@ -294,5 +311,62 @@ void Monster::STRATEGY::GHOST_OF_PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std
|
||||
m.F(A::SHRAPNEL_CANNON_TIMER)=0.f;
|
||||
}
|
||||
}break;
|
||||
case FINAL:{
|
||||
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
|
||||
m.target=ConfigVec("Final Standing Spot");
|
||||
m.PerformAnimation("SLASH",Direction::SOUTH);
|
||||
m.SetStrategyOnHitFunction({});
|
||||
m.SetStrategyDeathFunction([](GameEvent&event,Monster&m,const std::string&strategyName){
|
||||
for(std::unique_ptr<IBullet>&bullet:BULLET_LIST|std::views::filter([](std::unique_ptr<IBullet>&bullet){return !bullet->friendly;}))bullet->lifetime=0.f;
|
||||
const std::string_view PIRATES_TREASURE{"Pirate's Treasure"};
|
||||
for(std::shared_ptr<Monster>&treasure:MONSTER_LIST
|
||||
|std::views::filter([&PIRATES_TREASURE](std::shared_ptr<Monster>&m){return m->GetStrategyName()==PIRATES_TREASURE;})){
|
||||
m.target=treasure->GetPos();
|
||||
}
|
||||
m.B(A::IGNORE_DEFAULT_ANIMATIONS)=true;
|
||||
RUN_TOWARDS(m,game->GetElapsedTime(),"Run Towards");
|
||||
m.PerformAnimation("DEATH");
|
||||
return true;
|
||||
});
|
||||
m.F(A::CANNON_TIMER)+=fElapsedTime;
|
||||
if(m.F(A::CANNON_TIMER)>=ConfigFloat("Final Cannon Shot Delay")){
|
||||
switch(m.I(A::CANNON_SHOT_TYPE)){
|
||||
case BOMBARDMENT:{
|
||||
const float randomAng{util::random_range(0,2*PI)};
|
||||
const float range{util::random_range(0,ConfigPixels("Bombardment Max Distance"))};
|
||||
const vf2d targetPos{game->GetPlayer()->GetPos()+vf2d{range,randomAng}.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;
|
||||
case PRECISE_BOMBARDMENT:{
|
||||
const float randomAng{util::random_range(0,2*PI)};
|
||||
const float range{util::random_range(0,ConfigPixels("Precise Bombardment Max Distance"))};
|
||||
const vf2d targetPos{game->GetPlayer()->GetPos()+vf2d{range,randomAng}.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;
|
||||
case LINE:{
|
||||
//Draw a line from one side of the screen to the other, drawing through the middle.
|
||||
if(m.I(A::CANNON_SHOT_COUNT)==0)m.F(A::LINE_SHOT_ANG)=util::random_range(0,2*PI);
|
||||
const vf2d targetPos{geom2d::line{game->GetPlayer()->GetPos()+vf2d{float(game->ScreenHeight()),m.F(A::LINE_SHOT_ANG)}.cart(),game->GetPlayer()->GetPos()+vf2d{float(game->ScreenHeight()),m.F(A::LINE_SHOT_ANG)+PI}.cart()}.upoint(float(m.I(A::CANNON_SHOT_COUNT))/TOTAL_CANNON_SHOTS)};
|
||||
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;
|
||||
case SHARPSHOOTER:{
|
||||
if(m.I(A::CANNON_SHOT_COUNT)%2==0)CreateBullet(FallingBullet)("cannonball.png",game->GetPlayer()->GetPos(),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;
|
||||
case PREDICTION:{
|
||||
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 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;
|
||||
}
|
||||
AdvanceCannonPhase();
|
||||
m.I(A::CANNON_SHOT_COUNT)++;
|
||||
}
|
||||
if(m.F(A::SHRAPNEL_CANNON_TIMER)>=ConfigFloat("Final Shrapnel Timer")){
|
||||
m.I(A::SHRAPNEL_SHOT_COUNT)=ConfigInt("Shrapnel Shot Bullet Count");
|
||||
m.F(A::SHRAPNEL_SHOT_FALL_TIMER)=ConfigFloat("Shrapnel Shot Bullet Separation");
|
||||
m.F(A::SHRAPNEL_CANNON_TIMER)=0.f+util::random(0.5f); // A little randomness to offset the two timers if they happen to occur at once.
|
||||
}
|
||||
}break;
|
||||
}
|
||||
}
|
||||
@ -40,7 +40,7 @@ All rights reserved.
|
||||
#include "Attributable.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 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){}
|
||||
:Bullet(target.lock()->GetPos()+vf2d{distFromTarget,initialRot}.cart(),{},radius,damage,"ghost_dagger.png",upperLevel,true,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;
|
||||
@ -63,7 +63,7 @@ void GhostSaber::Update(float fElapsedTime){
|
||||
};
|
||||
|
||||
BulletDestroyState GhostSaber::PlayerHit(Player*player){
|
||||
player->ApplyIframes(0.2f);
|
||||
player->ApplyIframes(0.8f);
|
||||
player->Knockback(vf2d{knockbackAmt,rot}.cart());
|
||||
if(!attachedMonster.expired()){
|
||||
const int GHOSTSABER_SLASH_PHASEID{999};
|
||||
@ -72,12 +72,14 @@ BulletDestroyState GhostSaber::PlayerHit(Player*player){
|
||||
attachedMonster.lock()->PerformAnimation("SLASHING");
|
||||
attachedMonster.lock()->GetFloat(Attribute::GHOST_SABER_SLASH_ANIMATION_TIMER)=attachedMonster.lock()->GetCurrentAnimation().GetTotalAnimationDuration();
|
||||
}
|
||||
hitList.clear(); //Can keep hitting same target repeatedly
|
||||
return BulletDestroyState::KEEP_ALIVE;
|
||||
}
|
||||
|
||||
BulletDestroyState GhostSaber::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit){
|
||||
monster.ApplyIframes(0.2f);
|
||||
monster.ApplyIframes(0.8f);
|
||||
monster.Knockback(vf2d{knockbackAmt,rot}.cart());
|
||||
hitList.clear(); //Can keep hitting same target repeatedly
|
||||
return BulletDestroyState::KEEP_ALIVE;
|
||||
}
|
||||
|
||||
|
||||
@ -51,5 +51,6 @@ void Monster::STRATEGY::PIRATES_COIN(Monster&m,float fElapsedTime,std::string st
|
||||
m._DealTrueDamage(m.GetHealth(),HurtFlag::NO_DAMAGE_NUMBER);
|
||||
m.SetLifetime(0.f);
|
||||
m.SetSize(0.f,false);
|
||||
game->GetPlayer()->NotificationDisplay(ConfigString("Coin Owned Text"),INFINITE);
|
||||
}
|
||||
}
|
||||
@ -38,13 +38,20 @@ All rights reserved.
|
||||
|
||||
#include "AdventuresInLestoria.h"
|
||||
#include "MonsterStrategyHelpers.h"
|
||||
#include<ranges>
|
||||
#include"ItemDrop.h"
|
||||
|
||||
INCLUDE_game
|
||||
INCLUDE_MONSTER_LIST
|
||||
INCLUDE_ITEM_DATA
|
||||
|
||||
void Monster::STRATEGY::PIRATES_TREASURE(Monster&m,float fElapsedTime,std::string strategy){
|
||||
enum Phase{
|
||||
NORMAL,
|
||||
LOCKED,
|
||||
LOCK = 1,
|
||||
LOCKED = 2, //The locked phase will remain at 1 so that the Ghost Boss can utilize this specific phase number.
|
||||
WAITING= 3,
|
||||
REWARDED = 4,
|
||||
};
|
||||
|
||||
switch(PHASE()){
|
||||
@ -56,11 +63,42 @@ void Monster::STRATEGY::PIRATES_TREASURE(Monster&m,float fElapsedTime,std::strin
|
||||
game->GetPlayer()->RemoveBuff(BuffType::PIRATE_GHOST_CAPTAIN_PRECURSE);
|
||||
game->GetPlayer()->RemoveBuff(BuffType::PIRATE_GHOST_CAPTAIN_CURSE_COIN);
|
||||
game->GetPlayer()->RemoveBuff(BuffType::PIRATE_GHOST_CAPTAIN_CURSE_DOT);
|
||||
game->GetPlayer()->NotificationDisplay("",0);
|
||||
}
|
||||
}break;
|
||||
case LOCK:{
|
||||
m.SetCollisionRadius(m.GetCollisionRadius()+12);
|
||||
SETPHASE(LOCKED);
|
||||
}break;
|
||||
case LOCKED:{
|
||||
m.PerformAnimation("LOCKED");
|
||||
m.SetCollisionRadius(m.GetOriginalCollisionRadius()+12);
|
||||
bool isBossDead{true};
|
||||
for(std::shared_ptr<Monster>monster:MONSTER_LIST|std::views::filter([](std::shared_ptr<Monster>&monster){return monster->isBoss&&monster->GetDisplayName()=="Ghost of Pirate Captain";})){
|
||||
if(monster->IsAlive()){
|
||||
isBossDead=false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(isBossDead){
|
||||
SETPHASE(WAITING);
|
||||
m.SetCollisionRadius(m.GetCollisionRadius()-12);
|
||||
game->GetPlayer()->NotificationDisplay("The Ghost Pirate Captain rewards you handsomely",10);
|
||||
}
|
||||
}break;
|
||||
case WAITING:{
|
||||
const float distToPlayer{util::distance(game->GetPlayer()->GetPos(),m.GetPos())};
|
||||
if(distToPlayer<=ConfigFloat("Open Distance")){
|
||||
m.PerformAnimation("REWARD");
|
||||
for(const int ind:std::ranges::iota_view(size_t(0),Config("Reward Items").GetValueCount())){
|
||||
ItemDrop::SpawnItem(const_cast<ItemInfo*>(&ITEM_DATA.at(ConfigStringArr("Reward Items",ind))),m.GetPos(),m.OnUpperLevel());
|
||||
}
|
||||
SETPHASE(REWARDED);
|
||||
}
|
||||
else m.PerformIdleAnimation();
|
||||
}break;
|
||||
case REWARDED:{
|
||||
m.PerformAnimation("REWARD");
|
||||
m._DealTrueDamage(m.GetHealth(),HurtFlag::NO_DAMAGE_NUMBER);
|
||||
}break;
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,11 @@ For Demo Branch:
|
||||
Cherry pick b715dd994d4921e9be1811ceb7eb84120a5e6455 from MiscFixes
|
||||
Cherry pick f398adcde6bb5f8c3c0581f13f18e73d4417a46c from ConnectionPointFixes
|
||||
Cherry pick fb5a72267c5db89b7333287e12f05b614b71c23b from MiscFixes
|
||||
Cherry pick 6355054d6c8e76c6aa4b18760a293e3d1a020752 from master
|
||||
|
||||
Remove coin toss for final phase
|
||||
Issues with cannon fire in the final phase
|
||||
Rebalance final phase ghost sabers
|
||||
|
||||
DEMO
|
||||
====
|
||||
|
||||
@ -39,7 +39,7 @@ All rights reserved.
|
||||
#define VERSION_MAJOR 1
|
||||
#define VERSION_MINOR 3
|
||||
#define VERSION_PATCH 0
|
||||
#define VERSION_BUILD 12329
|
||||
#define VERSION_BUILD 12364
|
||||
|
||||
#define stringify(a) stringify_(a)
|
||||
#define stringify_(a) #a
|
||||
|
||||
@ -1349,7 +1349,7 @@ MonsterStrategy
|
||||
Ghost Saber Expand Spd = 14px
|
||||
|
||||
# What HP % the boss throws a coin at the player, applying a curse, and hiding from the player.
|
||||
Curse Thresholds = 95%, 40%, 10%
|
||||
Curse Thresholds = 70%, 40%, 10%
|
||||
# How much time before the curse starts dealing damage to the player
|
||||
Curse Damage Wait Time = 10s
|
||||
# How much % damage the curse does to the player every second.
|
||||
@ -1360,10 +1360,23 @@ MonsterStrategy
|
||||
Coin Toss Rise Amount = 72px
|
||||
|
||||
Shrapnel Hiding Shot Delay = 5.0s
|
||||
|
||||
# FINAL PHASE SETTINGS
|
||||
# The coordinates on the map indicating where the boss stands for the final portion of the fight.
|
||||
Final Standing Spot = 888,1104
|
||||
Final Ghost Saber Count = 10
|
||||
Final Ghost Saber Rotation Spd = 75deg/s
|
||||
# Amount of separation between each ghost saber
|
||||
Final Ghost Saber Separation Amount = 32px
|
||||
# How often the cannons fire during the final phase.
|
||||
Final Shrapnel Timer = 5s
|
||||
Final Cannon Shot Delay = 0.5s
|
||||
}
|
||||
Pirate's Treasure
|
||||
{
|
||||
Open Distance = 64px
|
||||
# Can be a comma-separated list for multiple drops.
|
||||
Reward Items = "Captain's Diamond Ring"
|
||||
}
|
||||
Pirate's Coin
|
||||
{
|
||||
@ -1371,6 +1384,9 @@ MonsterStrategy
|
||||
Coin Rise Timer = 2s
|
||||
# Highest Z position the collected coin icon appears at.
|
||||
Coin Collect Rise Amount = 36px
|
||||
|
||||
# The hint text displayed when the player is holding the coin.
|
||||
Coin Owned Text = "This treasure does not belong to you..."
|
||||
}
|
||||
Skeleton Barbarian
|
||||
{
|
||||
|
||||
@ -1850,7 +1850,7 @@ Monsters
|
||||
SLASHING = 4, 0.1, OneShot
|
||||
DEATH = 4, 0.25, OneShot
|
||||
STABBING = 3, 0.1, OneShot
|
||||
SLASH = 1, 0.1, OneShot
|
||||
SLASH = 2, 0.1, OneShot
|
||||
STAB = 1, 0.1, OneShot
|
||||
SHOOTING = 3, 0.2, OneShot
|
||||
SHOOT = 1, 0.1, OneShot
|
||||
@ -1858,8 +1858,11 @@ Monsters
|
||||
DRINK = 2, 0.65, PingPong
|
||||
}
|
||||
|
||||
### NOTE: The Pirate will float over to the Pirate;s Treasuure after the fight and reward the ring through it.
|
||||
### Modify the drop in the Pirate's Treasure monster strategy.
|
||||
# Drop Item Name, Drop Percentage(0-100%), Drop Min Quantity, Drop Max Quantity
|
||||
# DROP[0] = Octopus Ring,100%,1,1
|
||||
|
||||
|
||||
Hurt Sound = Monster Hurt
|
||||
Death Sound = Slime Dead
|
||||
@ -1901,6 +1904,7 @@ Monsters
|
||||
WALK = 1, 1.0, OneShot
|
||||
LOCKED = 1, 1.0, OneShot
|
||||
OPEN = 1, 1.0, OneShot
|
||||
REWARD = 1, 1.0, OneShot
|
||||
}
|
||||
|
||||
# Drop Item Name, Drop Percentage(0-100%), Drop Min Quantity, Drop Max Quantity
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user