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.
220 lines
12 KiB
220 lines
12 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 "SoundEffect.h"
|
|
|
|
INCLUDE_game
|
|
INCLUDE_BULLET_LIST
|
|
|
|
using A=Attribute;
|
|
|
|
/*
|
|
* Every 2 seconds after completing an attack do one of the following attacks:
|
|
Casts a Stone Pillar on the location of the Player: radius 350. Hits with 3 seconds delay. Becomes an solid object (Player colision) and decays after 5 seconds again.
|
|
If the Stone pillar would hit the Stone Elemental it will move out of its range.
|
|
Shoot a Stone (bullet) in the direction of the target.
|
|
The Stone gets created in form of a Cone and tracks the player movement without moving for the first second.
|
|
Once it locks it doesnt move for another second. then it starts flying towards the player with 3x the normal Bullet speed.
|
|
Dive under earth and show up 1 second later in a random location 400-700 away from player. while emerging shoot a ring of bullets (0.5x Attack)
|
|
(maybe emerge with 200 away from anything with colision to avoid the Elemental getting stuck? Other solutions for that are welcome aswell)
|
|
if range to player > 1200 always use 3rd attack
|
|
*/
|
|
|
|
void Monster::STRATEGY::STONE_ELEMENTAL(Monster&m,float fElapsedTime,std::string strategy){
|
|
enum PhaseName{
|
|
INITIALIZE,
|
|
WAITING,
|
|
STONE_PILLAR_CAST,
|
|
SHOOT_STONE_CAST,
|
|
DIVE_UNDERGROUND_DIG,
|
|
DIVE_UNDERGROUND_MOVE,
|
|
DIVE_UNDERGROUND_SURFACE,
|
|
};
|
|
|
|
auto ReturnToWaitingPhase=[&](){
|
|
SETPHASE(WAITING);
|
|
m.PerformIdleAnimation();
|
|
m.F(A::ATTACK_COOLDOWN)=0.f;
|
|
};
|
|
|
|
switch(PHASE()){
|
|
case INITIALIZE:{
|
|
m.F(A::ATTACK_COOLDOWN)=util::random(ConfigFloat("Attack Wait Time")/1.5f);
|
|
SETPHASE(WAITING);
|
|
}break;
|
|
case WAITING:{
|
|
m.F(A::ATTACK_COOLDOWN)+=fElapsedTime;
|
|
|
|
if(m.F(A::ATTACK_COOLDOWN)>=ConfigFloat("Attack Wait Time")){
|
|
int randomAttackChoice=util::random()%3;
|
|
|
|
float distToPlayer=util::distance(m.GetPos(),game->GetPlayer()->GetPos());
|
|
if(distToPlayer>=ConfigPixels("Auto Dive Range"))randomAttackChoice=2; //Force dig attack if too far away.
|
|
|
|
switch(randomAttackChoice){
|
|
case 0:{
|
|
m.PerformAnimation("STONE PILLAR CAST");
|
|
m.SIZET(A::LOOPING_SOUND_ID)=SoundEffect::PlayLoopingSFX("Rock Toss Cast",m.GetPos());
|
|
SETPHASE(STONE_PILLAR_CAST);
|
|
m.F(A::CASTING_TIMER)=ConfigFloat("Stone Pillar Cast Time");
|
|
m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos();
|
|
game->AddEffect(std::make_unique<SpellCircle>(m.V(A::LOCKON_POS),ConfigFloat("Stone Pillar Cast Time"),"range_indicator.png","spell_insignia.png",m.OnUpperLevel(),vf2d{1.f,1.f}*(MONSTER_DATA.at("Stone Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Pillar").GetSizeMult()/12.f)*1.25f,0.3f,vf2d{},ConfigPixel("Stone Pillar Spell Circle Color"),util::random(2*PI),util::degToRad(ConfigFloat("Stone Pillar Spell Circle Rotation Spd")),false,vf2d{1.f,1.f}*(MONSTER_DATA.at("Stone Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Pillar").GetSizeMult()/12.f)*0.9f,0.3f,vf2d{},ConfigPixel("Stone Pillar Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Stone Pillar Spell Insignia Rotation Spd"))),false);
|
|
}break;
|
|
case 1:{
|
|
m.PerformAnimation("ROCK TOSS CAST");
|
|
m.SIZET(A::LOOPING_SOUND_ID)=SoundEffect::PlayLoopingSFX("Rock Toss Cast",m.GetPos());
|
|
SETPHASE(SHOOT_STONE_CAST);
|
|
m.B(A::PLAYED_FLAG)=false;
|
|
m.F(A::CASTING_TIMER)=ConfigFloat("Rock Toss Track Time")+ConfigFloat("Rock Toss Wait Time");
|
|
CreateBullet(LevitatingRock)(m,game->GetPlayer()->GetPos(),1.f,0.f,ConfigPixels("Rock Toss Max Spawn Distance"),ConfigFloat("Rock Toss Track Time"),ConfigFloat("Rock Toss Wait Time"),ConfigFloat("Rock Toss Bullet Speed"),ConfigFloat("Rock Radius"),std::max(1,ConfigInt("Rock Toss Damage")/5),m.OnUpperLevel(),false,WHITE,vf2d{1,1})EndBullet;
|
|
const int masterRockInd=BULLET_LIST.size()-1;
|
|
CreateBullet(LevitatingRock)(m,game->GetPlayer()->GetPos(),1.f,util::degToRad(-20.f),ConfigPixels("Rock Toss Max Spawn Distance")*0.75f,ConfigFloat("Rock Toss Track Time"),ConfigFloat("Rock Toss Wait Time"),ConfigFloat("Rock Toss Bullet Speed"),ConfigFloat("Rock Radius")*0.6f,std::max(1,ConfigInt("Rock Toss Damage")/5),m.OnUpperLevel(),false,WHITE,vf2d{0.6f,0.6f})EndBullet;
|
|
CreateBullet(LevitatingRock)(m,game->GetPlayer()->GetPos(),1.f,util::degToRad(-40.f),ConfigPixels("Rock Toss Max Spawn Distance")*0.5f,ConfigFloat("Rock Toss Track Time"),ConfigFloat("Rock Toss Wait Time"),ConfigFloat("Rock Toss Bullet Speed"),ConfigFloat("Rock Radius")*0.6f,std::max(1,ConfigInt("Rock Toss Damage")/5),m.OnUpperLevel(),false,WHITE,vf2d{0.6f,0.6f})EndBullet;
|
|
CreateBullet(LevitatingRock)(m,game->GetPlayer()->GetPos(),1.f,util::degToRad(20.f),ConfigPixels("Rock Toss Max Spawn Distance")*0.75f,ConfigFloat("Rock Toss Track Time"),ConfigFloat("Rock Toss Wait Time"),ConfigFloat("Rock Toss Bullet Speed"),ConfigFloat("Rock Radius")*0.6f,std::max(1,ConfigInt("Rock Toss Damage")/5),m.OnUpperLevel(),false,WHITE,vf2d{0.6f,0.6f})EndBullet;
|
|
CreateBullet(LevitatingRock)(m,game->GetPlayer()->GetPos(),1.f,util::degToRad(40.f),ConfigPixels("Rock Toss Max Spawn Distance")*0.5f,ConfigFloat("Rock Toss Track Time"),ConfigFloat("Rock Toss Wait Time"),ConfigFloat("Rock Toss Bullet Speed"),ConfigFloat("Rock Radius")*0.6f,std::max(1,ConfigInt("Rock Toss Damage")/5),m.OnUpperLevel(),false,WHITE,vf2d{0.6f,0.6f})EndBullet;
|
|
auto bulletListEndIter{BULLET_LIST.end()};
|
|
for(int i=0;i<4;i++){ //This is going to assign the last four stones we created as slaves to the first rock so that their directions can all be changed together.
|
|
bulletListEndIter--;
|
|
((LevitatingRock*)(*bulletListEndIter).get())->AssignMaster((LevitatingRock*)BULLET_LIST[masterRockInd].get());
|
|
}
|
|
}break;
|
|
case 2:{
|
|
SoundEffect::PlaySFX("Dig",m.GetPos());
|
|
m.PerformAnimation("BURROW UNDERGROUND");
|
|
SETPHASE(DIVE_UNDERGROUND_DIG);
|
|
m.F(A::CASTING_TIMER)=m.GetCurrentAnimation().GetTotalAnimationDuration();
|
|
}break;
|
|
}
|
|
}
|
|
}break;
|
|
case STONE_PILLAR_CAST:{
|
|
m.F(A::CASTING_TIMER)-=fElapsedTime;
|
|
if(m.F(A::CASTING_TIMER)<=0.f){
|
|
SoundEffect::StopLoopingSFX(m.SIZET(A::LOOPING_SOUND_ID));
|
|
SoundEffect::PlaySFX("Pillar Rise",m.V(A::LOCKON_POS));
|
|
game->SpawnMonster(m.V(A::LOCKON_POS),MONSTER_DATA.at("Stone Pillar"),m.OnUpperLevel());
|
|
ReturnToWaitingPhase();
|
|
game->Hurt(m.V(A::LOCKON_POS),MONSTER_DATA.at("Stone Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Pillar").GetSizeMult(),m.GetAttack(),m.OnUpperLevel(),0.f,HurtType::PLAYER);
|
|
}
|
|
if(geom2d::overlaps(geom2d::circle<float>{m.V(A::LOCKON_POS),MONSTER_DATA.at("Stone Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Pillar").GetSizeMult()},geom2d::circle<float>{m.GetPos(),m.GetCollisionRadius()})){
|
|
geom2d::line<float>stonePillarCastLine{m.V(A::LOCKON_POS),m.GetPos()};
|
|
const vf2d targetWalkPos=stonePillarCastLine.rpoint(stonePillarCastLine.length()+48.f);
|
|
m.target=targetWalkPos;
|
|
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
|
|
m.PerformAnimation("STONE PILLAR CAST");
|
|
m.UpdateFacingDirection(targetWalkPos);
|
|
}else{
|
|
m.PerformAnimation("STONE PILLAR CAST");
|
|
m.UpdateFacingDirection(m.V(A::LOCKON_POS));
|
|
}
|
|
}break;
|
|
case SHOOT_STONE_CAST:{
|
|
m.F(A::CASTING_TIMER)-=fElapsedTime;
|
|
if(m.F(A::CASTING_TIMER)>=ConfigFloat("Rock Toss Wait Time")-ConfigFloat("Rock Toss Track Time")){
|
|
SoundEffect::StopLoopingSFX(m.SIZET(A::LOOPING_SOUND_ID));
|
|
m.PerformAnimation("STONE PILLAR CAST");
|
|
m.UpdateFacingDirection(game->GetPlayer()->GetPos());
|
|
}
|
|
if(m.F(A::CASTING_TIMER)>=ConfigFloat("Rock Toss Track Time")&&!m.B(A::PLAYED_FLAG)){
|
|
SoundEffect::PlaySFX("Rock Break",m.GetPos());
|
|
m.B(A::PLAYED_FLAG)=true;
|
|
}
|
|
if(m.F(A::CASTING_TIMER)<=0.f){
|
|
SoundEffect::StopLoopingSFX(m.SIZET(A::LOOPING_SOUND_ID));
|
|
ReturnToWaitingPhase();
|
|
}
|
|
}break;
|
|
case DIVE_UNDERGROUND_DIG:{
|
|
m.F(A::CASTING_TIMER)-=fElapsedTime;
|
|
if(m.F(A::CASTING_TIMER)<=0.f){
|
|
SETPHASE(DIVE_UNDERGROUND_MOVE);
|
|
float randomAngle=util::random(2*PI);
|
|
|
|
const float minDist=ConfigPixelsArr("Burrow Teleport Distance",0);
|
|
const float maxDist=ConfigPixelsArr("Burrow Teleport Distance",1);
|
|
const float distRange=maxDist-minDist;
|
|
|
|
float randomDist=util::random(distRange)+minDist;
|
|
|
|
m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos()+vf2d{randomDist,randomAngle}.cart();
|
|
m.target=m.V(A::LOCKON_POS);
|
|
m.targetAcquireTimer=INFINITY;
|
|
m.SetState(State::MOVE_TOWARDS);
|
|
|
|
const float baseMoveSpd=100.f*m.GetMoveSpdMult();
|
|
const float distToTarget=geom2d::line<float>(m.GetPos(),m.V(A::LOCKON_POS)).length();
|
|
const float targetMoveSpdRatio=std::max(0.f,distToTarget/baseMoveSpd/ConfigFloat("Burrow Wait Time")-1);
|
|
m.AddBuff(BuffType::SPEEDBOOST,ConfigFloat("Burrow Wait Time"),targetMoveSpdRatio);
|
|
m.F(A::CASTING_TIMER)=ConfigFloat("Burrow Wait Time");
|
|
m.B(A::IGNORE_DEFAULT_ANIMATIONS)=true;
|
|
m.ApplyIframes(ConfigFloat("Burrow Wait Time")+m.GetAnimation("RISE FROM UNDERGROUND").GetTotalAnimationDuration());
|
|
}
|
|
}break;
|
|
case DIVE_UNDERGROUND_MOVE:{
|
|
m.F(A::CASTING_TIMER)-=fElapsedTime;
|
|
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
|
|
|
|
if(m.F(A::CASTING_TIMER)<=0.f){
|
|
m.B(A::IGNORE_DEFAULT_ANIMATIONS)=false;
|
|
SoundEffect::PlaySFX("Rise",m.GetPos());
|
|
m.PerformAnimation("RISE FROM UNDERGROUND");
|
|
SETPHASE(DIVE_UNDERGROUND_SURFACE);
|
|
m.targetAcquireTimer=0;
|
|
m.F(A::CASTING_TIMER)=m.GetCurrentAnimation().GetTotalAnimationDuration();
|
|
|
|
const float bulletAngRandomOffset{util::random(PI/2)};
|
|
for(int i=0;i<ConfigInt("Burrow Ring Bullet Count");i++){
|
|
const float bulletAngle=((2*PI)/ConfigInt("Burrow Ring Bullet Count"))*i+bulletAngRandomOffset;
|
|
CreateBullet(Bullet)(m.GetPos()+vf2d{0,4*m.GetSizeMult()},vf2d{ConfigFloat("Burrow Ring Bullet Speed"),bulletAngle}.cart(),ConfigFloat("Burrow Ring Bullet Size"),ConfigInt("Burrow Ring Bullet Damage"),m.OnUpperLevel(),false,ConfigPixel("Burrow Ring Bullet Color"),vf2d{ConfigFloat("Burrow Ring Bullet Size")/3,ConfigFloat("Burrow Ring Bullet Size")/3})EndBullet;
|
|
BULLET_LIST.back()->SetIframeTimeOnHit(0.15f);
|
|
}
|
|
}
|
|
}break;
|
|
case DIVE_UNDERGROUND_SURFACE:{
|
|
m.F(A::CASTING_TIMER)-=fElapsedTime;
|
|
if(m.F(A::CASTING_TIMER)<=0.f){
|
|
ReturnToWaitingPhase();
|
|
}
|
|
}break;
|
|
}
|
|
} |