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); 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."); 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. 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){ 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, 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){ [&](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()); std::weak_ptr<T>comp=DYNAMIC_POINTER_CAST<T>(data.component.lock());
if(!comp.expired()){ 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))); Inventory::EquipItem(comp.lock()->GetItem(),EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE)));
#pragma region Fully Decked Out Achievement #pragma region Fully Decked Out Achievement
@ -229,28 +207,6 @@ void Menu::InitializeCharacterMenuWindow(){
equip->SetHoverFunc( equip->SetHoverFunc(
[&](MenuFuncData data){ [&](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; if(!data.component.lock()->GetSubcomponentParent().expired())return true;
std::weak_ptr<RowItemDisplay>button=DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component.lock()); std::weak_ptr<RowItemDisplay>button=DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component.lock());
if(!button.expired()){ if(!button.expired()){
@ -269,7 +225,7 @@ void Menu::InitializeCharacterMenuWindow(){
if(slot&EquipSlot::RING1)otherItem=Inventory::GetEquip(EquipSlot::RING2); if(slot&EquipSlot::RING1)otherItem=Inventory::GetEquip(EquipSlot::RING2);
else else
if(slot&EquipSlot::RING2)otherItem=Inventory::GetEquip(EquipSlot::RING1); 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); Inventory::EquipItem(buttonItem,slot);
for(int counter=0;const CharacterMenuWindow::AttributeData&attribute:CharacterMenuWindow::displayAttrs){ 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"); 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_fadeOutTime;
float original_fadeInTime{}; float original_fadeInTime{};
EffectType type{EffectType::NONE}; EffectType type{EffectType::NONE};
private:
Animate2D::Animation<std::string>animation; Animate2D::Animation<std::string>animation;
Animate2D::AnimationState internal_animState; Animate2D::AnimationState internal_animState;
private:
bool upperLevel=false; bool upperLevel=false;
double aliveTime{}; double aliveTime{};
}; };
struct Meteor:Effect{ 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; float startLifetime=0;
bool shakeField=false; bool shakeField=false;
bool Update(float fElapsedTime)override; bool Update(float fElapsedTime)override;
void Draw(const Pixel blendCol)const 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{ 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; std::vector<float>pulsatingFireValues;
float lastParticleTimer=0; float lastParticleTimer=0;
float lastDamageTimer=0; float lastDamageTimer=0;
float radius;
bool Update(float fElapsedTime)override; bool Update(float fElapsedTime)override;
void Draw(const Pixel blendCol)const override; void Draw(const Pixel blendCol)const override;
}; };

@ -1461,4 +1461,37 @@ const Pixel Item::GetDisplayNameColor()const{
Pixel col{WHITE}; Pixel col{WHITE};
if(HasEnchant())col=enchant.value().DisplayCol(); if(HasEnchant())col=enchant.value().DisplayCol();
return col; 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::weak_ptr<Item>rhs){return operator==(rhs,lhs);};
friend const bool operator==(const IT&lhs,std::shared_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; const Pixel GetDisplayNameColor()const;
static const bool SelectedEquipIsDifferent(const std::weak_ptr<Item>equipAttemptingToEquip,const EquipSlot slotToEquipTo);
}; };
class Inventory{ class Inventory{

@ -46,26 +46,66 @@ INCLUDE_game
INCLUDE_MONSTER_LIST INCLUDE_MONSTER_LIST
INCLUDE_GFX 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) 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,imgFile,upperLevel,size,fadeout,spd,col,rotation,rotationSpd,additiveBlending),startLifetime(lifetime){ :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){ bool Meteor::Update(float fElapsedTime){
if(lifetime<=0&&!shakeField){ if(lifetime<=0&&!shakeField){
shakeField=true; shakeField=true;
game->SetupWorldShake("Wizard.Ability 3.WorldShakeTime"_F); 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}; 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<"Wizard.Ability 3.MeteorImpactParticles"_I;i++){ for(int i=0;i<meteorImpactParticles;i++){
float randomAngle="Wizard.Ability 3.MeteorImpactParticleAngleRange"_FRange; 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 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; 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->Hurt(pos,meteorRadius,int(game->GetPlayer()->GetAttack()*damageMult),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); 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("Wizard Meteor",pos); SoundEffect::PlaySFX(meteorCrashSFX,pos);
} }
return Effect::Update(fElapsedTime); return Effect::Update(fElapsedTime);
} }
@ -73,11 +113,11 @@ bool Meteor::Update(float fElapsedTime){
void Meteor::Draw(const Pixel blendCol)const{ void Meteor::Draw(const Pixel blendCol)const{
if(lifetime>0){ if(lifetime>0){
vf2d scale=vf2d{192,64}/3.f*(startLifetime+1-lifetime)*0.25*size; 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}; 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}); 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){ if(lifetime<=0){
meteorOffset=pos-vf2d{0,GetFrame().GetSourceRect().size.y/4.f}*size; meteorOffset=pos-vf2d{0,GetFrame().GetSourceRect().size.y/4.f}*size;
} }

@ -1537,6 +1537,14 @@ void Player::RecalculateEquipStats(){
if(GetClass()&WIZARD){ if(GetClass()&WIZARD){
if(HasEnchant("Blink Portal"))GetRightClickAbility().COOLDOWN_TIME="Blink Portal"_ENC["COOLDOWN"]; 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()){ for(const std::reference_wrapper<Ability>&a:GetAbilities()){

@ -46,8 +46,8 @@ INCLUDE_game
INCLUDE_ANIMATION_DATA INCLUDE_ANIMATION_DATA
INCLUDE_MONSTER_LIST 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) 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)
:Effect(pos,lifetime,animation,upperLevel,size,fadeout,spd,col,rotation,rotationSpd,additiveBlending){ :radius(radius),Effect(pos,lifetime,animation,upperLevel,size,fadeout,spd,col,rotation,rotationSpd,additiveBlending){
for(int i=0;i<8;i++){ for(int i=0;i<8;i++){
pulsatingFireValues.push_back(util::random(1)); pulsatingFireValues.push_back(util::random(1));
} }
@ -69,7 +69,7 @@ bool PulsatingFire::Update(float fElapsedTime){
} }
if(lastDamageTimer<=0){ if(lastDamageTimer<=0){
lastDamageTimer="Wizard.Ability 3.FireRingDamageFreq"_F-0.01f; 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); SoundEffect::PlaySFX("Wizard Meteor Flames",pos);
} }
return Effect::Update(fElapsedTime); return Effect::Update(fElapsedTime);

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

@ -200,7 +200,10 @@ void Wizard::InitializeClassAbilities(){
#pragma region Wizard Ability 3 (Meteor) #pragma region Wizard Ability 3 (Meteor)
Wizard::ability3.action= Wizard::ability3.action=
[](Player*p,vf2d pos={}){ [](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; return true;
}; };
#pragma endregion #pragma endregion

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

@ -495,6 +495,12 @@ Events
File[0] = wizard_auto1.ogg, 60% File[0] = wizard_auto1.ogg, 60%
File[1] = wizard_auto2.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 Wizard Fire Bolt Shoot
{ {
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%) # 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_BlackHole = blackhole.png
GFX_Portal = portal.png GFX_Portal = portal.png
GFX_Flames = FlamesTexture.png GFX_Flames = FlamesTexture.png
GFX_Comet = comet.png
GFX_Thief_Sheet = nico-thief.png GFX_Thief_Sheet = nico-thief.png
GFX_Trapper_Sheet = nico-trapper.png GFX_Trapper_Sheet = nico-trapper.png

@ -529,13 +529,13 @@ Item Enchants
} }
Summon Comet 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 Affects = Ability 3
METEOR CAST TIME = 0.5s METEOR CAST TIME = 0.5s
FALL SPEED MULT = 2.0x FALL SPEED MULT = 2.0x
DAMAGE REDUCTION PCT = 10% DAMAGE REDUCTION PCT = 10%
COOLDOWN REDUCTION = 8s COOLDOWN REDUCTION = -8s
# Stat, Lowest, Highest Value # Stat, Lowest, Highest Value
# Stat Modifier[0] = ..., 0, 0 # Stat Modifier[0] = ..., 0, 0

Loading…
Cancel
Save