Implemented Blizzard ability. Added GetOrderedKeys range optimization. Release Build 12550.
Some checks failed
Emscripten Build / Build_and_Deploy_Web_Build (push) Failing after 3m17s

This commit is contained in:
sigonasr2 2026-02-12 16:06:26 -06:00
parent 349edf2ba1
commit de060afbd1
20 changed files with 55 additions and 45 deletions

View File

@ -37,12 +37,13 @@ All rights reserved.
#pragma endregion
#include"Effect.h"
#include"AdventuresInLestoria.h"
#include"BlizzardSnowEmitter.h"
#include<ranges>
INCLUDE_game
Blizzard::Blizzard(const vf2d pos,const float radius, const float lifetime,const int damage,const float tickRate,const Range snowSizeRange,const float emitterFreq,const bool upperLevel,const FriendlyType friendly)
:damage(damage),tickRate(tickRate),tickTimer(tickRate),radius(radius),friendly(friendly),Effect(pos,lifetime,"aiming_target.png",upperLevel,(radius*2/100)*vf2d{1.f,1.f},0.5f,{},{199,247,239,128},0.f,0.f,true)
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), Effect(pos,lifetime,"aiming_target.png",upperLevel,(radius*2)*vf2d{1.f,1.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();
}
@ -53,7 +54,8 @@ bool Blizzard::Update(float fElapsedTime){
game->Hurt(pos,radius,damage,OnUpperLevel(),z,friendly?HurtType::MONSTER:HurtType::PLAYER);
}
snow->Update(fElapsedTime);
return Effect::Update(fElapsedTime);
}
void Blizzard::Draw(const Pixel blendCol)const{
Effect::Draw(blendCol);
}

View File

@ -43,11 +43,12 @@ All rights reserved.
INCLUDE_game
BlizzardSnowEmitter::BlizzardSnowEmitter(const vf2d pos,const std::string&img_filename,const float radius,Range scaleRange,float frequency,float timer,const bool upperLevel)
BlizzardSnowEmitter::BlizzardSnowEmitter(const vf2d pos,const std::string&img_filename,const float radius,std::pair<MinScale,MaxScale>scaleRange,float frequency,float timer,const bool upperLevel)
:pos(pos),img_filename(img_filename),radius(radius),scaleRange(scaleRange),upperLevel(upperLevel),IEmitter(frequency,timer){}
void BlizzardSnowEmitter::Emit(){
const float dist{util::random_range(0,radius)};
const float angle{util::random_range(0,2*PI)};
const float startingZ{util::random_range(0,100)};
const float gravity{util::random_range(10,40)};
const float scale{util::random_range(scaleRange.first,scaleRange.second)};
@ -56,5 +57,5 @@ void BlizzardSnowEmitter::Emit(){
const float colorInterpolation{util::random(1)};
const Pixel baseCol{104,215,239};
const uint8_t alpha{uint8_t(util::random_range(96,255))};
game->AddEffect(std::make_unique<FallEffect>(pos+vf2d{xSpd*timeToFall,0.f},startingZ,gravity,img_filename,upperLevel,scale*vf2d{1.f,1.f},0.5f,vf2d{xSpd,0.f},Pixel{uint8_t(util::lerp(float(baseCol.r),255.f,colorInterpolation)),uint8_t(util::lerp(float(baseCol.g),255.f,colorInterpolation)),uint8_t(util::lerp(float(baseCol.b),255.f,colorInterpolation)),alpha},util::degToRad(util::random_range(0,360)),util::degToRad(util::random_range(-1.f,1.f)),true,EffectType::BLIZZARD_SNOW));
game->AddEffect(std::make_unique<FallEffect>(pos+vf2d{xSpd*timeToFall,0.f}+vf2d{dist,angle}.cart(),startingZ,gravity,img_filename,upperLevel,scale*vf2d{1.f,1.f},0.5f,vf2d{xSpd,0.f},Pixel{uint8_t(util::lerp(float(baseCol.r),255.f,colorInterpolation)),uint8_t(util::lerp(float(baseCol.g),255.f,colorInterpolation)),uint8_t(util::lerp(float(baseCol.b),255.f,colorInterpolation)),alpha},util::degToRad(util::random_range(0,360)),util::degToRad(util::random_range(-1.f,1.f)),true,EffectType::BLIZZARD_SNOW));
}

View File

@ -42,16 +42,15 @@ All rights reserved.
using MinScale=float;
using MaxScale=float;
using Range=std::pair<MinScale,MaxScale>;
class BlizzardSnowEmitter:public IEmitter{
public:
BlizzardSnowEmitter(const olc::vf2d pos,const std::string&img_filename,const float radius,Range scaleRange,float frequency,float timer,const bool upperLevel);
BlizzardSnowEmitter(const olc::vf2d pos,const std::string&img_filename,const float radius,std::pair<MinScale,MaxScale>scaleRange,float frequency,float timer,const bool upperLevel);
virtual void Emit()override final;
private:
const olc::vf2d pos;
const float radius;
const Range scaleRange;
const std::pair<MinScale,MaxScale>scaleRange;
const std::string img_filename;
const bool upperLevel;
};

View File

@ -38,11 +38,13 @@ All rights reserved.
#pragma once
#include"olcUTIL_Geometry2D.h"
#include<optional>
struct CastInfo{
std::string name="???";
float castTimer=0.f;
float castTotalTime=0.f;
vf2d castPos{};
int mpCost;
int mpCost{};
std::optional<float>castRadius{};
};

View File

@ -108,7 +108,7 @@ struct Meteor:Effect{
COMET,
SOLAR_FLARE,
COMET_FLARE,
SKELETON_FIRE_MAGE,
SKELETON_MAGE,
};
Meteor(const vf2d pos,const float lifetime,const bool upperLevel,const MeteorSetting setting,const std::pair<MeteorDamage,PulsatingFireDamage>damage,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;
@ -132,7 +132,7 @@ private:
struct PulsatingFire:Effect{
enum PulsatingFireSetting{
PLAYER,
SKELETON_FIRE_MAGE,
SKELETON_MAGE,
};
PulsatingFire(vf2d pos,float lifetime,const float radius,std::string imgFile,bool upperLevel,int damage,const PulsatingFireSetting setting=PLAYER,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;
@ -264,13 +264,13 @@ struct CollectCoinEffect:Effect{
struct Blizzard:Effect{
public:
Blizzard(const vf2d pos,const float radius,const float lifetime,const int damage,const float tickRate,const Range snowSizeRange,const float emitterFreq,const bool upperLevel,const FriendlyType friendly);
bool Update(float fElapsedTime)override;
void Draw(const Pixel blendCol)const override;
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);
bool Update(float fElapsedTime)override; //NOTE: In most cases, call Effect::Update() in your overwritten function!
void Draw(const Pixel blendCol)const override; //NOTE: In most cases, call Effect::Draw() in your overwritten function!
private:
const int damage;
const int tickRate;
int tickTimer;
const float tickRate;
float tickTimer;
const float radius;
const FriendlyType friendly;
const std::unique_ptr<BlizzardSnowEmitter>snow;

View File

@ -100,7 +100,7 @@ Meteor::Meteor(const vf2d pos,const float lifetime,const bool upperLevel,const M
fireRingLifetime="Wizard.Ability 3.FireRingLifetime"_F*1.5f;
meteorCrashSFX="Wizard Meteor";
}break;
case SKELETON_FIRE_MAGE:{
case SKELETON_MAGE:{
meteorImpactParticles="Wizard.Ability 3.MeteorImpactParticles"_I*4;
randomColorTintR=util::random_range(128,255);
randomColorTintG=util::random_range(128,255);
@ -131,9 +131,9 @@ bool Meteor::Update(float fElapsedTime){
}
#define PLAYER_CASES default
switch(setting){
case SKELETON_FIRE_MAGE:{
case SKELETON_MAGE:{
game->Hurt(pos,meteorRadius/1.25f,damage.first*damageMult,OnUpperLevel(),0,HurtType::PLAYER);
game->AddEffect(std::make_unique<PulsatingFire>(pos,fireRingLifetime,meteorRadius/1.25f,"fire_ring1.png",OnUpperLevel(),damage.second,PulsatingFire::SKELETON_FIRE_MAGE,vf2d{meteorRadius/16,meteorRadius/16},"Wizard.Ability 3.FireRingFadeoutTime"_F),true);
game->AddEffect(std::make_unique<PulsatingFire>(pos,fireRingLifetime,meteorRadius/1.25f,"fire_ring1.png",OnUpperLevel(),damage.second,PulsatingFire::SKELETON_MAGE,vf2d{meteorRadius/16,meteorRadius/16},"Wizard.Ability 3.FireRingFadeoutTime"_F),true);
}break;
PLAYER_CASES:{ //Default cases are player cases. Handle all other enemy cases in their own statements.
game->Hurt(pos,meteorRadius,damage.first*damageMult,OnUpperLevel(),0,HurtType::MONSTER,HurtFlag::PLAYER_ABILITY);

View File

@ -1792,8 +1792,9 @@ const float Monster::GetHastePct()const{
}
void Monster::CastAbility(const MonsterAbilityData&data,const vf2d pos){
currentCast=CastInfo{.name=data.name,.castTimer=data.castTime,.castTotalTime=data.castTime,.castPos=(pos==vf2d{}?game->GetPlayer()->GetPos():pos),.mpCost=data.mpCost};
currentCast=CastInfo{.name=data.name,.castTimer=data.castTime,.castTotalTime=data.castTime,.castPos=(pos==vf2d{}?game->GetPlayer()->GetPos():pos),.mpCost=data.mpCost,.castRadius=data.radius};
PerformAnimation("CASTING");
if(currentCast->castRadius)game->AddEffect(std::make_unique<Effect>(currentCast->castPos,currentCast->castTotalTime,"aiming_target.png",upperLevel,(*currentCast->castRadius*2/100)*vf2d{1.f,1.f},0.5f,vf2d{},Pixel{247,183,82,128},0.f,0.f,true),true);
}
void Monster::PerformSpell(const CastInfo&castData){

View File

@ -80,7 +80,7 @@ class Monster:public IAttributable{
friend class InventoryCreator;
friend class DeathSpawnInfo;
friend class MonsterTests::MonsterTest;
friend class MonsterData;
friend struct MonsterData;
public:
enum MonsterStrategy{
RUN_TOWARDS,
@ -122,7 +122,7 @@ public:
_RUN_RIGHT,
SKELETON_CAPTAIN,
SKELETON_CAPTAIN_FLAG,
SKELETON_FIRE_MAGE,
SKELETON_MAGE,
END_ENUM //THIS SHOULD ALWAYS BE THE LAST ITEM
@ -176,7 +176,7 @@ public:
static void _RUN_RIGHT(Monster&m,float fElapsedTime,const std::string&strategy);
static void SKELETON_CAPTAIN(Monster&m,float fElapsedTime,const std::string&strategy);
static void SKELETON_CAPTAIN_FLAG(Monster&m,float fElapsedTime,const std::string&strategy);
static void SKELETON_FIRE_MAGE(Monster&m,float fElapsedTime,const std::string&strategy);
static void SKELETON_MAGE(Monster&m,float fElapsedTime,const std::string&strategy);
};
struct StrategyFunction{
std::string name;

View File

@ -8,14 +8,15 @@ 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{
game->AddEffect(std::make_unique<Meteor>(pos,3.f,m.OnUpperLevel(),Meteor::SKELETON_FIRE_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));
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));
}},
{"Fire Bolt",[](SpellName spellName,CastPos pos,CasterMonster&m,const StrategyName&strategy)->void{
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;
}},
{"Blizzard",[](SpellName spellName,CastPos pos,CasterMonster&m,const StrategyName&strategy)->void{
//game->AddEffect(std::make_unique<Blizzard>(pos,ConfigFloat("Blizzard Radius")
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));
}},
{"Freeze Ground",[](SpellName spellName,CastPos pos,CasterMonster&m,const StrategyName&strategy)->void{
std::cout<<"Casted Freeze Ground"<<std::endl;
}},
};

View File

@ -215,7 +215,7 @@ void MonsterData::InitializeMonsterData(){
for(auto&[key,size]:abilityData){
auto&ability{abilityData[key]};
if(!MonsterAbility::SPELLS.contains(key))ERR(std::format("WARNING! Monster Ability {} does not exist! Failed to add ability to Monster {}",key,MonsterName));
monster.abilities.emplace_back(MonsterAbilityData{.name=key,.mpCost=ability.GetInt(0),.pctCastChance=ability.GetReal(1)/100.f,.castTime=ability.GetReal(2)});
monster.abilities.emplace_back(MonsterAbilityData{.name=key,.mpCost=ability.GetInt(0),.pctCastChance=ability.GetReal(1)/100.f,.castTime=ability.GetReal(2),.radius=ability.GetValueCount()>=4?DATA.GetProperty(ability.GetString(3)).GetReal():std::optional<float>{}});
}
}

View File

@ -41,6 +41,7 @@ All rights reserved.
#include"Direction.h"
#include"CastInfo.h"
#include<unordered_set>
#include<optional>
INCLUDE_ITEM_DATA
@ -66,6 +67,7 @@ struct MonsterAbilityData{
int mpCost;
float pctCastChance;
float castTime;
std::optional<float>radius;
};
namespace MonsterTests{

View File

@ -74,7 +74,7 @@ bool PulsatingFire::Update(float fElapsedTime){
game->Hurt(pos,radius,damage,OnUpperLevel(),0,HurtType::MONSTER);
SoundEffect::PlaySFX("Wizard Meteor Flames",pos);
}break;
case SKELETON_FIRE_MAGE:{
case SKELETON_MAGE:{
lastDamageTimer="MonsterStrategy.Skeleton Fire Mage.Fire Ring Damage Frequency"_F-0.01f;
game->Hurt(pos,radius,damage,OnUpperLevel(),0,HurtType::PLAYER);
SoundEffect::PlaySFX("Wizard Meteor Flames",pos);

View File

@ -44,6 +44,8 @@ INCLUDE_DATA
std::string Monster::STRATEGY::ERR{};
void Monster::InitializeStrategies(){
Monster::monsterStrategies.Reset();
Monster::stringToStrategyMap.Reset();
#define ADD_STRATEGY(enum,name) \
Monster::monsterStrategies.insert(enum,StrategyFunction{name,Monster::STRATEGY::enum}); \
Monster::stringToStrategyMap.insert(name,enum);
@ -86,7 +88,7 @@ void Monster::InitializeStrategies(){
ADD_STRATEGY(_RUN_RIGHT,"[TEST]Run Right");
ADD_STRATEGY(SKELETON_CAPTAIN,"Skeleton Captain");
ADD_STRATEGY(SKELETON_CAPTAIN_FLAG,"Skeleton Captain Flag");
ADD_STRATEGY(SKELETON_FIRE_MAGE,"Skeleton Fire Mage");
ADD_STRATEGY(SKELETON_MAGE,"Skeleton Mage");
Monster::monsterStrategies.SetInitialized();
}

View File

@ -38,6 +38,6 @@ All rights reserved.
#include "Monster.h"
DEFINE_STRATEGY(SKELETON_FIRE_MAGE)
DEFINE_STRATEGY(SKELETON_MAGE)
END_STRATEGY

View File

@ -20,9 +20,9 @@ Cherry pick 6355054d6c8e76c6aa4b18760a293e3d1a020752 from master
Cherry pick 5914938b709927bb8a8557795d6d46fcbfcfb4ea from SkeletonFireMage
Cherry pick 66a10cee9f874f024633efdc84ebb4432752ab15 from SkeletonFireMage
% chance ability picking (every second)
Making the abilities actually work!
Unit tests for testing monster spell casting
Snow particles must fade away when Blizzard dies
Blizzard blue area circle should remain during the duration of the attack
Add comments to Effect child Update and Draw declarations
DEMO

View File

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

View File

@ -1429,7 +1429,7 @@ MonsterStrategy
Haste Buff Range = 600
Haste Buff = 30%
}
Skeleton Fire Mage
Skeleton Mage
{
Meteor Fall Time = 2s
Meteor Radius = 300
@ -1443,6 +1443,8 @@ MonsterStrategy
Fire Bolt Damage Mult = 1.0x
Blizzard Radius = 350
Blizzard Duration = 5s
Blizzard Tick Damage Mult = 0.5x
Blizzard Tick Rate = 1s
}
}

View File

@ -2194,7 +2194,7 @@ Monsters
Fire Bolt = 20mp, 10%, 1.0sec
}
Strategy = Skeleton Fire Mage
Strategy = Skeleton Mage
#Size of each animation frame
SheetFrameSize = 32,32
@ -2238,12 +2238,12 @@ Monsters
Abilities
{
#Ability Name = Mana cost, % chance to cast (every second), Cast Time (Instant = 0.0s)
Blizzard = 60mp, 60%, 1.5sec
Freeze Ground = 30mp, 20%, 0.0sec
#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
}
Strategy = Skeleton Fire Mage
Strategy = Skeleton Mage
#Size of each animation frame
SheetFrameSize = 32,32

View File

@ -63,6 +63,7 @@ David Barr, aka javidx9, <20>OneLoneCoder 2019, 2020, 2021, 2022
#include <sstream>
#include <numeric>
#include "Error.h"
#include<ranges>
using namespace std::literals;
@ -188,11 +189,8 @@ namespace olc::utils
return m_mapObjects;
}
//This function is slightly expensive due to a filtering function required to remove all comments!
inline std::vector<std::pair<std::string,datafile>> GetOrderedKeys(){
std::vector<std::pair<std::string,datafile>>orderedKeys;
std::copy_if(m_vecObjects.begin(),m_vecObjects.end(),std::back_inserter(orderedKeys),[&](const std::pair<std::string,datafile>&data){return !datafile::IsComment(data);});
return orderedKeys;
inline auto GetOrderedKeys(){
return m_vecObjects|std::views::filter([&](const std::pair<std::string,datafile>&data){return !datafile::IsComment(data);});
}
// Checks if a property exists - useful to avoid creating properties