Expand Wizard's Meteor ability configuration parameters. Implemented Summon Comet and Solar Flare enchants. Added incompatibility checks for these two enchants as equipment. Add in unit tests to check for validity of equipping accessories. Release Build 11350.

pull/65/head
sigonasr2 3 months ago
parent fd1a7a6597
commit 050821b1d2
  1. 14
      Adventures in Lestoria Tests/EnchantTests.cpp
  2. 27
      Adventures in Lestoria Tests/ItemTests.cpp
  3. 48
      Adventures in Lestoria/CharacterMenuWindow.cpp
  4. 22
      Adventures in Lestoria/Effect.h
  5. 33
      Adventures in Lestoria/Item.cpp
  6. 1
      Adventures in Lestoria/Item.h
  7. 64
      Adventures in Lestoria/Meteor.cpp
  8. 8
      Adventures in Lestoria/Player.cpp
  9. 6
      Adventures in Lestoria/PulsatingFire.cpp
  10. 2
      Adventures in Lestoria/Version.h
  11. 5
      Adventures in Lestoria/Wizard.cpp
  12. BIN
      Adventures in Lestoria/assets/comet.png
  13. BIN
      Adventures in Lestoria/assets/comet.xcf
  14. 6
      Adventures in Lestoria/assets/config/audio/events.txt
  15. 1
      Adventures in Lestoria/assets/config/gfx/gfx.txt
  16. 4
      Adventures in Lestoria/assets/config/items/ItemEnchants.txt
  17. BIN
      Adventures in Lestoria/assets/gamepack.pak
  18. BIN
      x64/Release/Adventures in Lestoria.exe

@ -1238,5 +1238,19 @@ namespace EnchantTests
Game::Update(0.5f);
Assert::AreEqual(size_t(1),game->GetEffect(EffectType::TRAIL_OF_FIRE).size(),L"Trail of Fire should be generated with the enchant.");
}
TEST_METHOD(SummonCometNoEnchantCheck){
Game::ChangeClass(player,WIZARD);
Assert::AreEqual("Wizard.Ability 3.Cooldown"_F,player->GetAbility3().GetCooldownTime(),L"Meteor cooldown time is normal.");
}
TEST_METHOD(SummonCometEnchantCheck){
Game::ChangeClass(player,WIZARD);
Game::GiveAndEquipEnchantedRing("Summon Comet");
Assert::AreEqual("Wizard.Ability 3.Cooldown"_F+"Summon Comet"_ENC["COOLDOWN REDUCTION"],player->GetAbility3().GetCooldownTime(),L"Meteor cooldown time reduced with the Summon Comet enchant.");
}
TEST_METHOD(SolarFlareEnchantCheck){
Game::ChangeClass(player,WIZARD);
Game::GiveAndEquipEnchantedRing("Solar Flare");
Assert::AreEqual("Wizard.Ability 3.Cooldown"_F+"Solar Flare"_ENC["COOLDOWN INCREASE"],player->GetAbility3().GetCooldownTime(),L"Meteor cooldown time increased with the Solar Flare enchant.");
}
};
}

@ -248,5 +248,32 @@ namespace ItemTests
if(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().has_value())Assert::AreEqual(int(player->GetClass()),int(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().value())); //Validate enchant is only for this class if it's a class-based ability.
}
}
TEST_METHOD(AccessoryAntiCompatibilityCheck){
std::weak_ptr<Item>summonCometRing{Inventory::AddItem("Ring of the Slime King"s)};
summonCometRing.lock()->EnchantItem("Summon Comet");
std::weak_ptr<Item>solarFlareRing{Inventory::AddItem("Ring of the Slime King"s)};
solarFlareRing.lock()->EnchantItem("Solar Flare");
std::weak_ptr<Item>extraRing{Inventory::AddItem("Ring of the Slime King"s)};
std::weak_ptr<Item>extraRing2{Inventory::AddItem("Ring of the Slime King"s)};
Inventory::EquipItem(summonCometRing,EquipSlot::RING1);
Assert::AreEqual(false,Item::SelectedEquipIsDifferent(solarFlareRing,EquipSlot::RING2),L"The game should deny equipping the Solar Flare ring with the Summon Comet ring equipped.");
Inventory::EquipItem(summonCometRing,EquipSlot::RING2);
Assert::AreEqual(false,Item::SelectedEquipIsDifferent(solarFlareRing,EquipSlot::RING1),L"The game should deny equipping the Solar Flare ring with the Summon Comet ring equipped.");
Inventory::EquipItem(solarFlareRing,EquipSlot::RING2);
Assert::AreEqual(false,Item::SelectedEquipIsDifferent(summonCometRing,EquipSlot::RING1),L"The game should deny equipping the Summon Comet ring with the Solar Flare ring equipped.");
Inventory::EquipItem(solarFlareRing,EquipSlot::RING1);
Assert::AreEqual(false,Item::SelectedEquipIsDifferent(summonCometRing,EquipSlot::RING2),L"The game should deny equipping the Summon Comet ring with the Solar Flare ring equipped.");
Inventory::EquipItem(extraRing,EquipSlot::RING1);
Assert::AreEqual(true,Item::SelectedEquipIsDifferent(summonCometRing,EquipSlot::RING2),L"The game should allow equipping the Summon Comet ring when the Solar Flare ring is not present.");
Assert::AreEqual(true,Item::SelectedEquipIsDifferent(solarFlareRing,EquipSlot::RING2),L"The game should allow equipping the Solar Flare ring when the Summon Comet ring is not present.");
Inventory::EquipItem(extraRing,EquipSlot::RING2);
Assert::AreEqual(true,Item::SelectedEquipIsDifferent(summonCometRing,EquipSlot::RING1),L"The game should allow equipping the Summon Comet ring when the Solar Flare ring is not present.");
Assert::AreEqual(true,Item::SelectedEquipIsDifferent(solarFlareRing,EquipSlot::RING1),L"The game should allow equipping the Solar Flare ring when the Summon Comet ring is not present.");
Assert::AreEqual(true,Item::SelectedEquipIsDifferent(extraRing2,EquipSlot::RING1),L"The game should allow equipping of any two normal rings that are not the same ring.");
Assert::AreEqual(false,Item::SelectedEquipIsDifferent(extraRing,EquipSlot::RING1),L"The game should not allow equipping the same ring if it's already equipped.");
Inventory::UnequipItem(EquipSlot::RING2);
Assert::AreEqual(true,Item::SelectedEquipIsDifferent(extraRing,EquipSlot::RING1),L"The game should allow equipping a ring to either blank slot if they're open.");
}
};
}

@ -76,31 +76,9 @@ namespace CharacterMenuWindow{
std::shared_ptr<RowItemDisplay>GenerateItemDisplay(std::shared_ptr<ScrollableWindowComponent>parent,int invIndex,const std::weak_ptr<Item>it){
auto component=parent->ADD("Equip Item "+std::to_string(invIndex),T)(geom2d::rect<float>{{2,2+invIndex*29.f},{120-15,28}},it,
[&](MenuFuncData data){
const auto SelectedEquipIsDifferent=[](std::weak_ptr<RowItemDisplay>comp){
EquipSlot slot=EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE));
std::weak_ptr<Item>currentItem=Inventory::GetEquip(slot);
switch(slot){
case EquipSlot::RING1:
case EquipSlot::RING2:{
std::weak_ptr<Item>otherItem;
if(slot&EquipSlot::RING1)otherItem=Inventory::GetEquip(EquipSlot::RING2);
else
if(slot&EquipSlot::RING2)otherItem=Inventory::GetEquip(EquipSlot::RING1);
return ISBLANK(otherItem)||
(&*comp.lock()->GetItem().lock()!=&*otherItem.lock()&&
&*comp.lock()->GetItem().lock()!=&*currentItem.lock());
}break;
default:{
return &*comp.lock()->GetItem().lock()!=&*currentItem.lock();
}
}
};
std::weak_ptr<T>comp=DYNAMIC_POINTER_CAST<T>(data.component.lock());
if(!comp.expired()){
if(SelectedEquipIsDifferent(comp)){ //If we find that the opposite ring slot is equipped to us, this would be an item swap or the exact same ring, therefore no stat calculations apply.
if(Item::SelectedEquipIsDifferent(comp.lock()->GetItem(),EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE)))){ //If we find that the opposite ring slot is equipped to us, this would be an item swap or the exact same ring, therefore no stat calculations apply.
Inventory::EquipItem(comp.lock()->GetItem(),EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE)));
#pragma region Fully Decked Out Achievement
@ -229,28 +207,6 @@ void Menu::InitializeCharacterMenuWindow(){
equip->SetHoverFunc(
[&](MenuFuncData data){
const auto SelectedEquipIsDifferent=[](std::weak_ptr<RowItemDisplay>comp){
EquipSlot slot=EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE));
std::weak_ptr<Item>currentItem=Inventory::GetEquip(slot);
switch(slot){
case EquipSlot::RING1:
case EquipSlot::RING2:{
std::weak_ptr<Item>otherItem;
if(slot&EquipSlot::RING1)otherItem=Inventory::GetEquip(EquipSlot::RING2);
else
if(slot&EquipSlot::RING2)otherItem=Inventory::GetEquip(EquipSlot::RING1);
return ISBLANK(otherItem)||
(&*comp.lock()->GetItem().lock()!=&*otherItem.lock()&&
&*comp.lock()->GetItem().lock()!=&*currentItem.lock());
}break;
default:{
return &*comp.lock()->GetItem().lock()!=&*currentItem.lock();
}
}
};
if(!data.component.lock()->GetSubcomponentParent().expired())return true;
std::weak_ptr<RowItemDisplay>button=DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component.lock());
if(!button.expired()){
@ -269,7 +225,7 @@ void Menu::InitializeCharacterMenuWindow(){
if(slot&EquipSlot::RING1)otherItem=Inventory::GetEquip(EquipSlot::RING2);
else
if(slot&EquipSlot::RING2)otherItem=Inventory::GetEquip(EquipSlot::RING1);
if(SelectedEquipIsDifferent(button.lock())){ //If we find that the opposite ring slot is equipped to us, this would be an item swap or the exact same ring, therefore no stat calculations apply.
if(Item::SelectedEquipIsDifferent(button.lock()->GetItem(),slot)){ //If we find that the opposite ring slot is equipped to us, this would be an item swap or the exact same ring, therefore no stat calculations apply.
Inventory::EquipItem(buttonItem,slot);
for(int counter=0;const CharacterMenuWindow::AttributeData&attribute:CharacterMenuWindow::displayAttrs){
std::weak_ptr<StatLabel>statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute.attrName).Name())+" Label");

@ -88,26 +88,42 @@ protected:
float original_fadeOutTime;
float original_fadeInTime{};
EffectType type{EffectType::NONE};
private:
Animate2D::Animation<std::string>animation;
Animate2D::AnimationState internal_animState;
private:
bool upperLevel=false;
double aliveTime{};
};
struct Meteor:Effect{
Meteor(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);
enum MeteorSetting{
METEOR,
COMET,
SOLAR_FLARE,
};
Meteor(const vf2d pos,const float lifetime,const bool upperLevel,const MeteorSetting setting,const float fadeout=0.0f,const vf2d spd={},const Pixel col=WHITE,const float rotation=0,const float rotationSpd=0,const bool additiveBlending=false);
float startLifetime=0;
bool shakeField=false;
bool Update(float fElapsedTime)override;
void Draw(const Pixel blendCol)const override;
private:
int meteorImpactParticles{};
float randomColorTintR{};
float randomColorTintG{};
float randomColorTintB{};
float meteorRadius{};
float fallSpdMult{1.f};
float damageMult{1.f};
float fireRingLifetime{};
std::string meteorCrashSFX{};
};
struct PulsatingFire:Effect{
PulsatingFire(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);
PulsatingFire(vf2d pos,float lifetime,const float radius,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);
std::vector<float>pulsatingFireValues;
float lastParticleTimer=0;
float lastDamageTimer=0;
float radius;
bool Update(float fElapsedTime)override;
void Draw(const Pixel blendCol)const override;
};

@ -1461,4 +1461,37 @@ const Pixel Item::GetDisplayNameColor()const{
Pixel col{WHITE};
if(HasEnchant())col=enchant.value().DisplayCol();
return col;
}
const bool Item::SelectedEquipIsDifferent(const std::weak_ptr<Item>equipAttemptingToEquip,const EquipSlot slotToEquipTo){
std::weak_ptr<Item>currentItem=Inventory::GetEquip(slotToEquipTo);
switch(slotToEquipTo){
case EquipSlot::RING1:
case EquipSlot::RING2:{
std::weak_ptr<Item>otherItem;
if(slotToEquipTo&EquipSlot::RING1)otherItem=Inventory::GetEquip(EquipSlot::RING2);
else
if(slotToEquipTo&EquipSlot::RING2)otherItem=Inventory::GetEquip(EquipSlot::RING1);
const auto EquipIsNotSameAsOppositeRingSlot=[&equip=equipAttemptingToEquip,&otherItem](){
return &*equip.lock()!=&*otherItem.lock();
};
const auto EquipIsNotSameAsCurrentRingSlot=[&equip=equipAttemptingToEquip,&currentItem](){
return &*equip.lock()!=&*currentItem.lock();
};
const auto OneOfBothRingsDoesNotHaveEnchant=[&equip=equipAttemptingToEquip,&otherItem](){
return !equip.lock()->HasEnchant()||!otherItem.lock()->HasEnchant();
};
const auto BothRingsAreCompatible=[&equip=equipAttemptingToEquip,&otherItem](){
return !(equip.lock()->GetEnchant().value().Name()=="Summon Comet"&&otherItem.lock()->GetEnchant().value().Name()=="Solar Flare")&&!(equip.lock()->GetEnchant().value().Name()=="Solar Flare"&&otherItem.lock()->GetEnchant().value().Name()=="Summon Comet");
};
return (ISBLANK(otherItem)&&(OneOfBothRingsDoesNotHaveEnchant()||BothRingsAreCompatible())||
EquipIsNotSameAsOppositeRingSlot()&&
EquipIsNotSameAsCurrentRingSlot()&&
(OneOfBothRingsDoesNotHaveEnchant()||BothRingsAreCompatible()));
}break;
default:{
return &*equipAttemptingToEquip.lock()!=&*currentItem.lock();
}
}
}

@ -264,6 +264,7 @@ public:
friend const bool operator==(const IT&lhs,std::weak_ptr<Item>rhs){return operator==(rhs,lhs);};
friend const bool operator==(const IT&lhs,std::shared_ptr<Item>rhs){return operator==(rhs,lhs);};
const Pixel GetDisplayNameColor()const;
static const bool SelectedEquipIsDifferent(const std::weak_ptr<Item>equipAttemptingToEquip,const EquipSlot slotToEquipTo);
};
class Inventory{

@ -46,26 +46,66 @@ INCLUDE_game
INCLUDE_MONSTER_LIST
INCLUDE_GFX
Meteor::Meteor(vf2d pos, float lifetime, std::string imgFile, bool upperLevel, vf2d size, float fadeout, vf2d spd, Pixel col, float rotation, float rotationSpd, bool additiveBlending)
:Effect(pos,lifetime,imgFile,upperLevel,size,fadeout,spd,col,rotation,rotationSpd,additiveBlending),startLifetime(lifetime){
Meteor::Meteor(const vf2d pos,const float lifetime,const bool upperLevel,const MeteorSetting setting,const float fadeout,const vf2d spd,const Pixel col,const float rotation,const float rotationSpd,const bool additiveBlending)
:Effect(pos,lifetime,setting==COMET?"comet.png":"meteor.png",upperLevel,{},fadeout,spd,col,rotation,rotationSpd,additiveBlending),startLifetime(lifetime){
switch(setting){
case METEOR:{
meteorImpactParticles="Wizard.Ability 3.MeteorImpactParticles"_I;
randomColorTintR=255;
randomColorTintG=256*(1-util::random("Wizard.Ability 3.MeteorImpactParticleColorGVariance"_F))*(1-util::random("Wizard.Ability 3.MeteorImpactParticleColorGVariance"_F));
randomColorTintB="Wizard.Ability 3.MeteorImpactParticleColorBlueRange"_FRange;
meteorRadius="Wizard.Ability 3.MeteorRadius"_F/100*24;
fallSpdMult=1.f;
size=vf2d{meteorRadius/96,meteorRadius/96};
damageMult="Wizard.Ability 3.MeteorDamageMult"_F;
fireRingLifetime="Wizard.Ability 3.FireRingLifetime"_F;
meteorCrashSFX="Wizard Meteor";
}break;
case COMET:{
meteorImpactParticles="Wizard.Ability 3.MeteorImpactParticles"_I/2;
randomColorTintR=0.f;
randomColorTintG=256*(1-util::random("Wizard.Ability 3.MeteorImpactParticleColorGVariance"_F))*(1-util::random("Wizard.Ability 3.MeteorImpactParticleColorGVariance"_F));
randomColorTintB="Wizard.Ability 3.MeteorImpactParticleColorBlueRange"_FRange*2; //0-254
meteorRadius="Wizard.Ability 3.MeteorRadius"_F/100*24/2/1.5f;
fallSpdMult="Summon Comet"_ENC["FALL SPEED MULT"];
size=vf2d{meteorRadius/96*1.5f,meteorRadius/96*1.5f};
damageMult="Wizard.Ability 3.MeteorDamageMult"_F;
damageMult*=1.f-"Summon Comet"_ENC["DAMAGE REDUCTION PCT"]/100.f;
fireRingLifetime="Wizard.Ability 3.FireRingLifetime"_F;
meteorCrashSFX="Wizard Comet";
}break;
case SOLAR_FLARE:{
meteorImpactParticles="Wizard.Ability 3.MeteorImpactParticles"_I*4;
randomColorTintR=255;
randomColorTintG=256*(1-util::random("Wizard.Ability 3.MeteorImpactParticleColorGVariance"_F))*(1-util::random("Wizard.Ability 3.MeteorImpactParticleColorGVariance"_F));
randomColorTintB="Wizard.Ability 3.MeteorImpactParticleColorBlueRange"_FRange;
meteorRadius="Wizard.Ability 3.MeteorRadius"_F/100*24*2;
fallSpdMult=1.f;
size=vf2d{meteorRadius/96,meteorRadius/96};
damageMult="Solar Flare"_ENC["METEOR ATTACK"]/100.f;
fireRingLifetime="Wizard.Ability 3.FireRingLifetime"_F*2;
meteorCrashSFX="Wizard Meteor";
this->col=RED;
}break;
}
this->lifetime/=fallSpdMult;
startLifetime=this->lifetime;
}
bool Meteor::Update(float fElapsedTime){
if(lifetime<=0&&!shakeField){
shakeField=true;
game->SetupWorldShake("Wizard.Ability 3.WorldShakeTime"_F);
vf2d meteorOffset=pos+vf2d{lifetime*"Wizard.Ability 3.MeteorXMovementMult"_I,lifetime*"Wizard.Ability 3.MeteorYMovementMult"_I}*"Wizard.Ability 3.MeteorStartingDist"_F-vf2d{0,"Wizard.Ability 3.MeteorRadius"_F/100*12};
for(int i=0;i<"Wizard.Ability 3.MeteorImpactParticles"_I;i++){
vf2d meteorOffset=pos+vf2d{lifetime*"Wizard.Ability 3.MeteorXMovementMult"_I,lifetime*"Wizard.Ability 3.MeteorYMovementMult"_I}*"Wizard.Ability 3.MeteorStartingDist"_F/fallSpdMult-vf2d{0,meteorRadius/2};
for(int i=0;i<meteorImpactParticles;i++){
float randomAngle="Wizard.Ability 3.MeteorImpactParticleAngleRange"_FRange;
float randomRange=100*size.x*(1-util::random("Wizard.Ability 3.MeteorImpactParticleRandomVariance"_F))*(1-util::random("Wizard.Ability 3.MeteorImpactParticleRandomVariance"_F));
float randomColorTintG=256*(1-util::random("Wizard.Ability 3.MeteorImpactParticleColorGVariance"_F))*(1-util::random("Wizard.Ability 3.MeteorImpactParticleColorGVariance"_F));
float randomColorTintB="Wizard.Ability 3.MeteorImpactParticleColorBlueRange"_FRange;
vf2d effectPos=vf2d{cos(randomAngle),sin(randomAngle)}*randomRange+meteorOffset;
game->AddEffect(std::make_unique<Effect>(effectPos,0,"circle.png",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(randomColorTintB),uint8_t("Wizard.Ability 3.MeteorImpactParticleAlphaRange"_FRange)},0,0,true),effectPos.y<meteorOffset.y);
game->AddEffect(std::make_unique<Effect>(effectPos,0,"circle.png",OnUpperLevel(),vf2d{util::random(2)+1,util::random(3)+1},util::random(3)+1,vf2d{util::random(10)-5,-util::random(20)-5},Pixel{uint8_t(randomColorTintR),uint8_t(randomColorTintG),uint8_t(randomColorTintB),uint8_t("Wizard.Ability 3.MeteorImpactParticleAlphaRange"_FRange)},0,0,true),effectPos.y<meteorOffset.y);
}
game->Hurt(pos,"Wizard.Ability 3.MeteorRadius"_F/100*24,int(game->GetPlayer()->GetAttack()*"Wizard.Ability 3.MeteorDamageMult"_F),OnUpperLevel(),0,HurtType::MONSTER,HurtFlag::PLAYER_ABILITY);
game->AddEffect(std::make_unique<PulsatingFire>(pos,"Wizard.Ability 3.FireRingLifetime"_F,"fire_ring1.png",OnUpperLevel(),vf2d{"Wizard.Ability 3.MeteorRadius"_F/100*2,"Wizard.Ability 3.MeteorRadius"_F/100*2},"Wizard.Ability 3.FireRingFadeoutTime"_F),true);
SoundEffect::PlaySFX("Wizard Meteor",pos);
game->Hurt(pos,meteorRadius,int(game->GetPlayer()->GetAttack()*damageMult),OnUpperLevel(),0,HurtType::MONSTER,HurtFlag::PLAYER_ABILITY);
game->AddEffect(std::make_unique<PulsatingFire>(pos,fireRingLifetime,meteorRadius,"fire_ring1.png",OnUpperLevel(),vf2d{meteorRadius/12,meteorRadius/12},"Wizard.Ability 3.FireRingFadeoutTime"_F),true);
SoundEffect::PlaySFX(meteorCrashSFX,pos);
}
return Effect::Update(fElapsedTime);
}
@ -73,11 +113,11 @@ bool Meteor::Update(float fElapsedTime){
void Meteor::Draw(const Pixel blendCol)const{
if(lifetime>0){
vf2d scale=vf2d{192,64}/3.f*(startLifetime+1-lifetime)*0.25*size;
vf2d meteorOffset=vf2d{lifetime*"Wizard.Ability 3.MeteorXMovementMult"_I,0}*"Wizard.Ability 3.MeteorShadowStartingDist"_F;
vf2d meteorOffset=vf2d{lifetime*"Wizard.Ability 3.MeteorXMovementMult"_I,0}*"Wizard.Ability 3.MeteorShadowStartingDist"_F/fallSpdMult;
vf2d centerPoint=pos-vf2d{GFX["circle.png"].Sprite()->width*scale.x/2,GFX["circle.png"].Sprite()->height*scale.y/2};
game->view.DrawDecal(centerPoint+meteorOffset,GFX["circle.png"].Decal(),scale,{0,0,0,192});
}
vf2d meteorOffset=pos+vf2d{lifetime*"Wizard.Ability 3.MeteorXMovementMult"_I,lifetime*"Wizard.Ability 3.MeteorYMovementMult"_I}*"Wizard.Ability 3.MeteorStartingDist"_F-vf2d{0,GetFrame().GetSourceRect().size.y/4.f}*size;
vf2d meteorOffset=pos+vf2d{lifetime*"Wizard.Ability 3.MeteorXMovementMult"_I,lifetime*"Wizard.Ability 3.MeteorYMovementMult"_I}*"Wizard.Ability 3.MeteorStartingDist"_F/fallSpdMult-vf2d{0,GetFrame().GetSourceRect().size.y/4.f}*size;
if(lifetime<=0){
meteorOffset=pos-vf2d{0,GetFrame().GetSourceRect().size.y/4.f}*size;
}

@ -1537,6 +1537,14 @@ void Player::RecalculateEquipStats(){
if(GetClass()&WIZARD){
if(HasEnchant("Blink Portal"))GetRightClickAbility().COOLDOWN_TIME="Blink Portal"_ENC["COOLDOWN"];
if(HasEnchant("Summon Comet")){
GetAbility3().precastInfo.castTime="Summon Comet"_ENC["METEOR CAST TIME"];
GetAbility3().COOLDOWN_TIME+="Summon Comet"_ENC["COOLDOWN REDUCTION"]; //This is not a typo, we add because the cooldown reduction in the config is NEGATIVE!
}
if(HasEnchant("Solar Flare")){
GetAbility3().precastInfo.castTime="Solar Flare"_ENC["METEOR CAST TIME"];
GetAbility3().COOLDOWN_TIME+="Solar Flare"_ENC["COOLDOWN INCREASE"];
}
}
for(const std::reference_wrapper<Ability>&a:GetAbilities()){

@ -46,8 +46,8 @@ INCLUDE_game
INCLUDE_ANIMATION_DATA
INCLUDE_MONSTER_LIST
PulsatingFire::PulsatingFire(vf2d pos, float lifetime, std::string 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){
PulsatingFire::PulsatingFire(vf2d pos,float lifetime,const float radius,std::string animation,bool upperLevel,vf2d size,float fadeout,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:radius(radius),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));
}
@ -69,7 +69,7 @@ bool PulsatingFire::Update(float fElapsedTime){
}
if(lastDamageTimer<=0){
lastDamageTimer="Wizard.Ability 3.FireRingDamageFreq"_F-0.01f;
game->Hurt(pos,"Wizard.Ability 3.MeteorRadius"_F/100*24,int(game->GetPlayer()->GetAttack()*"Wizard.Ability 3.FireRingDamageMult"_F),OnUpperLevel(),0,HurtType::MONSTER);
game->Hurt(pos,radius,int(game->GetPlayer()->GetAttack()*"Wizard.Ability 3.FireRingDamageMult"_F),OnUpperLevel(),0,HurtType::MONSTER);
SoundEffect::PlaySFX("Wizard Meteor Flames",pos);
}
return Effect::Update(fElapsedTime);

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1
#define VERSION_MINOR 2
#define VERSION_PATCH 5
#define VERSION_BUILD 11332
#define VERSION_BUILD 11350
#define stringify(a) stringify_(a)
#define stringify_(a) #a

@ -200,7 +200,10 @@ void Wizard::InitializeClassAbilities(){
#pragma region Wizard Ability 3 (Meteor)
Wizard::ability3.action=
[](Player*p,vf2d pos={}){
game->AddEffect(std::make_unique<Meteor>(pos,3,"meteor.png",p->OnUpperLevel(),vf2d{"Wizard.Ability 3.MeteorRadius"_F/100/4,"Wizard.Ability 3.MeteorRadius"_F/100/4},"Wizard.Ability 3.MeteorFadeoutTime"_F));
Meteor::MeteorSetting meteorType{Meteor::METEOR};
if(p->HasEnchant("Summon Comet"))meteorType=Meteor::COMET;
else if(p->HasEnchant("Solar Flare"))meteorType=Meteor::SOLAR_FLARE;
game->AddEffect(std::make_unique<Meteor>(pos,3,p->OnUpperLevel(),meteorType,"Wizard.Ability 3.MeteorFadeoutTime"_F));
return true;
};
#pragma endregion

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

@ -495,6 +495,12 @@ Events
File[0] = wizard_auto1.ogg, 60%
File[1] = wizard_auto2.ogg, 60%
}
Wizard Comet
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = wizard_meteor.ogg, 100%, 140%, 160%
}
Wizard Fire Bolt Shoot
{
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)

@ -128,6 +128,7 @@ Images
GFX_BlackHole = blackhole.png
GFX_Portal = portal.png
GFX_Flames = FlamesTexture.png
GFX_Comet = comet.png
GFX_Thief_Sheet = nico-thief.png
GFX_Trapper_Sheet = nico-trapper.png

@ -529,13 +529,13 @@ Item Enchants
}
Summon Comet
{
Description = "Meteor cast time reduced to {METEOR CAST TIME} seconds. Meteor attack falls twice as quick. Damage is reduced by {DAMAGE REDUCTION PCT}% and -{COOLDOWN REDUCTION}s cooldown time."
Description = "Meteor cast time reduced to {METEOR CAST TIME} seconds. Meteor attack falls twice as quick. Damage is reduced by {DAMAGE REDUCTION PCT}% and {COOLDOWN REDUCTION}s cooldown time."
Affects = Ability 3
METEOR CAST TIME = 0.5s
FALL SPEED MULT = 2.0x
DAMAGE REDUCTION PCT = 10%
COOLDOWN REDUCTION = 8s
COOLDOWN REDUCTION = -8s
# Stat, Lowest, Highest Value
# Stat Modifier[0] = ..., 0, 0

Loading…
Cancel
Save