Add cat transform sprite. Added Witch Transform ability parameters. Implemented Transform ability. Fix Trapper Mark unit test since lock on target delays were added. Release Build 10354.
This commit is contained in:
parent
41aab6d8dd
commit
223bf1b2fe
@ -49,6 +49,7 @@ INCLUDE_MONSTER_DATA
|
||||
INCLUDE_game
|
||||
INCLUDE_GFX
|
||||
INCLUDE_DAMAGENUMBER_LIST
|
||||
INCLUDE_MONSTER_LIST
|
||||
|
||||
TEST_MODULE_INITIALIZE(AiLTestSuite)
|
||||
{
|
||||
@ -83,6 +84,9 @@ namespace MonsterTests
|
||||
|
||||
#pragma region Setup a fake test map
|
||||
game->MAP_DATA["CAMPAIGN_1_1"];
|
||||
game->MAP_DATA["CAMPAIGN_1_1"].ZoneData["UpperZone"];
|
||||
game->MAP_DATA["CAMPAIGN_1_1"].ZoneData["LowerZone"];
|
||||
game->currentLevel="CAMPAIGN_1_1";
|
||||
ItemDrop::drops.clear();
|
||||
#pragma endregion
|
||||
|
||||
@ -399,52 +403,59 @@ namespace MonsterTests
|
||||
,L"There should be 2 damage numbers of type DOT.");
|
||||
}
|
||||
TEST_METHOD(TrapperMarkTest){
|
||||
Monster testMonster{{},MONSTER_DATA["TestName"]};
|
||||
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
|
||||
game->SpawnMonster({},testMonsterData);
|
||||
game->SetElapsedTime(0.1f);
|
||||
game->OnUserUpdate(0.1f); //A monster that is spawned needs to be added to the monster list in the next tick.
|
||||
std::weak_ptr<Monster>testMonster{MONSTER_LIST.front()};
|
||||
|
||||
Assert::AreEqual(uint8_t(0),testMonster.GetMarkStacks(),L"Monster has 0 marks initially.");
|
||||
Assert::AreEqual(uint8_t(0),testMonster.lock()->GetMarkStacks(),L"Monster has 0 marks initially.");
|
||||
|
||||
testMonster.ApplyMark(7.f,5U);
|
||||
testMonster.lock()->ApplyMark(7.f,5U);
|
||||
|
||||
Assert::AreEqual(uint8_t(5),testMonster.GetMarkStacks(),L"Monster has 5 marks after receiving a buff.");
|
||||
game->SetElapsedTime(0.3f);
|
||||
game->OnUserUpdate(0.3f); //A monster that had a mark applied needs to be added as a lock on target in the next tick.
|
||||
|
||||
testMonster.Hurt(1,testMonster.OnUpperLevel(),testMonster.GetZ());
|
||||
Assert::AreEqual(uint8_t(5),testMonster.lock()->GetMarkStacks(),L"Monster has 5 marks after receiving a buff.");
|
||||
|
||||
Assert::AreEqual(uint8_t(5),testMonster.GetMarkStacks(),L"Monster should still have 5 marks after taking damage from a normal attack.");
|
||||
testMonster.lock()->Hurt(1,testMonster.lock()->OnUpperLevel(),testMonster.lock()->GetZ());
|
||||
|
||||
testMonster.Hurt(1,testMonster.OnUpperLevel(),testMonster.GetZ(),HurtFlag::PLAYER_ABILITY);
|
||||
Assert::AreEqual(uint8_t(5),testMonster.lock()->GetMarkStacks(),L"Monster should still have 5 marks after taking damage from a normal attack.");
|
||||
|
||||
Assert::AreEqual(uint8_t(4),testMonster.GetMarkStacks(),L"Monster should have 4 marks remaining.");
|
||||
Assert::AreEqual(22,testMonster.GetHealth(),L"Mark deals 60% of the player's attack. And 2 damage already taken from earlier.");
|
||||
testMonster.lock()->Hurt(1,testMonster.lock()->OnUpperLevel(),testMonster.lock()->GetZ(),HurtFlag::PLAYER_ABILITY);
|
||||
|
||||
testMonster.Hurt(1,testMonster.OnUpperLevel(),testMonster.GetZ(),HurtFlag::DOT);
|
||||
Assert::AreEqual(uint8_t(4),testMonster.lock()->GetMarkStacks(),L"Monster should have 4 marks remaining.");
|
||||
Assert::AreEqual(22,testMonster.lock()->GetHealth(),L"Mark deals 60% of the player's attack. And 2 damage already taken from earlier.");
|
||||
|
||||
Assert::AreEqual(uint8_t(4),testMonster.GetMarkStacks(),L"Monster should still have 4 marks remaining (DOTs don't remove a mark).");
|
||||
testMonster.lock()->Hurt(1,testMonster.lock()->OnUpperLevel(),testMonster.lock()->GetZ(),HurtFlag::DOT);
|
||||
|
||||
testMonster._DealTrueDamage(1);
|
||||
Assert::AreEqual(uint8_t(4),testMonster.lock()->GetMarkStacks(),L"Monster should still have 4 marks remaining (DOTs don't remove a mark).");
|
||||
|
||||
Assert::AreEqual(uint8_t(4),testMonster.GetMarkStacks(),L"Monster should still have 4 marks remaining after taking true damage from something not marked as a player ability.");
|
||||
testMonster.lock()->_DealTrueDamage(1);
|
||||
|
||||
testMonster.Heal(testMonster.GetMaxHealth()); //Heal the monster so it doesn't die.
|
||||
Assert::AreEqual(uint8_t(4),testMonster.lock()->GetMarkStacks(),L"Monster should still have 4 marks remaining after taking true damage from something not marked as a player ability.");
|
||||
|
||||
testMonster._DealTrueDamage(1,HurtFlag::PLAYER_ABILITY);
|
||||
testMonster.lock()->Heal(testMonster.lock()->GetMaxHealth()); //Heal the monster so it doesn't die.
|
||||
|
||||
Assert::AreEqual(uint8_t(3),testMonster.GetMarkStacks(),L"Monster should have 3 marks remaining after taking true damage.");
|
||||
testMonster.lock()->_DealTrueDamage(1,HurtFlag::PLAYER_ABILITY);
|
||||
|
||||
testMonster._DealTrueDamage(10,HurtFlag::DOT|HurtFlag::PLAYER_ABILITY);
|
||||
Assert::AreEqual(uint8_t(3),testMonster.lock()->GetMarkStacks(),L"Monster should have 3 marks remaining after taking true damage.");
|
||||
|
||||
Assert::AreEqual(uint8_t(2),testMonster.GetMarkStacks(),L"Monster should have 2 marks remaining after taking true damage, even though it's classified as a DOT. This is an edge case, but it's really meaningless here...");
|
||||
testMonster.lock()->_DealTrueDamage(10,HurtFlag::DOT|HurtFlag::PLAYER_ABILITY);
|
||||
|
||||
testMonster.Heal(testMonster.GetMaxHealth());
|
||||
Assert::AreEqual(uint8_t(2),testMonster.lock()->GetMarkStacks(),L"Monster should have 2 marks remaining after taking true damage, even though it's classified as a DOT. This is an edge case, but it's really meaningless here...");
|
||||
|
||||
testMonster.TriggerMark();
|
||||
testMonster.lock()->Heal(testMonster.lock()->GetMaxHealth());
|
||||
|
||||
Assert::AreEqual(uint8_t(1),testMonster.GetMarkStacks(),L"Monster should have 1 mark remaining after using TriggerMark function");
|
||||
Assert::AreEqual(24,testMonster.GetHealth(),L"Monster should not take damage from the TriggerMark function (Mark deals 6 damage though).");
|
||||
testMonster.lock()->TriggerMark();
|
||||
|
||||
Assert::AreEqual(uint8_t(1),testMonster.lock()->GetMarkStacks(),L"Monster should have 1 mark remaining after using TriggerMark function");
|
||||
Assert::AreEqual(24,testMonster.lock()->GetHealth(),L"Monster should not take damage from the TriggerMark function (Mark deals 6 damage though).");
|
||||
|
||||
game->SetElapsedTime(10.f);
|
||||
testMonster.Update(10.f);
|
||||
testMonster.lock()->Update(10.f);
|
||||
|
||||
Assert::AreEqual(uint8_t(0),testMonster.GetMarkStacks(),L"The marks should have expired after 10 seconds.");
|
||||
Assert::AreEqual(uint8_t(0),testMonster.lock()->GetMarkStacks(),L"The marks should have expired after 10 seconds.");
|
||||
}
|
||||
};
|
||||
}
|
@ -1036,6 +1036,7 @@ void AiL::RenderWorld(float fElapsedTime){
|
||||
else if(adrenalineRushBuffs.size()>0)playerCol={uint8_t(255*abs(sin(6.f*adrenalineRushBuffs[0].duration))),255,uint8_t(255*abs(sin(6.f*adrenalineRushBuffs[0].duration)))};
|
||||
else if(movespeedBuffs.size()>0)playerCol={uint8_t(255*abs(sin(2.f*movespeedBuffs[0].duration))),255,uint8_t(255*abs(sin(2.f*movespeedBuffs[0].duration)))};
|
||||
view.DrawPartialSquishedRotatedDecal(pos+vf2d{0,-player->GetZ()*(std::signbit(scale.y)?-1:1)},player->GetFrame().GetSourceImage()->Decal(),player->GetSpinAngle(),{12,12},player->GetFrame().GetSourceRect().pos,player->GetFrame().GetSourceRect().size,playerScale*scale,{1.f,player->ySquishFactor},playerCol);
|
||||
DrawAfterImage:view.DrawRotatedDecal(player->afterImagePos,player->afterImage.Decal(),0.f,player->afterImage.Sprite()->Size()/2,{player->GetSizeMult(),player->GetSizeMult()},{0xFFDCDA});
|
||||
SetDecalMode(DecalMode::NORMAL);
|
||||
if(player->GetState()==State::BLOCK){
|
||||
view.DrawDecal(player->GetPos()+vf2d{0,-player->GetZ()*(std::signbit(scale.y)?-1:1)}-vf2d{12,12},GFX["block.png"].Decal());
|
||||
|
@ -334,6 +334,18 @@ void sig::Animation::InitializeAnimations(){
|
||||
pl_witch_cast_w.AddFrame({&GFX["nico-witch.png"],{vi2d{7+i,2}*24,{24,24}}});
|
||||
}
|
||||
ANIMATION_DATA["WITCH_CAST_W"]=pl_witch_cast_w;
|
||||
Animate2D::FrameSequence pl_witch_transform_s(0.1f);
|
||||
pl_witch_transform_s.AddFrame({&GFX["nico-witch.png"],{vi2d{0,4}*24,{24,24}}});
|
||||
Animate2D::FrameSequence pl_witch_transform_n(0.1f);
|
||||
pl_witch_transform_n.AddFrame({&GFX["nico-witch.png"],{vi2d{0,5}*24,{24,24}}});
|
||||
Animate2D::FrameSequence pl_witch_transform_w(0.1f);
|
||||
pl_witch_transform_w.AddFrame({&GFX["nico-witch.png"],{vi2d{0,6}*24,{24,24}}});
|
||||
Animate2D::FrameSequence pl_witch_transform_e(0.1f);
|
||||
pl_witch_transform_e.AddFrame({&GFX["nico-witch.png"],{vi2d{0,7}*24,{24,24}}});
|
||||
ANIMATION_DATA["WITCH_TRANSFORM_S"]=pl_witch_transform_s;
|
||||
ANIMATION_DATA["WITCH_TRANSFORM_N"]=pl_witch_transform_n;
|
||||
ANIMATION_DATA["WITCH_TRANSFORM_W"]=pl_witch_transform_w;
|
||||
ANIMATION_DATA["WITCH_TRANSFORM_E"]=pl_witch_transform_e;
|
||||
|
||||
CreateHorizontalAnimationSequence("ground-slam-attack-back.png",5,{64,64},{0.02f,Animate2D::Style::OneShot});
|
||||
CreateHorizontalAnimationSequence("ground-slam-attack-front.png",5,{64,64},{0.02f,Animate2D::Style::OneShot});
|
||||
|
@ -115,8 +115,7 @@ void Player::Initialize(){
|
||||
SetBaseStat("Damage Reduction",0);
|
||||
SetBaseStat("Attack Spd",0);
|
||||
cooldownSoundInstance=Audio::Engine().LoadSound("spell_cast.ogg"_SFX);
|
||||
afterImage->sprite->Resize(24,24);
|
||||
afterImage->Update();
|
||||
afterImage.Create(24,24);
|
||||
}
|
||||
|
||||
void Player::InitializeMinimapImage(){
|
||||
@ -521,6 +520,19 @@ void Player::Update(float fElapsedTime){
|
||||
game->SetupWorldShake(0.3f);
|
||||
}
|
||||
}break;
|
||||
case State::LEAP:{
|
||||
leapTimer-=fElapsedTime;
|
||||
if(leapTimer<=0.f){
|
||||
SetState(State::NORMAL);
|
||||
SetupAfterImage();
|
||||
afterImagePos=GetPos();
|
||||
SetZ(0.f);
|
||||
break;
|
||||
}
|
||||
SetZ((sin((1.f/totalLeapTime)*PI*leapTimer)/2.f+0.5f)*"Witch.Right Click Ability.Leap Max Z"_F);
|
||||
SetVelocity(vf2d{"Witch.Right Click Ability.Leap Velocity"_F/100.f*24,transformTargetDir}.cart());
|
||||
animation.UpdateState(internal_animState,fElapsedTime);
|
||||
}break;
|
||||
default:{
|
||||
//Update animations normally.
|
||||
animation.UpdateState(internal_animState,fElapsedTime);
|
||||
@ -757,10 +769,10 @@ void Player::Update(float fElapsedTime){
|
||||
#pragma endregion
|
||||
#pragma region Witch
|
||||
const auto RemoveScanLine=[&](uint8_t scanLine){
|
||||
for(int x:std::ranges::iota_view(0,afterImage->sprite->width)){
|
||||
afterImage->sprite->SetPixel({x,scanLine},BLANK);
|
||||
for(int x:std::ranges::iota_view(0,afterImage.Sprite()->width)){
|
||||
afterImage.Sprite()->SetPixel({x,scanLine},BLANK);
|
||||
}
|
||||
afterImage->Update();
|
||||
afterImage.Decal()->Update();
|
||||
};
|
||||
|
||||
//Scan Line goes through 1-23 (odd numbers) first, then 0-22.
|
||||
@ -1803,11 +1815,11 @@ const std::unordered_set<std::string>&Player::GetMyClass()const{
|
||||
}
|
||||
|
||||
void Player::SetupAfterImage(){
|
||||
game->SetDrawTarget(afterImage->sprite);
|
||||
game->SetDrawTarget(afterImage.Sprite());
|
||||
game->Clear(BLANK);
|
||||
game->DrawPartialSprite({},animation.GetFrame(internal_animState).GetSourceImage()->Sprite(),animation.GetFrame(internal_animState).GetSourceRect().pos,animation.GetFrame(internal_animState).GetSourceRect().size,1U,0U,{255,255,254}); //Off-white so that the sprite is rendered completely in white.
|
||||
game->SetDrawTarget(nullptr);
|
||||
afterImage->Update();
|
||||
afterImage.Decal()->Update();
|
||||
removeLineTimer=TIME_BETWEEN_LINE_REMOVALS;
|
||||
scanLine=1U;
|
||||
}
|
@ -408,12 +408,17 @@ protected:
|
||||
float footstepTimer=0.f;
|
||||
float ySquishFactor{1.f};
|
||||
size_t cooldownSoundInstance=std::numeric_limits<size_t>::max();
|
||||
std::unique_ptr<Decal>afterImage;
|
||||
Renderable afterImage;
|
||||
Animate2D::AnimationState internal_animState;
|
||||
float removeLineTimer{};
|
||||
const float TIME_BETWEEN_LINE_REMOVALS{0.025f};
|
||||
uint8_t scanLine{24}; //0-23.
|
||||
void SetupAfterImage();
|
||||
vf2d afterImagePos{};
|
||||
float transformTargetDir{};
|
||||
float leapTimer{};
|
||||
float totalLeapTime{};
|
||||
vf2d leapStartingPos{};
|
||||
};
|
||||
|
||||
#pragma region Warrior
|
||||
|
@ -59,5 +59,6 @@ namespace State{
|
||||
DEATH,
|
||||
ROLL,
|
||||
DEADLYDASH,
|
||||
LEAP,
|
||||
};
|
||||
}
|
@ -118,9 +118,14 @@ struct NPCData{
|
||||
NPCData(XMLTag npcTag);
|
||||
};
|
||||
|
||||
namespace MonsterTests{
|
||||
class MonsterTest;
|
||||
}
|
||||
|
||||
struct Map{
|
||||
friend class AiL;
|
||||
friend class TMXParser;
|
||||
friend class MonsterTests::MonsterTest;
|
||||
private:
|
||||
MapTag MapData;
|
||||
std::string name;
|
||||
|
@ -39,7 +39,7 @@ All rights reserved.
|
||||
#define VERSION_MAJOR 1
|
||||
#define VERSION_MINOR 2
|
||||
#define VERSION_PATCH 3
|
||||
#define VERSION_BUILD 10347
|
||||
#define VERSION_BUILD 10356
|
||||
|
||||
#define stringify(a) stringify_(a)
|
||||
#define stringify_(a) #a
|
||||
|
@ -43,6 +43,7 @@ All rights reserved.
|
||||
#include "config.h"
|
||||
#include "SoundEffect.h"
|
||||
#include "BulletTypes.h"
|
||||
#include "util.h"
|
||||
|
||||
INCLUDE_MONSTER_LIST
|
||||
INCLUDE_BULLET_LIST
|
||||
@ -63,6 +64,41 @@ void Witch::Initialize(){
|
||||
SETUP_CLASS(Witch)
|
||||
|
||||
void Witch::OnUpdate(float fElapsedTime){
|
||||
if(attack_cooldown_timer>0){
|
||||
idle_n="WITCH_IDLE_ATTACK_N";
|
||||
idle_e="WITCH_IDLE_ATTACK_E";
|
||||
idle_s="WITCH_IDLE_ATTACK_S";
|
||||
idle_w="WITCH_IDLE_ATTACK_W";
|
||||
walk_n="WITCH_ATTACK_N";
|
||||
walk_e="WITCH_ATTACK_E";
|
||||
walk_s="WITCH_ATTACK_S";
|
||||
walk_w="WITCH_ATTACK_W";
|
||||
} else {
|
||||
idle_n="WITCH_IDLE_N";
|
||||
idle_e="WITCH_IDLE_E";
|
||||
idle_s="WITCH_IDLE_S";
|
||||
idle_w="WITCH_IDLE_W";
|
||||
walk_n="WITCH_WALK_N";
|
||||
walk_e="WITCH_WALK_E";
|
||||
walk_s="WITCH_WALK_S";
|
||||
walk_w="WITCH_WALK_W";
|
||||
}
|
||||
if(GetState()==State::CASTING){
|
||||
switch(GetFacingDirection()){
|
||||
case UP:{
|
||||
UpdateAnimation("WITCH_CAST_N",WIZARD|WITCH);
|
||||
}break;
|
||||
case DOWN:{
|
||||
UpdateAnimation("WITCH_CAST_S",WIZARD|WITCH);
|
||||
}break;
|
||||
case LEFT:{
|
||||
UpdateAnimation("WITCH_CAST_W",WIZARD|WITCH);
|
||||
}break;
|
||||
case RIGHT:{
|
||||
UpdateAnimation("WITCH_CAST_E",WIZARD|WITCH);
|
||||
}break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Witch::AutoAttack(){
|
||||
@ -78,6 +114,14 @@ void Witch::InitializeClassAbilities(){
|
||||
Witch::rightClickAbility.action=
|
||||
[](Player*p,vf2d pos={}){
|
||||
p->SetupAfterImage();
|
||||
p->afterImagePos=p->leapStartingPos=p->GetPos();
|
||||
geom2d::line<float>targetLine{p->GetPos(),p->GetWorldAimingLocation(Player::USE_WALK_DIR,Player::INVERTED)};
|
||||
const float LeapMaxRange{"Witch.Right Click Ability.Leap Velocity"_F*"Witch.Right Click Ability.Leap Max Range Time"_F};
|
||||
p->leapTimer=p->totalLeapTime=std::min("Witch.Right Click Ability.Leap Max Range Time"_F,util::lerp(0.f,"Witch.Right Click Ability.Leap Max Range Time"_F,targetLine.length()/(LeapMaxRange/100.f*24)));
|
||||
p->transformTargetDir=targetLine.vector().polar().y;
|
||||
p->SetAnimationBasedOnTargetingDirection("TRANSFORM",p->transformTargetDir);
|
||||
p->ApplyIframes("Witch.Right Click Ability.Leap Max Range Time"_F+0.1f);
|
||||
p->SetState(State::LEAP);
|
||||
return true;
|
||||
};
|
||||
#pragma endregion
|
||||
|
@ -96,6 +96,7 @@ Player
|
||||
PLAYER_ANIMATION[22] = WITCH_ATTACK
|
||||
PLAYER_ANIMATION[23] = WITCH_CAST
|
||||
PLAYER_ANIMATION[24] = WITCH_IDLE
|
||||
PLAYER_ANIMATION[25] = WITCH_TRANSFORM
|
||||
}
|
||||
|
||||
PlayerXP
|
||||
|
@ -45,13 +45,17 @@ Witch
|
||||
# Whether or not this ability cancels casts.
|
||||
CancelCast = 1
|
||||
|
||||
Leap Velocity = 800
|
||||
Leap Max Range Time = 0.5s
|
||||
Leap Max Z = 15px
|
||||
|
||||
#RGB Values. Color 1 is the circle at full cooldown, Color 2 is the color at empty cooldown.
|
||||
Cooldown Bar Color 1 = 0, 0, 64, 192
|
||||
Cooldown Bar Color 2 = 0, 0, 128, 192
|
||||
|
||||
Precast Time = 0
|
||||
Casting Range = 400
|
||||
Casting Size = 100
|
||||
Casting Range = 0
|
||||
Casting Size = 0
|
||||
}
|
||||
Ability 1
|
||||
{
|
||||
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 20 KiB |
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user