Implement Pirate Marauder AI. Release Build 11609.

master
sigonasr2 5 months ago
parent 9509f317c3
commit d6c2b6c87f
  1. 11
      Adventures in Lestoria/Monster.cpp
  2. 10
      Adventures in Lestoria/Monster.h
  3. 95
      Adventures in Lestoria/Pirate_Marauder.cpp
  4. 2
      Adventures in Lestoria/Version.h
  5. 24
      Adventures in Lestoria/assets/Campaigns/3_1.tmx
  6. 23
      Adventures in Lestoria/assets/config/MonsterStrategies.txt
  7. 29
      Adventures in Lestoria/assets/config/Monsters.txt
  8. 6
      Adventures in Lestoria/assets/config/audio/events.txt
  9. 2
      Adventures in Lestoria/olcUTIL_Geometry2D.h
  10. 6
      Adventures in Lestoria/util.h
  11. BIN
      x64/Release/Adventures in Lestoria.exe

@ -169,11 +169,16 @@ void Monster::PerformAnimation(const std::string_view animationName){
if(HasFourWaySprites())animation.ChangeState(internal_animState,std::format("{}_{}",animationName,int(facingDirection)));
else animation.ChangeState(internal_animState,std::string(animationName));
}
//Performs an animation, optionally changes the facing direction of this monster.
void Monster::PerformAnimation(const std::string_view animationName,const Direction facingDir){
facingDirection=facingDir;
PerformAnimation(animationName);
}
void Monster::PerformAnimation(const std::string_view animationName,const vf2d targetFacingDir){
facingDirection=GetFacingDirectionToTarget(targetFacingDir);
PerformAnimation(animationName);
}
bool Monster::_SetX(float x,const bool monsterInvoked){
vf2d newPos={x,pos.y};
vi2d tilePos=vi2d(newPos/float(game->GetCurrentMapData().tilewidth))*game->GetCurrentMapData().tilewidth;
@ -1637,3 +1642,7 @@ void Monster::ResetCurseOfDeathDamage(){
void Monster::AddAddedVelocity(vf2d vel){
this->addedVel+=vel;
}
void Monster::MoveForward(const vf2d&moveForwardVec,const float fElapsedTime){
if(moveForwardVec.mag()==0.f)ERR("WARNING! Passed a zero length vector into Move Forward! THIS IS NOT ALLOWED!");
SetPos(pos+moveForwardVec.norm()*100.f*fElapsedTime*GetMoveSpdMult());
}

@ -128,7 +128,8 @@ public:
void PerformNPCLeftAnimation();
void PerformNPCRightAnimation();
void PerformAnimation(const std::string_view animationName);
void PerformAnimation(const std::string_view animationName,const Direction facingDir);
void PerformAnimation(const std::string_view animationName,const Direction facingDir); //Performs an animation, optionally changes the facing direction of this monster.
void PerformAnimation(const std::string_view animationName,const vf2d targetFacingDir); //Faces a target vector while performing an animation.
const Animate2D::FrameSequence&GetCurrentAnimation()const;
const Animate2D::FrameSequence&GetAnimation(const std::string_view animationName)const;
const bool OnUpperLevel()const;
@ -162,8 +163,8 @@ public:
const std::function<void(Monster&,float,std::string)>&GetStrategy()const;
void SetSize(float newSize,bool immediate=true);
geom2d::circle<float>BulletCollisionHitbox();
void SetStrategyDrawFunction(std::function<void(AiL*,Monster&,const std::string&)>func);
void SetStrategyDrawOverlayFunction(std::function<void(AiL*,Monster&,const std::string&)>func);
void SetStrategyDrawFunction(std::function<void(AiL*game,Monster&monster,const std::string&strategy)>func);
void SetStrategyDrawOverlayFunction(std::function<void(AiL*game,Monster&monster,const std::string&strategy)>func);
std::function<void(AiL*,Monster&,const std::string&)>strategyDraw=[](AiL*pge,Monster&m,const std::string&strategy){};
std::function<void(AiL*,Monster&,const std::string&)>strategyDrawOverlay=[](AiL*pge,Monster&m,const std::string&strategy){};
const ItemAttributable&GetStats()const;
@ -227,6 +228,7 @@ public:
const bool FadeoutWhenStandingBehind()const;
const bool FaceTarget()const;
void ResetCurseOfDeathDamage();
void MoveForward(const vf2d&moveForwardVec,const float fElapsedTime); //Moves the monster forward in given vector direction (will be auto-normalized) applying speeed boosts and other proper movement requirements as if you wanted to move on a frame-by-frame basis.
private:
//NOTE: Marking a monster for deletion does not trigger any death events. It just simply removes the monster from the field!!
// The way this works is that monsters marked for deletion will cause the monster update loop to detect there's at least one or more monsters that must be deleted and will call erase_if on the list at the end of the iteration loop.
@ -280,7 +282,7 @@ private:
NPCData npcData;
float lastPathfindingCooldown=0.f;
std::function<bool(GameEvent&,Monster&,const std::string&)>strategyDeathFunc{};
void SetStrategyDeathFunction(std::function<bool(GameEvent&,Monster&,const std::string&)>func);
void SetStrategyDeathFunction(std::function<bool(GameEvent&event,Monster&monster,const std::string&strategyName)>func);
//If you are trying to change a Get() stat, use the STAT_UP buff (and the optional argument) to supply an attribute you want to apply.
const ItemAttribute&GetBonusStat(std::string_view attr)const;
//Returns false if the monster could not be moved to the requested location due to collision.

@ -48,19 +48,64 @@ INCLUDE_game
void Monster::STRATEGY::PIRATE_MARAUDER(Monster&m,float fElapsedTime,std::string strategy){
enum PhaseName{
INIT,
MOVE,
WINDUP,
RECOVERY,
};
enum AttackType{
STAB,
SLASH
LEAP,
PREPARE_WHIRLWIND,
WHIRLWIND,
};
switch(m.phase){
case INIT:{
m.F(A::CHASE_TIMER)=ConfigFloat("Ability Choose Timer");
m.phase=MOVE;
}break;
case MOVE:{
float distToPlayer=m.GetDistanceFrom(game->GetPlayer()->GetPos());
m.F(A::CHASE_TIMER)-=fElapsedTime;
if(m.F(A::CHASE_TIMER)<=0.f){
m.I(A::ABILITY_COUNT)++;
m.F(A::CHASE_TIMER)=ConfigFloat("Ability Choose Timer");
}
if(m.I(A::ABILITY_COUNT)>0){
float roll{util::random_range(0.f,100.f)};
float distanceToPlayer{util::distance(m.GetPos(),game->GetPlayer()->GetPos())};
std::pair<float,float>jumpAttackRollRange{0.f,ConfigFloat("Jump Attack Chance")};
std::pair<float,float>whirlwindAttackRollRange{jumpAttackRollRange.second,jumpAttackRollRange.second+ConfigFloat("Whirlwind Attack Chance")};
if(roll<jumpAttackRollRange.second&&distanceToPlayer>=ConfigFloatArr("Jump Attack Ranges",0)/100.f*24.f&&distanceToPlayer<=ConfigFloatArr("Jump Attack Ranges",1)/100.f*24.f){
m.phase=LEAP;
m.V(A::JUMP_TARGET_POS)=game->GetPlayer()->GetPos();
m.I(A::ABILITY_COUNT)--;
const float impactArea{ConfigFloat("Jump Attack Impact Area")};
m.SetStrategyDrawFunction([impactArea](AiL*game,Monster&monster,const std::string&strategy){
game->view.DrawRotatedDecal(monster.V(A::JUMP_TARGET_POS),GFX["range_indicator.png"].Decal(),0.f,GFX["range_indicator.png"].Sprite()->Size()/2.f,vf2d{1,1}*(impactArea/100.f),{255,0,0,96});
});
m.F(A::JUMP_MOVE_TO_TARGET_TIMER)=0.f;
m.V(A::PREV_POS)=m.GetPos();
m.PerformAnimation("LEAPING",m.V(A::JUMP_TARGET_POS));
break;
}else
if(roll>=whirlwindAttackRollRange.first&&roll<whirlwindAttackRollRange.second
&&distanceToPlayer>=ConfigFloatArr("Whirlwind Attack Ranges",0)/100.f*24.f&&distanceToPlayer<=ConfigFloatArr("Whirlwind Attack Ranges",1)/100.f*24.f){
m.phase=PREPARE_WHIRLWIND;
m.I(A::ABILITY_COUNT)--;
vf2d aimingTarget{game->GetPlayer()->GetPos()};
if(aimingTarget==m.GetPos()){ //Handle edge case.
aimingTarget=m.GetPos()+vf2d{1.f,util::random(2*PI)}.cart();
}
m.V(A::PATH_DIR)=util::pointTo(m.GetPos(),aimingTarget);
m.PerformAnimation("SPIN",m.V(A::LOCKON_POS));
m.F(A::CASTING_TIMER)=ConfigFloat("Whirlwind Chargeup Time");
m.AddBuff(BuffType::SPEEDBOOST,ConfigFloat("Whirlwind Spin Time"),ConfigFloat("Whirlwind Bonus Movespd")/100.f);
break;
}
}
NormalBehavior:
if(distToPlayer>ConfigFloat("Attack Spacing")/100.f*24){
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
}else{
@ -86,5 +131,45 @@ void Monster::STRATEGY::PIRATE_MARAUDER(Monster&m,float fElapsedTime,std::string
if(m.F(A::CASTING_TIMER)<=0){m.PerformIdleAnimation(m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));}
if(m.F(A::RECOVERY_TIME)<=0)m.phase=MOVE;
}break;
case LEAP:{
m.F(A::JUMP_MOVE_TO_TARGET_TIMER)+=fElapsedTime;
const float halfJumpTime{ConfigFloat("Jump Attack Duration")/2.f};
if(m.F(A::JUMP_MOVE_TO_TARGET_TIMER)<halfJumpTime)m.SetZ(util::smoothstep(0,ConfigFloat("Jump Attack Height"),m.F(A::JUMP_MOVE_TO_TARGET_TIMER)/halfJumpTime));
else m.SetZ(util::smoothstep(ConfigFloat("Jump Attack Height"),0,(m.F(A::JUMP_MOVE_TO_TARGET_TIMER)-halfJumpTime)/halfJumpTime));
m.SetPos(m.V(A::PREV_POS).lerp(m.V(A::JUMP_TARGET_POS),m.F(A::JUMP_MOVE_TO_TARGET_TIMER)/ConfigFloat("Jump Attack Duration")));
if(m.F(A::JUMP_MOVE_TO_TARGET_TIMER)>=ConfigFloat("Jump Attack Duration")){
SoundEffect::PlaySFX("Leap Land",m.GetPos());
game->Hurt(m.GetPos(),ConfigFloat("Jump Attack Impact Area")/100.f*24.f,m.GetAttack(),m.OnUpperLevel(),m.GetZ(),HurtType::PLAYER);
game->ProximityKnockback(m.GetPos(),ConfigFloat("Jump Attack Impact Area")/100.f*24.f,ConfigFloat("Jump Attack Knockback Amount"),HurtType::MONSTER|HurtType::PLAYER);
m.SetZ(0.f);
m.SetPos(m.V(A::JUMP_TARGET_POS));
m.phase=MOVE;
m.PerformIdleAnimation();
m.SetStrategyDrawFunction([](AiL*game,Monster&monster,const std::string&strategy){});
}
}break;
case PREPARE_WHIRLWIND:{
m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0.f){
m.phase=WHIRLWIND;
m.F(A::CHASE_TIMER)=0.f;
m.PerformAnimation("SPINNING");
}
}break;
case WHIRLWIND:{
m.F(A::CHASE_TIMER)+=fElapsedTime;
if(m.F(A::CHASE_TIMER)>=ConfigFloat("Whirlwind Spin Time")){
m.phase=MOVE;
m.PerformIdleAnimation();
}
m.MoveForward(m.V(A::PATH_DIR),fElapsedTime);
const HurtList hurtTargets{game->Hurt(m.GetPos(),ConfigFloat("Whirlwind Radius")/100.f*24.f,m.GetAttack(),m.OnUpperLevel(),m.GetZ(),HurtType::PLAYER)};
for(auto&[target,isHurt]:hurtTargets){
if(std::holds_alternative<Player*>(target)){
(std::get<Player*>(target))->ApplyIframes(0.5f);
}else ERR("WARNING! Somehow ended up with a non-player entity for whirlwind damage check! THIS SHOULD NOT BE HAPPENING!")
}
game->ProximityKnockback(m.GetPos(),ConfigFloat("Whirlwind Radius")/100.f*24.f,ConfigFloat("Whirlwind Knockback Amount"),HurtType::MONSTER|HurtType::PLAYER);
}break;
}
}

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1
#define VERSION_MINOR 3
#define VERSION_PATCH 0
#define VERSION_BUILD 11602
#define VERSION_BUILD 11609
#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="335" height="165" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="9">
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="335" height="165" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="13">
<properties>
<property name="Background Music" propertytype="BGM" value="mountain"/>
<property name="Level Type" type="int" propertytype="LevelType" value="0"/>
@ -9,6 +9,7 @@
<tileset firstgid="4533" source="../maps/palm_trees.tsx"/>
<tileset firstgid="4617" source="../maps/objects.tsx"/>
<tileset firstgid="5400" source="../maps/24x24_Waterfall.tsx"/>
<tileset firstgid="5480" source="../maps/Monsters.tsx"/>
<layer id="2" name="Layer 1" width="335" height="165">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2312,1736,1785,1785,1785,1735,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,1736,1785,1785,1785,1785,1785,1785,1785,1735,2312,2312,2312,2312,2312,2312,2312,2312,1734,2298,2298,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1732,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2164,2165,2113,2113,2165,2112,2113,2164,2312,2312,2312,2312,2312,2312,2312,2312,2312,1734,2298,2298,2298,2298,2298,2298,1732,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,2312,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@ -861,13 +862,28 @@
<property name="Upper?" type="bool" value="false"/>
</properties>
</object>
<object id="5" template="../maps/Monsters/Pirate.tx" x="492" y="1452">
<object id="8" name="Spawn Zone" type="SpawnGroup" x="180" y="1326" width="426" height="306">
<ellipse/>
</object>
<object id="9" template="../maps/Monsters/Pirate Marauder.tx" x="474" y="1398">
<properties>
<property name="spawner" type="object" value="8"/>
</properties>
</object>
<object id="8" name="Spawn Zone" type="SpawnGroup" x="180" y="1326" width="426" height="306">
<ellipse/>
<object id="10" template="../maps/Monsters/Pirate Marauder.tx" x="474" y="1350">
<properties>
<property name="spawner" type="object" value="8"/>
</properties>
</object>
<object id="11" template="../maps/Monsters/Pirate Marauder.tx" x="456" y="1494">
<properties>
<property name="spawner" type="object" value="8"/>
</properties>
</object>
<object id="12" template="../maps/Monsters/Pirate Marauder.tx" x="480" y="1566">
<properties>
<property name="spawner" type="object" value="8"/>
</properties>
</object>
</objectgroup>
</map>

@ -1032,6 +1032,29 @@ MonsterStrategy
}
Pirate Marauder
{
# How long before adding an ability choice charge to perform abilities with.
Ability Choose Timer = 8s
Jump Attack Chance = 25%
Whirlwind Attack Chance = 25%
Whirlwind Chargeup Time = 0.2s
Whirlwind Bonus Movespd = 60%
# Spin time derived from 200% movespd (60% bonus movespd + 125% base movespd)
Whirlwind Spin Time = 1.5s
Whirlwind Knockback Amount = 140
Whirlwind Radius = 200
# Min, Max Range
Whirlwind Attack Ranges = 400, 700
Jump Attack Height = 12
Jump Attack Duration = 0.4s
Jump Attack Impact Area = 150
Jump Attack Knockback Amount = 120
# Min, Max Range
Jump Attack Ranges = 400, 9999
# Distance from player to run to before swinging weapon.
Attack Spacing = 50

@ -1249,6 +1249,8 @@ Monsters
XP = 33
Collision Radius = 12
Strategy = Goblin Dagger
#### Script Override ####
@ -1327,6 +1329,8 @@ Monsters
XP = 39
Collision Radius = 12
Strategy = Pirate Marauder
# Wait time override for Run Towards strategy.
@ -1338,6 +1342,17 @@ Monsters
# Setting this to true means every four rows indicates one animation, the ordering of the directions is: NORTH, EAST, SOUTH, WEST
4-Way Spritesheet = True
#### Script Override ####
# Distance from player to run to before swinging weapon.
Attack Spacing = 100
# Slash Attack windup time
Slash Windup Time = 0.2s
Dagger Slash Image = "pirate_slash.png"
#########################
Animations
{
# Frame Count, Frame Speed (s), Frame Cycling (Repeat,OneShot,PingPong,Reverse,ReverseOneShot)
@ -1372,6 +1387,8 @@ Monsters
XP = 59
Collision Radius = 12
Strategy = Pirate Captain
# Wait time override for Run Towards strategy.
@ -1419,6 +1436,8 @@ Monsters
XP = 37
Collision Radius = 12
Strategy = Pirate Buccaneer
# Wait time override for Run Towards strategy.
@ -1461,6 +1480,8 @@ Monsters
XP = 37
Collision Radius = 10
Strategy = Crab
# Wait time override for Run Towards strategy.
@ -1502,6 +1523,8 @@ Monsters
XP = 61
Collision Radius = 10
Strategy = Crab
# Wait time override for Run Towards strategy.
@ -1543,6 +1566,8 @@ Monsters
XP = 6
Collision Radius = 9
Strategy = Seagull
# Wait time override for Run Towards strategy.
@ -1584,6 +1609,8 @@ Monsters
XP = 24
Collision Radius = 10
Strategy = Sandworm
# Wait time override for Run Towards strategy.
@ -1627,6 +1654,8 @@ Monsters
XP = 0
Collision Radius = 7
Strategy = Parrot
# Instead of the monster dying, it gets knocked unconscious

@ -237,6 +237,12 @@ Events
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = rockbreak.ogg, 90%, 40%, 50%
}
Leap Land
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = rockbreak.ogg, 80%
}
Level Up
{
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)

@ -191,6 +191,7 @@
#include <optional>
#include <cassert>
#include <numeric>
#include "Error.h"
#ifndef OLC_V2D_TYPE
#define OLC_V2D_TYPE
@ -246,6 +247,7 @@ namespace olc
inline v_2d norm() const
{
auto r = 1 / mag();
if(!std::isfinite(r))ERR("WARNING! Passing a value that creates an unusable infinite value while normalizing! THIS SHOULD NOT BE HAPPENING!");
return v_2d(x * r, y * r);
}

@ -75,6 +75,12 @@ namespace olc::util{
}
#pragma endregion
template<class T,class U>
inline auto smoothstep(const T val1,const U val2,const float t){
auto x{decltype(val1+val2)(1-pow(1-t,3))};
return val1*(1-x)+val2*x;
}
std::string timerStr(float time);
std::string WrapText(PixelGameEngine*pge,std::string str,int width,bool proportional,vd2d scale);
std::u32string WrapText(PixelGameEngine*pge,std::u32string str,int width,Font&font,vd2d scale);

Loading…
Cancel
Save