The open source repository for the action RPG game in development by Sig Productions titled 'Adventures in Lestoria'!
https://forums.lestoria.net
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
178 lines
6.8 KiB
178 lines
6.8 KiB
#pragma region License
|
|
/*
|
|
License (OLC-3)
|
|
~~~~~~~~~~~~~~~
|
|
|
|
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
|
|
|
|
Redistribution and use in source and binary forms, with or without modification,
|
|
are permitted provided that the following conditions are met:
|
|
|
|
1. Redistributions or derivations of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
|
|
2. Redistributions or derivative works in binary form must reproduce the above
|
|
copyright notice. This list of conditions and the following disclaimer must be
|
|
reproduced in the documentation and/or other materials provided with the distribution.
|
|
|
|
3. Neither the name of the copyright holder nor the names of its contributors may
|
|
be used to endorse or promote products derived from this software without specific
|
|
prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
|
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
SUCH DAMAGE.
|
|
|
|
Portions of this software are copyright © 2024 The FreeType
|
|
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
|
|
All rights reserved.
|
|
*/
|
|
#pragma endregion
|
|
#include "AdventuresInLestoria.h"
|
|
#include "DEFINES.h"
|
|
#include "Monster.h"
|
|
#include "MonsterStrategyHelpers.h"
|
|
#include "BulletTypes.h"
|
|
#include "util.h"
|
|
|
|
INCLUDE_game
|
|
INCLUDE_MONSTER_DATA
|
|
|
|
using A=Attribute;
|
|
|
|
void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy){
|
|
enum Phase{
|
|
INITIALIZE,
|
|
IDLE,
|
|
FLY_ACROSS_PREPARE,
|
|
FLY_ACROSS,
|
|
TORNADO_ATTACK_PREPARE,
|
|
TORNADO_ATTACK,
|
|
WIND_ATTACK,
|
|
HALFHEALTH_PHASE,
|
|
};
|
|
|
|
enum class AttackChoice{
|
|
RIGHT,
|
|
LEFT
|
|
};
|
|
|
|
if(m.phase!=HALFHEALTH_PHASE)m.F(A::SPAWNER_TIMER)-=fElapsedTime;
|
|
if(m.F(A::SPAWNER_TIMER)<=0.f){
|
|
const float randomDir=util::random(2*PI);
|
|
game->SpawnMonster(m.GetPos()+vf2d{ConfigFloat("Basic Hawk Spawn Radius"),randomDir}.cart(),MONSTER_DATA.at("Hawk_NOXP"),m.OnUpperLevel());
|
|
m.F(A::SPAWNER_TIMER)=ConfigFloat("Basic Hawk Spawn Time");
|
|
}
|
|
|
|
switch(m.phase){
|
|
case INITIALIZE:{
|
|
m.F(A::SPAWNER_TIMER)=ConfigFloat("Basic Hawk Spawn Time");
|
|
m.phase=IDLE;
|
|
}break;
|
|
case IDLE:{
|
|
const int randomAttackChoice=util::random()%2;
|
|
|
|
switch(randomAttackChoice){
|
|
case 0:{
|
|
m.I(A::ATTACK_CHOICE)=util::random()%2;
|
|
|
|
const bool RightDirectionChosen=m.I(A::ATTACK_CHOICE)==int(AttackChoice::RIGHT);
|
|
|
|
if(RightDirectionChosen)m.target=ConfigVec("Fly Across Attack.Right Edge Start Pos");
|
|
else m.target=ConfigVec("Fly Across Attack.Left Edge Start Pos");
|
|
m.phase=FLY_ACROSS_PREPARE;
|
|
}break;
|
|
case 1:{
|
|
m.phase=TORNADO_ATTACK_PREPARE;
|
|
m.target=ConfigVec("Tornado Attack.Arena Center");
|
|
}break;
|
|
case 2:{
|
|
m.phase=WIND_ATTACK;
|
|
}break;
|
|
case 3:{
|
|
m.phase=HALFHEALTH_PHASE;
|
|
}break;
|
|
}
|
|
}break;
|
|
case FLY_ACROSS_PREPARE:{
|
|
m.targetAcquireTimer=20.f;
|
|
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
|
|
if(m.ReachedTargetPos()){
|
|
const bool RightDirectionChosen=m.I(A::ATTACK_CHOICE)==int(AttackChoice::RIGHT);
|
|
|
|
//We're choosing the opposite side of the field to direct the boss towards for this attack.
|
|
if(RightDirectionChosen)m.target=ConfigVec("Fly Across Attack.Left Edge Start Pos");
|
|
else m.target=ConfigVec("Fly Across Attack.Right Edge Start Pos");
|
|
m.phase=FLY_ACROSS;
|
|
|
|
m.AddBuff(BuffType::SPEEDBOOST,INFINITY,ConfigFloat("Fly Across Attack.Move Speed Multiplier")-1.f);
|
|
}
|
|
}break;
|
|
case FLY_ACROSS:{
|
|
m.targetAcquireTimer=20.f;
|
|
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
|
|
m.F(A::SHOOT_TIMER)-=fElapsedTime;
|
|
if(m.F(A::SHOOT_TIMER)<=0.f){
|
|
CreateBullet(Bullet)(m.GetPos(),vf2d{0.f,ConfigFloat("Fly Across Attack.Attack Y Speed")},4,ConfigInt("Fly Across Attack.Poop Damage"),"birdpoop.png",m.OnUpperLevel(),false,INFINITY,false,false,WHITE,vf2d{1.f,1.25f})
|
|
.SetIframeTimeOnHit(0.25f)EndBullet;
|
|
const int extraPoopBitsCount=util::random()%6;
|
|
for(int i=0;i<extraPoopBitsCount;i++){
|
|
const bool RightDirection=util::random()%2;
|
|
float xOffset{0.f};
|
|
if(RightDirection)xOffset=1.f;
|
|
else xOffset=-1.f;
|
|
xOffset*=9.f;
|
|
CreateBullet(Bullet)(m.GetPos()+vf2d{xOffset+util::random_range(-3.f,3.f),-util::random(10.f)-4.f},vf2d{0.f,ConfigFloat("Fly Across Attack.Attack Y Speed")},1,ConfigInt("Fly Across Attack.Poop Damage"),"birdpoop.png",m.OnUpperLevel(),false,INFINITY,false,false,WHITE,vf2d{util::random_range(0.2f,0.3f),util::random_range(0.2f,0.3f)},util::random(2*PI))
|
|
.SetIframeTimeOnHit(0.25f)EndBullet;
|
|
}
|
|
m.F(A::SHOOT_TIMER)=ConfigFloat("Fly Across Attack.Landing Area");
|
|
}
|
|
if(m.ReachedTargetPos()){
|
|
m.phase=IDLE;
|
|
m.RemoveBuff(BuffType::SPEEDBOOST);
|
|
m.targetAcquireTimer=0.f;
|
|
}
|
|
}break;
|
|
case TORNADO_ATTACK_PREPARE:{
|
|
m.targetAcquireTimer=20.f;
|
|
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
|
|
if(m.ReachedTargetPos()){
|
|
m.phase=TORNADO_ATTACK;
|
|
m.PerformAnimation("ATTACK",Direction::SOUTH);
|
|
m.targetAcquireTimer=0.f;
|
|
m.F(A::CASTING_TIMER)=ConfigFloat("Tornado Attack.Attack Duration");
|
|
|
|
const int tornadoRingCount=Config("Tornado Attack.Tornados").GetKeys().size();
|
|
for(int tornadoRingId=1;tornadoRingId<=tornadoRingCount;tornadoRingId++){
|
|
|
|
//From strategy documentation:
|
|
//# For each Ring: Distance from Boss in Units, # of tornados, Rotation Speed (degrees/sec).
|
|
const float tornadoDistance=ConfigPixelsArr(std::format("Tornado Attack.Tornados.Ring{}",tornadoRingId),0);
|
|
const int tornadoCount=ConfigIntArr(std::format("Tornado Attack.Tornados.Ring{}",tornadoRingId),1);
|
|
const float tornadoRotSpd=util::degToRad(ConfigFloatArr(std::format("Tornado Attack.Tornados.Ring{}",tornadoRingId),2)); //It's in degrees, let's convert it to radians now.
|
|
|
|
const float randomRotDir=util::random(2*PI);
|
|
|
|
CreateBullet(Tornado)(m.GetPos(),tornadoDistance,randomRotDir,tornadoRotSpd,m.GetAttack(),ConfigFloat("Tornado Attack.Knockup Duration"),ConfigFloat("Tornado Attack.Knockback Amount"),ConfigFloat("Tornado Attack.Attack Duration"),m.OnUpperLevel(),false,WHITE)EndBullet;
|
|
BULLET_LIST.back()->SetFadeinTime(ConfigFloat("Tornado Fade-In Time"));
|
|
}
|
|
}
|
|
}break;
|
|
case TORNADO_ATTACK:{
|
|
|
|
}break;
|
|
case WIND_ATTACK:{
|
|
|
|
}break;
|
|
case HALFHEALTH_PHASE:{
|
|
|
|
}break;
|
|
}
|
|
} |