Refactored player buff rendering code. Added Freeze Ground Slowdown oscillation color effect. Implemented Speed Up Spell. Changed MonsterAbility lambda to return success or failure spell cast state. Skeleton Lightnng Mage monster setup. Release Build 12939
All checks were successful
Emscripten Build / Build_and_Deploy_Web_Build (push) Successful in 8m8s
All checks were successful
Emscripten Build / Build_and_Deploy_Web_Build (push) Successful in 8m8s
This commit is contained in:
parent
451c248f7d
commit
19746432d4
@ -1089,25 +1089,20 @@ void AiL::RenderWorld(float fElapsedTime){
|
||||
pos=player->teleportStartPosition.lerp(player->teleportTarget,("Wizard.Right Click Ability.AnimationTime"_F-player->teleportAnimationTimer)/"Wizard.Right Click Ability.AnimationTime"_F);
|
||||
}
|
||||
const std::vector<Buff>attackBuffs{player->GetStatBuffs({"Attack","Attack %"})};
|
||||
const std::vector<Buff>movespeedBuffs{player->GetBuffs(BuffType::SPEEDBOOST)};
|
||||
const std::vector<Buff>adrenalineRushBuffs{player->GetBuffs(BuffType::ADRENALINE_RUSH)};
|
||||
const std::vector<Buff>damageReductionBuffs{player->GetBuffs(BuffType::DAMAGE_REDUCTION)};
|
||||
const std::vector<Buff>inkSlowdownDebuff{player->GetBuffs(BuffType::INK_SLOWDOWN)};
|
||||
const std::vector<Buff>curseDebuff{player->GetBuffs(BuffType::PIRATE_GHOST_CAPTAIN_CURSE_DOT)};
|
||||
const std::vector<Buff>hastenBuff{player->GetBuffs(BuffType::HASTEN)};
|
||||
const bool displayCoinSymbol{player->GetBuffs(BuffType::PIRATE_GHOST_CAPTAIN_CURSE_COIN).size()>0};
|
||||
const bool displayCoinSymbol{player->HasBuff(BuffType::PIRATE_GHOST_CAPTAIN_CURSE_COIN)};
|
||||
|
||||
Pixel playerCol{WHITE};
|
||||
if(attackBuffs.size()>0)playerCol={255,uint8_t(255*abs(sin(1.4f*attackBuffs[0].totalTimeAlive))),uint8_t(255*abs(sin(1.4f*attackBuffs[0].totalTimeAlive)))};
|
||||
else if(adrenalineRushBuffs.size()>0)playerCol={uint8_t(255*abs(sin(6.f*adrenalineRushBuffs[0].totalTimeAlive))),255,uint8_t(255*abs(sin(6.f*adrenalineRushBuffs[0].totalTimeAlive)))};
|
||||
else if(movespeedBuffs.size()>0)playerCol={uint8_t(255*abs(sin(2.f*movespeedBuffs[0].totalTimeAlive))),255,uint8_t(255*abs(sin(2.f*movespeedBuffs[0].totalTimeAlive)))};
|
||||
else if(inkSlowdownDebuff.size()>0)playerCol={uint8_t(255*abs(sin(2.f*inkSlowdownDebuff[0].totalTimeAlive))),uint8_t(255*abs(sin(2.f*inkSlowdownDebuff[0].totalTimeAlive))),uint8_t(255*abs(sin(2.f*inkSlowdownDebuff[0].totalTimeAlive)))};
|
||||
else if(curseDebuff.size()>0)playerCol={uint8_t(128*abs(sin(2.f*GetRunTime()))+127),uint8_t(128*abs(sin(2.f*GetRunTime()))),uint8_t(128*abs(sin(2.f*GetRunTime()))+127)};
|
||||
else if(hastenBuff.size()>0)playerCol={uint8_t(abs(sin(0.4f*GetRunTime()))+127),uint8_t(0),uint8_t(0)};
|
||||
else if(auto&buff{player->GetBuff(BuffType::ADRENALINE_RUSH)};buff)playerCol={uint8_t(255*abs(sin(6.f*(*buff).totalTimeAlive))),255,uint8_t(255*abs(sin(2.f*(*buff).totalTimeAlive)))};
|
||||
else if(auto&buff{player->GetBuff(BuffType::INK_SLOWDOWN)};buff)playerCol={uint8_t(255*abs(sin(2.f*(*buff).totalTimeAlive))),255,uint8_t(255*abs(sin(6.f*(*buff).totalTimeAlive)))};
|
||||
else if(auto&buff{player->GetBuff(BuffType::FREEZEGROUND_SLOWDOWN)};buff)playerCol={uint8_t(192*abs(sin(3.f*(*buff).totalTimeAlive))),uint8_t(192*abs(sin(3.f*(*buff).totalTimeAlive))),uint8_t(255*abs(sin(3.f*(*buff).totalTimeAlive)))};
|
||||
else if(auto&buff{player->GetBuff(BuffType::SPEEDBOOST)};buff)playerCol={uint8_t(255*abs(sin(2.f*(*buff).totalTimeAlive))),uint8_t(255*abs(sin(2.f*(*buff).totalTimeAlive))),uint8_t(255*abs(sin(2.f*(*buff).totalTimeAlive)))};
|
||||
else if(player->HasBuff(BuffType::PIRATE_GHOST_CAPTAIN_CURSE_DOT))playerCol={uint8_t(128*abs(sin(2.f*GetRunTime()))+127),uint8_t(128*abs(sin(2.f*GetRunTime()))),uint8_t(128*abs(sin(2.f*GetRunTime()))+127)};
|
||||
else if(player->HasBuff(BuffType::HASTEN))playerCol={uint8_t(abs(sin(0.4f*GetRunTime()))+127),uint8_t(0),uint8_t(0)};
|
||||
|
||||
if(player->HasIframes())playerCol.a*=0.62f;
|
||||
|
||||
if(damageReductionBuffs.size()>0)view.DrawPartialSquishedRotatedDecal(pos+vf2d{0,-player->GetZ()*(std::signbit(scale.y)?-1:1)},player->playerOutline.Decal(),player->GetSpinAngle(),{12,12},animationFrame.get().GetSourceRect().pos,animationFrame.get().GetSourceRect().size,playerScale*scale+0.1f,{1.f,player->ySquishFactor},{210,210,210,uint8_t(util::lerp(0,255,abs(sin((PI*GetRunTime())/1.25f))))});
|
||||
if(player->HasBuff(BuffType::DAMAGE_REDUCTION))view.DrawPartialSquishedRotatedDecal(pos+vf2d{0,-player->GetZ()*(std::signbit(scale.y)?-1:1)},player->playerOutline.Decal(),player->GetSpinAngle(),{12,12},animationFrame.get().GetSourceRect().pos,animationFrame.get().GetSourceRect().size,playerScale*scale+0.1f,{1.f,player->ySquishFactor},{210,210,210,uint8_t(util::lerp(0,255,abs(sin((PI*GetRunTime())/1.25f))))});
|
||||
|
||||
view.DrawPartialSquishedRotatedDecal(pos+vf2d{0,-player->GetZ()*(std::signbit(scale.y)?-1:1)},animationFrame.get().GetSourceImage()->Decal(),player->GetSpinAngle(),{12,12},animationFrame.get().GetSourceRect().pos,animationFrame.get().GetSourceRect().size,playerScale*scale,{1.f,player->ySquishFactor},playerCol);
|
||||
|
||||
|
||||
@ -45,8 +45,8 @@ INCLUDE_game
|
||||
|
||||
Blizzard::Blizzard(const vf2d pos,const float radius, const float lifetime,const int damage,const float tickRate,const std::pair<MinScale,MaxScale>snowSizeRange,const float emitterFreq,const bool upperLevel,const FriendlyType friendly)
|
||||
:damage(damage),tickRate(tickRate),tickTimer(tickRate),radius(radius),friendly(friendly),blizzardSFX("Blizzard",pos),Effect(pos,lifetime,"aiming_target.png",upperLevel,(radius*2)*vf2d{1.f,1.f}/24.f,0.5f,{},{199,247,239,128},0.f,0.f,true)
|
||||
,snow(std::make_unique<BlizzardSnowEmitter>(pos,"circle.png",radius,snowSizeRange,emitterFreq,lifetime,upperLevel)){
|
||||
for(int i:std::ranges::iota_view(0,100))snow->Emit();
|
||||
,snow(std::make_unique<BlizzardSnowEmitter>(pos,"circle.png",radius*1.25f,snowSizeRange,emitterFreq,lifetime,upperLevel)){
|
||||
for(int i:std::ranges::iota_view(0,200))snow->Emit();
|
||||
}
|
||||
bool Blizzard::Update(float fElapsedTime){
|
||||
if(!IsDead()){
|
||||
|
||||
@ -70,6 +70,8 @@ enum BuffType{
|
||||
PIRATE_GHOST_CAPTAIN_CURSE_COIN, //A coin icon appears above the player's head.
|
||||
PIRATE_GHOST_CAPTAIN_CURSE_DOT, //The same as above, but now is a damage over time as well.
|
||||
HASTEN, //Any unit under this effect gets X% Attack,Movement speed, and Animation speed
|
||||
SPEED_UP_SPELL_BUFF,
|
||||
FREEZEGROUND_SLOWDOWN,
|
||||
};
|
||||
enum class BuffRestorationType{
|
||||
ONE_OFF, //This is used as a hack fix for the RestoreDuringCast Item script since they require us to restore 1 tick immediately. Over time buffs do not apply a tick immediately.
|
||||
|
||||
@ -38,6 +38,7 @@ All rights reserved.
|
||||
|
||||
#include "Entity.h"
|
||||
#include "Player.h"
|
||||
#include "MonsterStrategyHelpers.h"
|
||||
|
||||
#define is(type) std::holds_alternative<type>(this->entity)
|
||||
#define get(type) std::get<type>(this->entity)
|
||||
@ -77,4 +78,27 @@ bool Entity::Hurt(int damage,bool onUpperLevel,float z,HurtFlag::HurtFlag hurtFl
|
||||
|
||||
std::optional<Buff>Entity::EditBuff(BuffType buff){
|
||||
CallClassFunc(EditBuff(buff));
|
||||
}
|
||||
|
||||
|
||||
const bool Entity::IsSkeletonLightningMage()const{
|
||||
if(is(Player*)){return false;}
|
||||
else if(is(Monster*)){return DATA.HasProperty(std::format("MonsterStrategy.{}.Is Skeleton Lightning Mage?",get(Monster*)->GetStrategyName()))&&Monster::STRATEGY::_GetBool(*get(Monster*),"Is Skeleton Lightning Mage?",get(Monster*)->GetStrategyName());}
|
||||
ERR("Entity is not a valid type! THIS SHOULD NOT BE HAPPENING!");
|
||||
std::unreachable();
|
||||
};
|
||||
|
||||
const bool Entity::IsBoss()const{
|
||||
if(is(Player*)){return false;}
|
||||
else if(is(Monster*)){return get(Monster*)->isBoss;}
|
||||
ERR("Entity is not a valid type! THIS SHOULD NOT BE HAPPENING!");
|
||||
std::unreachable();
|
||||
};
|
||||
|
||||
const std::vector<Buff>Entity::GetBuffs(BuffType buff)const{
|
||||
CallClassFunc(GetBuffs(buff));
|
||||
}
|
||||
|
||||
const bool Entity::OnUpperLevel()const{
|
||||
CallClassFunc(OnUpperLevel());
|
||||
}
|
||||
@ -60,6 +60,10 @@ public:
|
||||
const float GetDistanceFrom(vf2d target)const;
|
||||
bool Hurt(int damage,bool onUpperLevel,float z,HurtFlag::HurtFlag hurtFlags=HurtFlag::NONE);
|
||||
std::optional<Buff>EditBuff(BuffType buff);
|
||||
const bool IsSkeletonLightningMage()const;
|
||||
const bool IsBoss()const;
|
||||
const std::vector<Buff>GetBuffs(BuffType buff)const;
|
||||
const bool OnUpperLevel()const;
|
||||
private:
|
||||
const std::variant<Monster*,Player*>entity;
|
||||
inline bool operator==(const Entity&rhs){return entity==rhs.entity;}
|
||||
|
||||
@ -15,7 +15,7 @@ bool FreezeGround::Update(float fElapsedTime){
|
||||
tickTimer+=fElapsedTime;
|
||||
while(tickTimer>=settings.slowdownFrequency){
|
||||
for(Entity&target:game->GetTargetsInRange(pos,settings.radius/100.f*24,OnUpperLevel(),GetZ(),friendly?HurtType::MONSTER:HurtType::PLAYER)){
|
||||
Buff&slowdownBuff{target.GetOrAddBuff(BuffType::SLOWDOWN,{settings.slowdownFrequency,0.f})};
|
||||
Buff&slowdownBuff{target.GetOrAddBuff(BuffType::FREEZEGROUND_SLOWDOWN,{settings.slowdownFrequency,0.f})};
|
||||
slowdownBuff.monsterBuffCallbackFunc=[settings=settings](std::weak_ptr<Monster>m,Buff&buff){
|
||||
int stacks=round(buff.intensity/settings.slowdownPerStack*100.f);
|
||||
|
||||
@ -38,7 +38,7 @@ bool FreezeGround::Update(float fElapsedTime){
|
||||
|
||||
std::ranges::for_each(entitiesThatHaveLeft,[&slowdownPerStack=settings.slowdownPerStack](auto&pair){
|
||||
auto&[key,entity]=pair;
|
||||
if(auto buffCheck{entity.EditBuff(BuffType::SLOWDOWN)};buffCheck){
|
||||
if(auto buffCheck{entity.EditBuff(BuffType::FREEZEGROUND_SLOWDOWN)};buffCheck){
|
||||
Buff&slowdownBuff{*buffCheck};
|
||||
int stacks=round(slowdownBuff.intensity/slowdownPerStack*100.f);
|
||||
if(stacks>1)stacks--;
|
||||
|
||||
@ -64,7 +64,6 @@ INCLUDE_DAMAGENUMBER_LIST
|
||||
INCLUDE_game
|
||||
INCLUDE_BULLET_LIST
|
||||
INCLUDE_DATA
|
||||
INCLUDE_GFX
|
||||
INCLUDE_SPAWNER_CONTROLLER
|
||||
INCLUDE_SPAWNER_LIST
|
||||
|
||||
@ -111,16 +110,10 @@ float Monster::GetMoveSpdMult()const{
|
||||
float moveSpdPct=stats.A_Read("Move Spd %")/100.f;
|
||||
|
||||
float mod_moveSpd=moveSpdPct;
|
||||
for(const Buff&b:GetBuffs(SLOWDOWN)){
|
||||
mod_moveSpd-=moveSpdPct*b.intensity;
|
||||
for(const Buff&debuff:GetBuffs({SLOWDOWN,SELF_INFLICTED_SLOWDOWN,BLOCK_SLOWDOWN,INK_SLOWDOWN,FREEZEGROUND_SLOWDOWN})){
|
||||
mod_moveSpd-=moveSpdPct*debuff.intensity;
|
||||
}
|
||||
for(const Buff&b:GetBuffs(SELF_INFLICTED_SLOWDOWN)){
|
||||
mod_moveSpd-=moveSpdPct*b.intensity;
|
||||
}
|
||||
for(const Buff&b:GetBuffs(LOCKON_SPEEDBOOST)){
|
||||
mod_moveSpd+=moveSpdPct*b.intensity;
|
||||
}
|
||||
for(const Buff&b:GetBuffs(SPEEDBOOST)){
|
||||
for(const Buff&b:GetBuffs({LOCKON_SPEEDBOOST,SPEEDBOOST,SPEED_UP_SPELL_BUFF,ADRENALINE_RUSH})){
|
||||
mod_moveSpd+=moveSpdPct*b.intensity;
|
||||
}
|
||||
return mod_moveSpd;
|
||||
@ -534,7 +527,6 @@ void Monster::Draw()const{
|
||||
|
||||
if(GetBuffs(BuffType::GLOW_PURPLE).size()>0)glowPurpleBuff=GetBuffs(BuffType::GLOW_PURPLE)[0];
|
||||
|
||||
const auto HasBuff=[&](const BuffType buff){return GetBuffs(buff).size()>0;};
|
||||
//The lerpCutAmount is how much to divide the initial color by, which is used as the lerp oscillation amount. 0.5 means half the color is always active, and the other half linearly oscillates. 0.1 would mean 90% of the color is normal and 10% of the color oscillates.
|
||||
const auto GetBuffBlendCol=[&](const BuffType buff,const float oscillationTime_s,const Pixel blendCol,const float lerpCutAmount=0.5f){return Pixel{uint8_t(blendCol.r*(1-lerpCutAmount)+blendCol.r*lerpCutAmount*abs(sin(oscillationTime_s*PI*GetBuffs(buff)[0].totalTimeAlive))),uint8_t(blendCol.g*(1-lerpCutAmount)+blendCol.g*lerpCutAmount*abs(sin(oscillationTime_s*PI*GetBuffs(buff)[0].totalTimeAlive))),uint8_t(blendCol.b*(1-lerpCutAmount)+blendCol.b*lerpCutAmount*abs(sin(oscillationTime_s*PI*GetBuffs(buff)[0].totalTimeAlive)))};};
|
||||
|
||||
@ -544,6 +536,7 @@ void Monster::Draw()const{
|
||||
else if(HasBuff(BuffType::COLOR_MOD))blendCol=GetBuffBlendCol(BuffType::COLOR_MOD,1.4f,PixelRaw(GetBuffs(BuffType::COLOR_MOD)[0].intensity),1.f);
|
||||
else if(HasBuff(BuffType::SLOWDOWN))blendCol=GetBuffBlendCol(BuffType::SLOWDOWN,1.4f,{255,255,128},0.5f);
|
||||
else if(HasBuff(BuffType::HASTEN))blendCol=GetBuffBlendCol(BuffType::HASTEN,0.4f,{128,0,0},1.f);
|
||||
else if(HasBuff(BuffType::SPEED_UP_SPELL_BUFF))blendCol=GetBuffBlendCol(BuffType::SPEED_UP_SPELL_BUFF,0.2f,{192,255,192},0.6f);
|
||||
else if(glowPurpleBuff.has_value())blendCol=Pixel{uint8_t(255*abs(sin(1.4*glowPurpleBuff.value().lifetime))),uint8_t(255*abs(sin(1.4*glowPurpleBuff.value().lifetime))),uint8_t(128+127*abs(sin(1.4*glowPurpleBuff.value().lifetime)))};
|
||||
|
||||
const vf2d hitTimerOffset=vf2d{sin(20*PI*lastHitTimer+randomFrameOffset),0.f}*2.f*GetSizeMult();
|
||||
@ -1803,9 +1796,9 @@ void Monster::CastAbility(const MonsterAbilityData&data,const vf2d pos){
|
||||
}
|
||||
|
||||
void Monster::PerformSpell(const CastInfo&castData){
|
||||
MonsterAbility::SPELLS.at(castData.name)(castData.name,castData.castPos,*this,GetStrategyName());
|
||||
const MonsterAbility::SpellSucceededResult result{MonsterAbility::SPELLS.at(castData.name)(castData.name,castData.castPos,*this,GetStrategyName())};
|
||||
PerformIdleAnimation();
|
||||
mp-=castData.mpCost;
|
||||
if(result== MonsterAbility::SpellSucceededResult::SPELL_COMPLETED)mp-=castData.mpCost;
|
||||
}
|
||||
|
||||
void Monster::RunStrategy(const MonsterStrategy&strategy){
|
||||
|
||||
@ -51,6 +51,7 @@ All rights reserved.
|
||||
#include "HurtDamageInfo.h"
|
||||
#include "Oscillator.h"
|
||||
#include"MonsterStrategyHelpers.h"
|
||||
#include"Entity.h"
|
||||
|
||||
INCLUDE_ITEM_DATA
|
||||
INCLUDE_MONSTER_DATA
|
||||
@ -66,6 +67,8 @@ namespace MonsterTests{
|
||||
class MonsterTest;
|
||||
};
|
||||
|
||||
class Entity;
|
||||
|
||||
class DeathSpawnInfo{
|
||||
std::string monsterSpawnName;
|
||||
uint8_t spawnAmt;
|
||||
@ -81,6 +84,7 @@ class Monster:public IAttributable{
|
||||
friend class DeathSpawnInfo;
|
||||
friend class MonsterTests::MonsterTest;
|
||||
friend struct MonsterData;
|
||||
friend const bool Entity::IsBoss()const;
|
||||
public:
|
||||
enum MonsterStrategy{
|
||||
RUN_TOWARDS,
|
||||
@ -131,6 +135,7 @@ public:
|
||||
static std::string ERR;
|
||||
static int _GetInt(Monster&m,const std::string¶m,const std::string&strategy,int index=0);
|
||||
static float _GetFloat(Monster&m,const std::string¶m,const std::string&strategy,int index=0);
|
||||
static bool _GetBool(Monster&m,const std::string¶m,const std::string&strategy,int index=0);
|
||||
static Pixel _GetPixel(Monster&m,const std::string¶m,const std::string&strategy,int index=0);
|
||||
//Converts unit distances to pixels. (Every 100 units = 24 pixels)
|
||||
static float _GetPixels(Monster&m,const std::string¶m,const std::string&strategy,int index=0);
|
||||
|
||||
@ -3,20 +3,24 @@
|
||||
#include"MonsterStrategyHelpers.h"
|
||||
#include"BulletTypes.h"
|
||||
#include "util.h"
|
||||
#include<algorithm>
|
||||
|
||||
INCLUDE_game
|
||||
|
||||
std::unordered_map<std::string_view,std::function<void(SpellName,CastPos,CasterMonster&,const StrategyName&)>>MonsterAbility::SPELLS{
|
||||
{"Meteor",[](SpellName spellName,CastPos pos,CasterMonster&m,const StrategyName&strategy)->void{
|
||||
std::unordered_map<std::string_view,std::function<MonsterAbility::SpellSucceededResult(SpellName,CastPos,CasterMonster&,const StrategyName&)>>MonsterAbility::SPELLS{
|
||||
{"Meteor",[](SpellName spellName,CastPos pos,CasterMonster&m,const StrategyName&strategy)->SpellSucceededResult{
|
||||
game->AddEffect(std::make_unique<Meteor>(pos,3.f,m.OnUpperLevel(),Meteor::SKELETON_MAGE,std::pair<MeteorDamage,PulsatingFireDamage>{int(m.GetAttack()*ConfigFloat("Meteor Damage Mult")),int(m.GetAttack()*ConfigFloat("Fire Ring Damage Mult"))},"Wizard.Ability 3.MeteorFadeoutTime"_F));
|
||||
return SpellSucceededResult::SPELL_COMPLETED;
|
||||
}},
|
||||
{"Fire Bolt",[](SpellName spellName,CastPos pos,CasterMonster&m,const StrategyName&strategy)->void{
|
||||
{"Fire Bolt",[](SpellName spellName,CastPos pos,CasterMonster&m,const StrategyName&strategy)->SpellSucceededResult{
|
||||
CreateBullet(FireBolt)(m.GetPos(),util::pointTo(m.GetPos(),game->GetPlayer()->GetPos())*ConfigFloat("Fire Bolt Bullet Speed"),"Wizard.Ability 1.Radius"_F/100*12,m.GetAttack()*ConfigFloat("Fire Bolt Damage Mult"),m.OnUpperLevel())EndBullet;
|
||||
return SpellSucceededResult::SPELL_COMPLETED;
|
||||
}},
|
||||
{"Blizzard",[](SpellName spellName,CastPos pos,CasterMonster&m,const StrategyName&strategy)->void{
|
||||
game->AddEffect(std::make_unique<Blizzard>(pos,ConfigPixels("Blizzard Radius"),ConfigFloat("Blizzard Duration"),m.GetAttack()*ConfigFloat("Blizzard Tick Damage Mult"),ConfigFloat("Blizzard Tick Rate"),std::pair<MinScale,MaxScale>{1.f,2.f},0.1f,m.OnUpperLevel(),NON_FRIENDLY));
|
||||
{"Blizzard",[](SpellName spellName,CastPos pos,CasterMonster&m,const StrategyName&strategy)->SpellSucceededResult{
|
||||
game->AddEffect(std::make_unique<Blizzard>(pos,ConfigPixels("Blizzard Radius"),ConfigFloat("Blizzard Duration"),m.GetAttack()*ConfigFloat("Blizzard Tick Damage Mult"),ConfigFloat("Blizzard Tick Rate"),std::pair<MinScale,MaxScale>{1.f,2.f},0.05f,m.OnUpperLevel(),NON_FRIENDLY));
|
||||
return SpellSucceededResult::SPELL_COMPLETED;
|
||||
}},
|
||||
{"Freeze Ground",[](SpellName spellName,CastPos pos,CasterMonster&m,const StrategyName&strategy)->void{
|
||||
{"Freeze Ground",[](SpellName spellName,CastPos pos,CasterMonster&m,const StrategyName&strategy)->SpellSucceededResult{
|
||||
const FreezeGround::FreezeGroundSettings settings{
|
||||
.radius=ConfigFloat("Freeze Ground Radius"),
|
||||
.slowdownPerStack=ConfigFloat("Freeze Ground Slowdown Per Stack"),
|
||||
@ -29,5 +33,26 @@ std::unordered_map<std::string_view,std::function<void(SpellName,CastPos,CasterM
|
||||
.lifetime=ConfigFloat("Freeze Ground Lifetime"),
|
||||
};
|
||||
game->AddEffect(std::make_unique<FreezeGround>(pos,ConfigFloat("Freeze Ground Lifetime"),m.GetAttack(),settings,m.OnUpperLevel(),NON_FRIENDLY),true);
|
||||
return SpellSucceededResult::SPELL_COMPLETED;
|
||||
}},
|
||||
{"Speed Up",[](SpellName spellName,CastPos pos,CasterMonster&m,const StrategyName&strategy)->SpellSucceededResult{
|
||||
if(const auto&entitiesInRange{game->GetTargetsInRange(m.GetPos(),ConfigPixels("Speed Up Radius"),m.OnUpperLevel(),m.GetZ(),HurtType::MONSTER)|std::views::filter([](const Entity&entity){return !entity.IsSkeletonLightningMage()&&!entity.IsBoss();})|std::ranges::to<std::vector>()};entitiesInRange.size()>0){
|
||||
std::vector<Entity>randomEntityChosenList;
|
||||
std::ranges::sample(entitiesInRange,std::back_inserter(randomEntityChosenList),1,util::GetRNG());
|
||||
if(randomEntityChosenList.size()!=1)ERR(std::format("WARNING! Expected only 1 element! ({} inside container)",randomEntityChosenList.size()));
|
||||
Entity&chosenEntity{randomEntityChosenList.at(0)};
|
||||
Buff&speedBoostBuff{chosenEntity.GetOrAddBuff(BuffType::SPEED_UP_SPELL_BUFF,{ConfigFloat("Speed Up Boost Duration"),ConfigFloat("Speed Up Boost Amount")/100.f})};
|
||||
speedBoostBuff.lifetime=speedBoostBuff.originalDuration;
|
||||
for(int i:std::ranges::iota_view(0,30)){
|
||||
vf2d polarRandomCoord{util::random_range(0,24),util::degToRad(util::random(360))};
|
||||
game->AddEffect(std::make_unique<Effect>(chosenEntity.GetPos()+polarRandomCoord.cart(),util::random_range(0,3),"circle.png",chosenEntity.OnUpperLevel(),util::random_range(0.5f,1.75f)*vf2d{1,1},1.f,vf2d{0.f,-util::random_range(3.f,10.f)},PixelLerp({255,255,255,uint8_t(util::random_range(128,255))},{0,255,0,uint8_t(util::random_range(128,255))},util::random(1)),util::random(2*PI),0.f,false));
|
||||
}
|
||||
return SpellSucceededResult::SPELL_COMPLETED;
|
||||
}
|
||||
return SpellSucceededResult::SPELL_FAILED;
|
||||
}},
|
||||
{"Thunderorb",[](SpellName spellName,CastPos pos,CasterMonster&m,const StrategyName&strategy)->SpellSucceededResult{
|
||||
//game->AddEffect(std::make_unique<Blizzard>(pos,ConfigPixels("Blizzard Radius"),ConfigFloat("Blizzard Duration"),m.GetAttack()*ConfigFloat("Blizzard Tick Damage Mult"),ConfigFloat("Blizzard Tick Rate"),std::pair<MinScale,MaxScale>{1.f,2.f},0.05f,m.OnUpperLevel(),NON_FRIENDLY));
|
||||
return SpellSucceededResult::SPELL_FAILED;
|
||||
}},
|
||||
};
|
||||
@ -15,5 +15,9 @@ using CasterMonster=Monster;
|
||||
|
||||
class MonsterAbility{
|
||||
public:
|
||||
static std::unordered_map<std::string_view,std::function<void(SpellName,CastPos,CasterMonster&,const StrategyName&)>>SPELLS;
|
||||
enum class SpellSucceededResult:bool{
|
||||
SPELL_COMPLETED=true,
|
||||
SPELL_FAILED=false,
|
||||
};
|
||||
static std::unordered_map<std::string_view,std::function<SpellSucceededResult(SpellName,CastPos,CasterMonster&,const StrategyName&)>>SPELLS;
|
||||
};
|
||||
@ -43,6 +43,7 @@ using StrategyName=std::string;
|
||||
#pragma once
|
||||
#define ConfigInt(param) Monster::STRATEGY::_GetInt(m,param,strategy)
|
||||
#define ConfigFloat(param) Monster::STRATEGY::_GetFloat(m,param,strategy)
|
||||
#define ConfigBool(param) Monster::STRATEGY::_GetBool(m,param,strategy)
|
||||
#define ConfigPixel(param) Monster::STRATEGY::_GetPixel(m,param,strategy)
|
||||
//Converts unit distances to pixels. (Every 100 units = 24 pixels)
|
||||
#define ConfigPixels(param) Monster::STRATEGY::_GetPixels(m,param,strategy)
|
||||
|
||||
@ -313,27 +313,15 @@ const int Player::GetDefense()const{
|
||||
float Player::GetMoveSpdMult(){
|
||||
float moveSpdPct=GetEquipStat("Move Spd %")/100.f;
|
||||
float mod_moveSpd=moveSpdPct;
|
||||
for(const Buff&b:GetBuffs(BuffType::SLOWDOWN)){
|
||||
mod_moveSpd-=moveSpdPct*b.intensity;
|
||||
for(const Buff&debuff:GetBuffs({SLOWDOWN,SELF_INFLICTED_SLOWDOWN,BLOCK_SLOWDOWN,INK_SLOWDOWN,FREEZEGROUND_SLOWDOWN})){
|
||||
mod_moveSpd-=moveSpdPct*debuff.intensity;
|
||||
}
|
||||
for(const Buff&b:GetBuffs(BuffType::BLOCK_SLOWDOWN)){
|
||||
mod_moveSpd-=moveSpdPct*b.intensity;
|
||||
}
|
||||
for(const Buff&b:GetBuffs(BuffType::INK_SLOWDOWN)){
|
||||
mod_moveSpd-=moveSpdPct*b.intensity;
|
||||
}
|
||||
for(const Buff&b:GetBuffs(LOCKON_SPEEDBOOST)){
|
||||
mod_moveSpd+=moveSpdPct*b.intensity;
|
||||
}
|
||||
for(const Buff&b:GetBuffs(SPEEDBOOST)){
|
||||
for(const Buff&b:GetBuffs({LOCKON_SPEEDBOOST,SPEEDBOOST,SPEED_UP_SPELL_BUFF,ADRENALINE_RUSH})){
|
||||
mod_moveSpd+=moveSpdPct*b.intensity;
|
||||
}
|
||||
for(const Buff&b:GetStatBuffs({"Move Spd %"})){
|
||||
mod_moveSpd+=moveSpdPct*b.intensity;
|
||||
}
|
||||
for(const Buff&b:GetBuffs(BuffType::ADRENALINE_RUSH)){
|
||||
mod_moveSpd+=moveSpdPct*"Thief.Ability 3.Movement Speed Increase"_F/100.f;
|
||||
}
|
||||
return mod_moveSpd;
|
||||
}
|
||||
|
||||
@ -1187,6 +1175,10 @@ const std::vector<Buff>Player::GetBuffs(BuffType buff)const{
|
||||
return buffList|std::views::filter([&buff](const Buff&b){return b.type==buff;})|std::ranges::to<std::vector<Buff>>();
|
||||
}
|
||||
|
||||
const std::vector<Buff>Player::GetBuffs(std::vector<BuffType>buffs)const{
|
||||
return buffList|std::views::filter([&buffs](const Buff&b){return std::find(buffs.begin(),buffs.end(),b.type)!=buffs.end();})|std::ranges::to<std::vector>();
|
||||
}
|
||||
|
||||
void Player::RemoveBuff(BuffType buff){
|
||||
for(auto it=buffList.begin();it!=buffList.end();++it){
|
||||
Buff&b=*it;
|
||||
@ -2357,4 +2349,4 @@ const float Player::GetHastePct()const{
|
||||
|
||||
const float Player::GetDistanceFrom(vf2d target)const{
|
||||
return geom2d::line<float>(GetPos(),target).length();
|
||||
}
|
||||
}
|
||||
@ -180,6 +180,7 @@ public:
|
||||
Buff&AddBuff(BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,Buff::PlayerBuffExpireCallbackFunction expireCallbackFunc);
|
||||
const bool HasBuff(BuffType buff)const;
|
||||
const std::vector<Buff>GetBuffs(BuffType buff)const;
|
||||
const std::vector<Buff>GetBuffs(std::vector<BuffType>buffs)const;
|
||||
const std::vector<Buff>GetStatBuffs(const std::vector<std::string>&attr)const;
|
||||
|
||||
const std::optional<Buff>GetBuff(BuffType buff)const;
|
||||
|
||||
@ -121,6 +121,20 @@ float Monster::STRATEGY::_GetFloat(Monster&m,const std::string¶m,const std::
|
||||
return{};
|
||||
}
|
||||
}
|
||||
bool Monster::STRATEGY::_GetBool(Monster&m,const std::string¶m,const std::string&strategy,int index){
|
||||
if(m.IsNPC()&&DATA["NPCs"][m.name].HasProperty(param)){
|
||||
return float(DATA["NPCs"][m.name].GetProperty(param).GetBool(index));
|
||||
}else
|
||||
if(!m.IsNPC()&&DATA["Monsters"][m.name].HasProperty(param)){
|
||||
return float(DATA["Monsters"][m.name].GetProperty(param).GetBool(index));
|
||||
}else
|
||||
if(DATA["MonsterStrategy"][strategy].HasProperty(param)){
|
||||
return float(DATA["MonsterStrategy"][strategy].GetProperty(param).GetBool(index));
|
||||
}else{
|
||||
ERR(std::format("Monster {} trying to read non-existent Float Property {}[{}] for Strategy {}. THIS SHOULD NOT BE HAPPENING!",m.GetName(),param,index,strategy))
|
||||
return{};
|
||||
}
|
||||
}
|
||||
const std::string&Monster::STRATEGY::_GetString(Monster&m,const std::string¶m,const std::string&strategy,int index){
|
||||
if(m.IsNPC()&&DATA["NPCs"][m.name].HasProperty(param)){
|
||||
return DATA["NPCs"][m.name].GetProperty(param).GetString(index);
|
||||
|
||||
@ -39,7 +39,7 @@ All rights reserved.
|
||||
#define VERSION_MAJOR 1
|
||||
#define VERSION_MINOR 3
|
||||
#define VERSION_PATCH 0
|
||||
#define VERSION_BUILD 12914
|
||||
#define VERSION_BUILD 12939
|
||||
|
||||
#define stringify(a) stringify_(a)
|
||||
#define stringify_(a) #a
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="315" height="238" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="17">
|
||||
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="315" height="238" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="23">
|
||||
<properties>
|
||||
<property name="Background Music" propertytype="BGM" value="undead_swamp"/>
|
||||
<property name="Level Type" type="int" propertytype="LevelType" value="0"/>
|
||||
@ -990,7 +990,27 @@
|
||||
<object id="6" name="Monster Spawn Zone" type="SpawnGroup" x="438" y="4770" width="216" height="246">
|
||||
<ellipse/>
|
||||
</object>
|
||||
<object id="16" template="../maps/Monsters/Skeleton Frost Mage.tx" x="612" y="4794">
|
||||
<object id="17" template="../maps/Monsters/Skeleton Lightning Mage.tx" x="606" y="4788">
|
||||
<properties>
|
||||
<property name="spawner" type="object" value="6"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="18" template="../maps/Monsters/Crab.tx" x="618" y="4842">
|
||||
<properties>
|
||||
<property name="spawner" type="object" value="6"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="20" template="../maps/Monsters/Skeleton Lightning Mage.tx" x="570" y="4794">
|
||||
<properties>
|
||||
<property name="spawner" type="object" value="6"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="21" template="../maps/Monsters/Skeleton Frost Mage.tx" x="606" y="4806">
|
||||
<properties>
|
||||
<property name="spawner" type="object" value="6"/>
|
||||
</properties>
|
||||
</object>
|
||||
<object id="22" template="../maps/Monsters/Skeleton Frost Mage.tx" x="600" y="4830">
|
||||
<properties>
|
||||
<property name="spawner" type="object" value="6"/>
|
||||
</properties>
|
||||
|
||||
@ -1456,5 +1456,11 @@ MonsterStrategy
|
||||
Freeze Ground Stacks Removed Immediately On Leave = 1
|
||||
Freeze Ground Stack Removal Rate = 1s
|
||||
Freeze Ground Lifetime = 10s
|
||||
|
||||
Speed Up Radius = 400
|
||||
Speed Up Boost Amount = 20%
|
||||
Speed Up Boost Duration = 20s
|
||||
|
||||
Is Skeleton Lightning Mage? = false
|
||||
}
|
||||
}
|
||||
@ -2240,7 +2240,7 @@ Monsters
|
||||
{
|
||||
#Ability Name = Mana cost, % chance to cast (every second), Cast Time (Instant = 0.0s), [Variable representing the cast radius]
|
||||
Blizzard = 60mp, 60%, 1.5sec, MonsterStrategy.Skeleton Mage.Blizzard Radius
|
||||
# Freeze Ground = 30mp, 20%, 0.0sec
|
||||
Freeze Ground = 30mp, 20%, 0.0sec
|
||||
}
|
||||
|
||||
Strategy = Skeleton Mage
|
||||
@ -2267,6 +2267,58 @@ Monsters
|
||||
Death Sound = Slime Dead
|
||||
Walk Sound = Slime Walk
|
||||
|
||||
# Drop Item Name, Drop Percentage(0-100%), Drop Min Quantity, Drop Max Quantity
|
||||
#DROP[0] = Broken Bow,30%,1,1
|
||||
}
|
||||
Skeleton Lightning Mage
|
||||
{
|
||||
Health = 600
|
||||
Attack = 44
|
||||
|
||||
CollisionDmg = 44
|
||||
|
||||
MoveSpd = 80%
|
||||
Size = 90%
|
||||
|
||||
XP = 49
|
||||
|
||||
MP = 0
|
||||
MP Recovery = 5mp/s
|
||||
|
||||
Abilities
|
||||
{
|
||||
#Ability Name = Mana cost, % chance to cast (every second), Cast Time (Instant = 0.0s), [Variable representing the cast radius]
|
||||
Speed Up = 20mp, 15%, 0.0sec
|
||||
Thunderorb = 40mp, 25%, 0.0sec
|
||||
}
|
||||
|
||||
Strategy = Skeleton Mage
|
||||
|
||||
# Override Property from Strategy
|
||||
Is Skeleton Lightning Mage? = true
|
||||
|
||||
#Size of each animation frame
|
||||
SheetFrameSize = 32,32
|
||||
|
||||
# Setting this to true means every four rows indicates one animation, the ordering of the directions is: NORTH, EAST, SOUTH, WEST
|
||||
4-Way Spritesheet = False
|
||||
|
||||
Animations
|
||||
{
|
||||
# Frame Count, Frame Speed (s), Frame Cycling (Repeat,OneShot,PingPong,Reverse,ReverseOneShot)
|
||||
# Animations must be defined in the same order as they are in their sprite sheets
|
||||
# The First Four animations must represent a standing, walking, attack, and death animation. Their names are up to the creator.
|
||||
IDLE = 2, 0.6, Repeat
|
||||
WALK = 3, 0.2, Repeat
|
||||
ATTACK = 4, 0.1, Repeat
|
||||
DEATH = 4, 0.15, OneShot
|
||||
CASTING = 4, 0.1, Repeat
|
||||
}
|
||||
|
||||
Hurt Sound = Monster Hurt
|
||||
Death Sound = Slime Dead
|
||||
Walk Sound = Slime Walk
|
||||
|
||||
# Drop Item Name, Drop Percentage(0-100%), Drop Min Quantity, Drop Max Quantity
|
||||
#DROP[0] = Broken Bow,30%,1,1
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ CAMPAIGN_3_6 = 2026-01-21 20:30:58.4008008
|
||||
CAMPAIGN_3_7 = 2026-01-21 20:30:58.4023071
|
||||
CAMPAIGN_3_8 = 2026-01-21 20:30:58.4049027
|
||||
CAMPAIGN_3_B1 = 2026-01-21 20:30:58.4336641
|
||||
CAMPAIGN_4_1 = 2026-02-26 21:44:13.5188507
|
||||
CAMPAIGN_4_1 = 2026-03-06 21:16:02.6682120
|
||||
CAMPAIGN_4_2 = 2026-02-26 21:44:13.5210572
|
||||
CAMPAIGN_4_4 = 2026-02-26 21:44:13.5270745
|
||||
CAMPAIGN_4_6 = 2026-02-26 21:44:13.5342554
|
||||
|
||||
Binary file not shown.
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<template>
|
||||
<tileset firstgid="1" source="../Monsters.tsx"/>
|
||||
<object name="Skeleton Frost Mage" type="Monster" gid="40" width="48" height="48"/>
|
||||
<object name="Skeleton Frost Mage" type="Monster" gid="40" width="43.2" height="43.2"/>
|
||||
</template>
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<template>
|
||||
<tileset firstgid="1" source="../Monsters.tsx"/>
|
||||
<object name="Skeleton Lightning Mage" type="Monster" gid="41" width="43.2" height="43.2"/>
|
||||
</template>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
@ -1760,7 +1760,7 @@ namespace olc
|
||||
{
|
||||
if (id != -1)
|
||||
{
|
||||
renderer->DeleteTexture(id);
|
||||
renderer->DeleteTexture(id);
|
||||
id = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,6 +196,7 @@ namespace olc::utils
|
||||
|
||||
// Checks if a property exists - useful to avoid creating properties
|
||||
// via reading them, though non-essential
|
||||
// Access a datafile via a convenient name - "root.node.something.property"
|
||||
inline bool HasProperty(const std::string& sName)
|
||||
{
|
||||
size_t x = sName.find_first_of('.');
|
||||
|
||||
@ -53,6 +53,10 @@ int util::random(){
|
||||
return distrib(rng);
|
||||
}
|
||||
|
||||
std::mt19937&util::GetRNG(){
|
||||
return rng;
|
||||
}
|
||||
|
||||
const float util::random_range(const float min,const float max){
|
||||
return random(max-min)+min;
|
||||
}
|
||||
|
||||
@ -50,6 +50,7 @@ namespace olc::util{
|
||||
float random(float range);
|
||||
//Returns a random float value min(inclusive) to max(exclusive).
|
||||
const float random_range(const float min,const float max);
|
||||
std::mt19937&GetRNG();
|
||||
//Returns 0-32767 (as an int).
|
||||
int random();
|
||||
//Returns a normalized vector pointing from posFrom towards posTo.
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user