Wolf behavior implemented. Fixed facing direction behavior for AI scripts.

pull/30/head
sigonasr2 1 year ago
parent acaf1bc3bf
commit db9c35f813
  1. 2
      Adventures in Lestoria/AdventuresInLestoria.cpp
  2. 6
      Adventures in Lestoria/Attributable.h
  3. 2
      Adventures in Lestoria/DEFINES.h
  4. 23
      Adventures in Lestoria/Monster.cpp
  5. 4
      Adventures in Lestoria/Monster.h
  6. 6
      Adventures in Lestoria/MonsterAttribute.h
  7. 4
      Adventures in Lestoria/Pathfinding.cpp
  8. 4
      Adventures in Lestoria/Player.cpp
  9. 10
      Adventures in Lestoria/Player.h
  10. 3
      Adventures in Lestoria/RunTowards.cpp
  11. 12
      Adventures in Lestoria/ShootAfar.cpp
  12. 2
      Adventures in Lestoria/TODO.txt
  13. 2
      Adventures in Lestoria/Version.h
  14. 2
      Adventures in Lestoria/Wizard.cpp
  15. 70
      Adventures in Lestoria/Wolf.cpp
  16. 6
      Adventures in Lestoria/assets/Campaigns/1_1_v2.tmx
  17. 6
      Adventures in Lestoria/assets/config/MonsterStrategies.txt
  18. 2
      Adventures in Lestoria/assets/config/Monsters.txt
  19. 5
      Adventures in Lestoria/olcUTIL_Geometry2D.h

@ -1983,7 +1983,7 @@ geom2d::rect<float>AiL::GetTileCollision(MapName map,vf2d pos,bool upperLevel){
int tileID=layer.tiles[int(pos.y)/GetCurrentMapData().tilewidth][int(pos.x)/GetCurrentMapData().tilewidth]-1;
if(tileID!=-1&&GetTileSheet(map,tileID%1000000).tileset->collision.find(tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1)!=GetTileSheet(map,tileID%1000000).tileset->collision.end()){
geom2d::rect<float>collisionRect=GetTileSheet(map,tileID%1000000).tileset->collision[tileID%1000000-GetTileSheet(map,tileID%1000000).firstgid+1].collision;
if(foundRect.pos==NO_COLLISION.pos&&foundRect.size==NO_COLLISION.size){
if(foundRect==NO_COLLISION){
foundRect=collisionRect;
}else{
//When we find another rectangle in the same square, we expand it to consume the area, whichever tile creates a larger surface or the combination of the two.

@ -74,4 +74,10 @@ public:
}
return std::get<vf2d>(attributes[a]);
};
inline std::vector<std::any>&GetVec(Attribute a){
if(attributes.count(a)==0){
attributes[a]=std::vector<std::any>{};
}
return std::get<std::vector<std::any>>(attributes[a]);
};
};

@ -65,7 +65,7 @@ using BackdropName=std::string;
#define ACCESS_PLAYER Player*p=game->GetPlayer();
#define VARIANTS float,int,std::string,bool,vf2d
#define VARIANTS float,int,std::string,bool,vf2d,std::vector<std::any>
#undef INFINITE
#define INFINITE 999999

@ -125,7 +125,7 @@ bool Monster::_SetX(float x,const bool monsterInvoked){
vf2d newPos={x,pos.y};
vi2d tilePos=vi2d(newPos/float(game->GetCurrentMapData().tilewidth))*game->GetCurrentMapData().tilewidth;
geom2d::rect<float>collisionRect=game->GetTileCollision(game->GetCurrentLevel(),newPos,upperLevel);
if(collisionRect.pos==game->NO_COLLISION.pos&&collisionRect.size==game->NO_COLLISION.size){
if(collisionRect==game->NO_COLLISION){
pos.x=std::clamp(x,game->GetCurrentMapData().tilewidth/2.f*GetSizeMult(),float(game->GetCurrentMapData().width*game->GetCurrentMapData().tilewidth-game->GetCurrentMapData().tilewidth/2.f*GetSizeMult()));
Moved();
return true;
@ -155,7 +155,7 @@ bool Monster::_SetY(float y,const bool monsterInvoked){
vf2d newPos={pos.x,y};
vi2d tilePos=vi2d(newPos/float(game->GetCurrentMapData().tilewidth))*game->GetCurrentMapData().tilewidth;
geom2d::rect<float>collisionRect=game->GetTileCollision(game->GetCurrentLevel(),newPos,upperLevel);
if(collisionRect.pos==game->NO_COLLISION.pos&&collisionRect.size==game->NO_COLLISION.size){
if(collisionRect==game->NO_COLLISION){
pos.y=std::clamp(y,game->GetCurrentMapData().tilewidth/2.f*GetSizeMult(),float(game->GetCurrentMapData().height*game->GetCurrentMapData().tilewidth-game->GetCurrentMapData().tilewidth/2.f*GetSizeMult()));
Moved();
return true;
@ -267,6 +267,15 @@ bool Monster::Update(float fElapsedTime){
Key Monster::GetFacingDirection(){
return facingDirection;
}
void Monster::UpdateFacingDirection(vf2d facingTargetPoint){
if(facingTargetPoint.x>GetPos().x){
facingDirection=RIGHT;
}
if(facingTargetPoint.x<GetPos().x){
facingDirection=LEFT;
}
}
void Monster::Draw(){
if(GetZ()>0){
vf2d shadowScale=vf2d{8*GetSizeMult()/3.f,1}/std::max(1.f,GetZ()/24);
@ -458,6 +467,10 @@ void Monster::AddBuff(BuffType type,float duration,float intensity){
buffList.push_back(Buff{type,duration,intensity});
}
void Monster::RemoveBuff(BuffType type){
std::erase_if(buffList,[&](const Buff&buff){return buff.type==type;});
}
bool Monster::StartPathfinding(float pathingTime){
SetState(State::PATH_AROUND);
path=game->pathfinder.Solve_WalkPath(pos,target,12,OnUpperLevel());
@ -480,11 +493,7 @@ void Monster::PathAroundBehavior(float fElapsedTime){
pathIndex=0;
targetAcquireTimer=0;
}
if(moveTowardsLine.vector().x>0){
facingDirection=RIGHT;
} else {
facingDirection=LEFT;
}
UpdateFacingDirection(moveTowardsLine.end);
}else{
if(pathIndex>=path.points.size()-1){
//We have reached the end of the path!

@ -133,6 +133,8 @@ public:
bool IsAlive();
vf2d&GetTargetPos();
Key GetFacingDirection();
//Will make the monster face in the correct direction relative to a given target point to look at.
void UpdateFacingDirection(vf2d facingTargetPoint);
void Draw();
void DrawReflection(float drawRatioX,float multiplierX);
void Collision(Player*p);
@ -155,6 +157,8 @@ public:
void PathAroundBehavior(float fElapsedTime);
void AddBuff(BuffType type,float duration,float intensity);
std::vector<Buff>GetBuffs(BuffType buff);
//Removes all buffs of a given type.
void RemoveBuff(BuffType type);
State::State GetState();
void SetState(State::State newState);
static void InitializeStrategies();

@ -42,6 +42,7 @@ All rights reserved.
#define S(attr) GetString(attr)
#define B(attr) GetBool(attr)
#define V(attr) GetVf2d(attr)
#define VEC(attr) GetVec(attr)
enum class Attribute{
IFRAME_TIME_UPON_HIT, //When this is set, the monster gains iframes if they take damage based on the value this is set to.
@ -79,4 +80,9 @@ enum class Attribute{
PHASE,
LOCKON_WAITTIME,
LOCKON_POS,
TARGET_TIMER,
DISENGAGE_PATH,
PATH_INDEX,
PATH_DIR,
PREV_POS,
};

@ -63,13 +63,13 @@ void Pathfinding::Initialize(){
node.x = x; // ...because we give each node its own coordinates
node.y = y; // ...because we give each node its own coordinates
geom2d::rect<float>tile=game->GetTileCollision(game->GetCurrentLevel(),{float(x),float(y)});
if(tile.pos==game->NO_COLLISION.pos&&tile.size==game->NO_COLLISION.size){
if(tile==game->NO_COLLISION){
node.bObstacle=false;
}else{
node.bObstacle=geom2d::overlaps(vf2d{fmodf(x+gridSpacing.x/2,24),fmodf(y+gridSpacing.y/2,24)},tile);
}
tile=game->GetTileCollision(game->GetCurrentLevel(),{float(x),float(y)},true);
if(tile.pos==game->NO_COLLISION.pos&&tile.size==game->NO_COLLISION.size){
if(tile==game->NO_COLLISION){
node.bObstacleUpper=false;
}else{
node.bObstacleUpper=geom2d::overlaps(vf2d{fmodf(x,24),fmodf(y,24)},tile);

@ -110,7 +110,7 @@ bool Player::_SetX(float x,const bool playerInvoked){
vi2d tilePos=vi2d(newPos/float(game->GetCurrentMapData().tilewidth))*game->GetCurrentMapData().tilewidth;
geom2d::rect<float>collisionRect=game->GetTileCollision(game->GetCurrentLevel(),newPos,upperLevel);
#pragma region lambdas
auto NoTileCollisionExistsHere=[&](){return collisionRect.pos==game->NO_COLLISION.pos&&collisionRect.size==game->NO_COLLISION.size;};
auto NoTileCollisionExistsHere=[&](){return collisionRect==game->NO_COLLISION;};
#pragma endregion
if(NoTileCollisionExistsHere()){
pos.x=std::clamp(x,game->GetCurrentMapData().tilewidth/2.f*GetSizeMult(),float(game->GetCurrentMapData().width*game->GetCurrentMapData().tilewidth-game->GetCurrentMapData().tilewidth/2.f*GetSizeMult()));
@ -143,7 +143,7 @@ bool Player::_SetY(float y,const bool playerInvoked){
vi2d tilePos=vi2d(newPos/float(game->GetCurrentMapData().tilewidth))*game->GetCurrentMapData().tilewidth;
geom2d::rect<float>collisionRect=game->GetTileCollision(game->GetCurrentLevel(),newPos,upperLevel);
#pragma region lambdas
auto NoTileCollisionExistsHere=[&](){return collisionRect.pos==game->NO_COLLISION.pos&&collisionRect.size==game->NO_COLLISION.size;};
auto NoTileCollisionExistsHere=[&](){return collisionRect==game->NO_COLLISION;};
#pragma endregion
if(NoTileCollisionExistsHere()){
pos.y=std::clamp(y,game->GetCurrentMapData().tilewidth/2.f*GetSizeMult(),float(game->GetCurrentMapData().height*game->GetCurrentMapData().tilewidth-game->GetCurrentMapData().tilewidth/2.f*GetSizeMult()));

@ -159,11 +159,15 @@ public:
void AddBuff(BuffType type,float duration,float intensity,std::set<ItemAttribute>attr);
void AddBuff(BuffType type,float duration,float intensity,std::set<std::string>attr);
void AddBuff(BuffType type,float duration,float intensity,float timeBetweenTicks,std::function<void(AiL*,int)>repeatAction);
const std::vector<Buff>GetBuffs(BuffType buff)const;
const std::vector<Buff>GetStatBuffs(const std::vector<std::string>&attr)const;
void RemoveBuff(BuffType type); //Removes the first buff found.
void RemoveAllBuffs(BuffType type); //Removes all buffs of a certain type.
void RemoveAllBuffs(); //Remove every buff.
//Removes the first buff found.
void RemoveBuff(BuffType type);
//Removes all buffs of a certain type.
void RemoveAllBuffs(BuffType type);
//Remove every buff.
void RemoveAllBuffs();
void RecalculateEquipStats();

@ -58,6 +58,9 @@ void Monster::STRATEGY::RUN_TOWARDS(Monster&m,float fElapsedTime,std::string str
}
m.SetState(State::MOVE_TOWARDS);
}
if(geom2d::line(m.pos,m.target).length()>4.f){
m.UpdateFacingDirection(m.target);
}
switch(m.state){
case State::MOVE_TOWARDS:{
if(geom2d::line(m.pos,m.target).length()>100*fElapsedTime*m.GetMoveSpdMult()){

@ -101,11 +101,7 @@ void Monster::STRATEGY::SHOOT_AFAR(Monster&m,float fElapsedTime,std::string stra
if(line.length()<=24.f*ConfigInt("CloseInRange")/100.0f){
m.SetState(State::NORMAL);
}
if(moveTowardsLine.vector().x>0){
m.facingDirection=RIGHT;
} else {
m.facingDirection=LEFT;
}
m.UpdateFacingDirection(moveTowardsLine.end);
m.PerformJumpAnimation();
}break;
case State::MOVE_AWAY:{
@ -129,11 +125,7 @@ void Monster::STRATEGY::SHOOT_AFAR(Monster&m,float fElapsedTime,std::string stra
if((m.path.points.size()==0&&!m.canMove)||line.length()>=24.f*ConfigInt("Range")/100.f){
m.SetState(State::NORMAL);
}
if(moveTowardsLine.vector().x>0){
m.facingDirection=RIGHT;
} else {
m.facingDirection=LEFT;
}
m.UpdateFacingDirection(moveTowardsLine.end);
m.PerformJumpAnimation();
}break;
case State::PATH_AROUND:{

@ -12,6 +12,8 @@ Settings Menu
January 31st
============
Make new unlocked nodes more obvious, made neighboring nodes more obvious
Implement the rest of the enemy types:
- Baby Wolf
- Wolf

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 0
#define VERSION_MINOR 2
#define VERSION_PATCH 1
#define VERSION_BUILD 5761
#define VERSION_BUILD 5794
#define stringify(a) stringify_(a)
#define stringify_(a) #a

@ -124,7 +124,7 @@ void Wizard::InitializeClassAbilities(){
vi2d tilePos=vi2d(teleportPoint/float(game->GetCurrentMapData().tilewidth))*game->GetCurrentMapData().tilewidth;
geom2d::rect<float>collisionRect=game->GetTileCollision(game->GetCurrentLevel(),teleportPoint,p->OnUpperLevel());
#pragma region lambdas
auto NoTileCollisionExistsHere=[&](){return collisionRect.pos==game->NO_COLLISION.pos&&collisionRect.size==game->NO_COLLISION.size;};
auto NoTileCollisionExistsHere=[&](){return collisionRect==game->NO_COLLISION;};
#pragma endregion
collisionRect.pos+=tilePos;
#pragma region lambdas

@ -51,20 +51,74 @@ using A=Attribute;
void Monster::STRATEGY::WOLF(Monster&m,float fElapsedTime,std::string strategy){
switch(m.I(A::PHASE)){
case 0:{ //Run towards the player.
float distToPlayer = geom2d::line<float>(game->GetPlayer()->GetPos(),m.GetPos()).length();
if(distToPlayer<=ConfigFloat("Lockon Range")){
float distToPlayer=geom2d::line<float>(game->GetPlayer()->GetPos(),m.GetPos()).length();
if(distToPlayer<=ConfigFloat("Lockon Range")/100*24.f){
m.I(A::PHASE)=1;
m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos();
m.AddBuff(BuffType::)
m.V(A::LOCKON_POS)=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).upoint(1.2f);
m.AddBuff(BuffType::LOCKON_SPEEDBOOST,INFINITE,ConfigFloat("Lockon Speed Boost")/100);
m.F(A::TARGET_TIMER)=5.0f;
}
m.target=game->GetPlayer()->GetPos();
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
}break;
case 1:{ //Charge the player.
m.F(A::TARGET_TIMER)=std::max(0.f,m.F(A::TARGET_TIMER)-fElapsedTime);
float distToTarget=geom2d::line<float>(m.GetPos(),m.V(A::LOCKON_POS)).length();
if(distToTarget<=12.f||m.F(A::TARGET_TIMER)==0.f){
m.I(A::PHASE)=2;
m.F(A::RECOVERY_TIME)=ConfigFloat("Charge Recovery Time");
m.PerformIdleAnimation();
}else{
m.target=m.V(A::LOCKON_POS);
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
}
}break;
case 2:{ //Recovery period after charging.
m.F(A::RECOVERY_TIME)=std::max(0.f,m.F(A::RECOVERY_TIME)-fElapsedTime);
if(m.F(A::RECOVERY_TIME)==0.f){
m.RemoveBuff(BuffType::LOCKON_SPEEDBOOST);
m.AddBuff(BuffType::SPEEDBOOST,ConfigFloatArr("Disengage Speed Boost",1),ConfigFloatArr("Disengage Speed Boost",0)/100);
std::vector<vf2d>disengagePoints;
for(float angle=0.f;angle<360.f;angle+=22.5f){
float range=50;
vf2d checkLoc=vf2d{0,util::degToRad(angle)}.cart();
while(range<=ConfigFloat("Disengage Range")){
checkLoc=game->GetPlayer()->GetPos()+vf2d{range/100*24.f,util::degToRad(angle)}.cart();
if(game->GetTileCollision(game->GetCurrentMapName(),checkLoc,m.OnUpperLevel())!=game->NO_COLLISION){
break;
}
range+=50;
}
disengagePoints.push_back(checkLoc);
}
m.path.Initialize(disengagePoints);
if(disengagePoints.size()!=16)ERR(std::format("Disengage Points size was not equal to 16! Size: {}",disengagePoints.size()));
if(m.path.points.size()!=16)ERR(std::format("Path Points size was not equal to 16! Size: {}",m.path.points.size()));
m.I(A::PATH_DIR)=util::random()%2;
if(m.I(A::PATH_DIR)==0)m.I(A::PATH_DIR)=-1;
m.pathIndex=util::random()%disengagePoints.size();
m.I(A::PHASE)=3;
m.F(A::RUN_AWAY_TIMER)=ConfigFloat("Disengage Duration");
m.PerformJumpAnimation();
}
}break;
case 3:{
case 3:{ //Disengage.
m.F(A::RUN_AWAY_TIMER)=std::max(0.f,m.F(A::RUN_AWAY_TIMER)-fElapsedTime);
m.pathIndex+=m.I(A::PATH_DIR)*fElapsedTime*1.25f;
m.pathIndex=fmod(m.pathIndex,m.path.points.size());
if(m.pathIndex<0){ //When the modulus is negative, adding the total number of elements gets us to the correct index.
m.pathIndex=m.pathIndex+m.path.points.size();
}
if(m.F(A::RUN_AWAY_TIMER)==0.f){
m.I(A::PHASE)=0;
}
m.target=m.path.GetSplinePoint(m.pathIndex).pos;
geom2d::line<float>moveTowardsLine=geom2d::line(m.pos,m.path.GetSplinePoint(m.pathIndex).pos);
if(moveTowardsLine.length()>4.f){
m.UpdateFacingDirection(moveTowardsLine.end);
}
m.SetPos(m.pos+moveTowardsLine.vector().norm()*100*fElapsedTime*m.GetMoveSpdMult());
}break;
}
}

@ -1075,7 +1075,7 @@
</object>
<object id="4" name="Green Slime" type="Monster" x="1052" y="4304">
<properties>
<property name="Type" propertytype="MonsterName" value="Frog"/>
<property name="Type" propertytype="MonsterName" value="Green Slime"/>
<property name="spawner" type="object" value="2"/>
</properties>
<point/>
@ -1716,14 +1716,14 @@
</object>
<object id="151" name="Green Slime" type="Monster" x="1056" y="4386">
<properties>
<property name="Type" propertytype="MonsterName" value="Frog"/>
<property name="Type" propertytype="MonsterName" value="Green Slime"/>
<property name="spawner" type="object" value="2"/>
</properties>
<point/>
</object>
<object id="155" name="Green Slime" type="Monster" x="1050" y="4218">
<properties>
<property name="Type" propertytype="MonsterName" value="Frog"/>
<property name="Type" propertytype="MonsterName" value="Green Slime"/>
<property name="spawner" type="object" value="2"/>
</properties>
<point/>

@ -198,10 +198,16 @@ MonsterStrategy
# The speed boost percentage to increase by during the charge.
Lockon Speed Boost = 15%
# Time waiting after charging.
Charge Recovery Time = 1.0s
# The speed boost percentage to increase by and the duration while disengaging.
Disengage Speed Boost = 30%, 3s
# The distance to disengage the player.
Disengage Range = 800
# The amount of time to spend disengaged.
Disengage Duration = 3.0s
}
}

@ -221,7 +221,7 @@ Monsters
XP = 14
Strategy = Run Towards
Strategy = Wolf
#Size of each animation frame
SheetFrameSize = 24,24

@ -2428,6 +2428,11 @@ namespace olc::utils::geom2d
return internal::filter_duplicate_points(intersections);
}
template<typename T>
inline const bool operator ==(const rect<T>&r1,const rect<T>&r2){
return r1.pos==r2.pos&&r1.size==r2.size;
}
}
using namespace olc;

Loading…
Cancel
Save