Framework for Entity versions of player actions and first Shadow Boss (Warrior+Thief) setup. Release Build 13266.
All checks were successful
Emscripten Build / Build_and_Deploy_Web_Build (push) Successful in 8m30s

This commit is contained in:
AMay 2026-04-13 23:38:58 -05:00
parent fc47c1219b
commit 98b13568df
28 changed files with 230 additions and 56 deletions

View File

@ -39,6 +39,7 @@ All rights reserved.
#include "Key.h"
#include <string_view>
#include "olcUTIL_DataFile.h"
#include"Entity.h"
class InputGroup;
@ -89,7 +90,7 @@ struct Ability{
//Ability action function, returns true if the ability can be casted, otherwise returns false.
// Argument 1: Player* - player pointer
// Argument 2: vf2d - The returned precast target position (if the ability needs to be aimed, otherwise {})
std::function<bool(Player*,vf2d)>action=[](Player*,vf2d){return false;};
std::function<bool(Entity,vf2d)>action=[](Entity,vf2d){return false;};
static InputGroup DEFAULT;
const float GetCooldownTime()const;
const bool operator==(const Ability&a)const;

View File

@ -1304,8 +1304,9 @@
</SubType>
</ClCompile>
<ClCompile Include="VisualNovel.cpp" />
<ClCompile Include="Warrior.cpp" />
<ClCompile Include="Warrior+Thief.cpp" />
<ClCompile Include="util.cpp" />
<ClCompile Include="Warrior.cpp" />
<ClCompile Include="Wisp.cpp">
<SubType>
</SubType>

View File

@ -782,9 +782,6 @@
<ClCompile Include="LightningBoltEmitter.cpp">
<Filter>Source Files\Emitters</Filter>
</ClCompile>
<ClCompile Include="Warrior.cpp">
<Filter>Source Files\Player Classes</Filter>
</ClCompile>
<ClCompile Include="Emitter.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@ -1382,6 +1379,12 @@
<ClCompile Include="Spider.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Warrior+Thief.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Warrior.cpp">
<Filter>Source Files\Player Classes</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="cpp.hint" />

View File

@ -3184,7 +3184,7 @@ int operator ""_I(const char*key,std::size_t len){
return DATA.GetProperty(std::string(key,len)).GetInt();
}
float operator ""_F(const char*key,std::size_t len){
float operator""_F(const char*key,std::size_t len){
AiL::OutputDebugInfo(key,len);
return float(DATA.GetProperty(std::string(key,len)).GetReal());
}
@ -3873,7 +3873,7 @@ void AiL::SetLoadoutItem(int slot,std::string itemName){
switch(slot){
case 0:{
itemAbility.action=[&](Player*p,vf2d pos={}){
itemAbility.action=[&](Entity e,vf2d pos={}){
return game->UseLoadoutItem(0,pos);
};
game->GetPlayer()->SetItem1UseFunc(itemAbility);
@ -3881,7 +3881,7 @@ void AiL::SetLoadoutItem(int slot,std::string itemName){
Component<MenuItemItemButton>(MenuType::ITEM_HUB_LOADOUT,"Loadout Item 1")->SetItem(loadout[slot]);
}break;
case 1:{
itemAbility.action=[&](Player*p,vf2d pos={}){
itemAbility.action=[&](Entity e,vf2d pos={}){
return game->UseLoadoutItem(1,pos);
};
game->GetPlayer()->SetItem2UseFunc(itemAbility);
@ -3889,7 +3889,7 @@ void AiL::SetLoadoutItem(int slot,std::string itemName){
Component<MenuItemItemButton>(MenuType::ITEM_HUB_LOADOUT,"Loadout Item 2")->SetItem(loadout[slot]);
}break;
case 2:{
itemAbility.action=[&](Player*p,vf2d pos={}){
itemAbility.action=[&](Entity e,vf2d pos={}){
return game->UseLoadoutItem(2,pos);
};
game->GetPlayer()->SetItem3UseFunc(itemAbility);
@ -3938,7 +3938,7 @@ void AiL::ClearLoadoutItem(int slot){
Ability itemAbility{"???","","",0,0,inputGroup,""};
switch(slot){
case 0:{
itemAbility.action=[&](Player*p,vf2d pos={}){
itemAbility.action=[&](Entity e,vf2d pos={}){
return game->UseLoadoutItem(0,pos);
};
game->GetPlayer()->SetItem1UseFunc(itemAbility);
@ -3949,7 +3949,7 @@ void AiL::ClearLoadoutItem(int slot){
Component<MenuItemItemButton>(MenuType::ITEM_HUB_LOADOUT,"Loadout Item 1")->UpdateIcon();
}break;
case 1:{
itemAbility.action=[&](Player*p,vf2d pos={}){
itemAbility.action=[&](Entity e,vf2d pos={}){
return game->UseLoadoutItem(1,pos);
};
game->GetPlayer()->SetItem2UseFunc(itemAbility);
@ -3961,7 +3961,7 @@ void AiL::ClearLoadoutItem(int slot){
}break;
case 2:{
itemAbility.action=[&](Player*p,vf2d pos={}){
itemAbility.action=[&](Entity e,vf2d pos={}){
return game->UseLoadoutItem(2,pos);
};
game->GetPlayer()->SetItem3UseFunc(itemAbility);

View File

@ -67,4 +67,8 @@ namespace classutils{//Classes have bit-wise operator capabilities.
static inline std::string ClassToString(const Class&cl){
return classList.at(cl);
}
};
};
#define CONFIG_F(var)e.IsFriendly()?operator ""_F(std::string{var}.data(),std::string{var}.length()):Monster::STRATEGY::_GetFloat(*e.ToMonster(),var,(*e.ToMonster()).GetStrategyName())
#define CONFIG_I(var)e.IsFriendly()?operator ""_I(std::string{var}.data(),std::string{var}.length()):Monster::STRATEGY::_GetInt(*e.ToMonster(),var,(*e.ToMonster()).GetStrategyName())
#define CONFIG_S(var)e.IsFriendly()?operator ""_S(std::string{var}.data(),std::string{var}.length()):Monster::STRATEGY::_GetString(*e.ToMonster(),var,(*e.ToMonster()).GetStrategyName())

View File

@ -103,6 +103,13 @@ const bool Entity::OnUpperLevel()const{
CallClassFunc(OnUpperLevel());
}
Player*const Entity::ToPlayer()const{
return get(Player*);
}
Monster*const Entity::ToMonster()const{
return get(Monster*);
}
const float Entity::GetMoveSpdMult()const{
CallClassFunc(GetMoveSpdMult());
}
@ -118,4 +125,21 @@ const FriendlyType Entity::IsFriendly()const{
const float Entity::GetSizeMult()const{
CallClassFunc(GetSizeMult());
}
const bool Entity::CanMove()const{
CallClassFunc(CanMove());
}
const bool Entity::HasEnchant(const std::string&enchant)const{
if(is(Player*))return get(Player*)->HasEnchant(enchant);
return false;
}
float&Entity::GetBlockTimer()const{
CallClassFunc(blockTimer);
}
Buff&Entity::AddBuff(BuffType type,float duration,float intensity){
CallClassFunc(AddBuff(type,duration,intensity));
}

View File

@ -43,6 +43,7 @@ All rights reserved.
#include"HurtDamageInfo.h"
#include"FriendlyType.h"
#include"DEFINES.h"
#include"State.h"
class Player;
class Monster;
@ -56,6 +57,8 @@ public:
Entity(Player*player);
Entity(Monster*monster);
Entity(const std::variant<Monster*,Player*>ent);
Player*const ToPlayer()const;
Monster*const ToMonster()const;
const vf2d GetPos()const;
void SetIframeTime(const float iframeTime);
Buff&GetOrAddBuff(BuffType buffType,std::pair<BuffDuration,BuffIntensity>newBuff);
@ -71,6 +74,11 @@ public:
const float GetSizeMult()const;
const int GetAttack()const;
const FriendlyType IsFriendly()const;
const State::State GetState()const;
const bool CanMove()const;
const bool HasEnchant(const std::string&enchant)const;
float&GetBlockTimer()const;
Buff&AddBuff(BuffType type,float duration,float intensity);
private:
mutableconst std::variant<Monster*,Player*>entity;
inline bool operator==(const Entity&rhs){return entity==rhs.entity;}

View File

@ -284,6 +284,7 @@ void Monster::Update(const float fElapsedTime){
lastPathfindingCooldown=std::max(0.f,lastPathfindingCooldown-fElapsedTime);
markApplicationTimer=std::max(0.f,markApplicationTimer-fElapsedTime);
specialMarkApplicationTimer=std::max(0.f,specialMarkApplicationTimer-fElapsedTime);
blockTimer=std::max(0.f,blockTimer-fElapsedTime);
lastFacingDirectionChange+=fElapsedTime;
timeSpentAlive+=fElapsedTime;
@ -573,6 +574,7 @@ void Monster::Draw()const{
};
const auto DrawOverlayMonster=[&](vf2d scale={1.f,1.f},Pixel col=WHITE){
game->view.DrawPartialRotatedDecal(drawPos,GFX[overlaySprite].Decal(),finalSpriteRot,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,scale,col);
if(blockTimer>0.f)game->view.DrawPartialRotatedDecal(drawPos,GFX["block.png"].Decal(),finalSpriteRot,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,scale,col);
};
const auto DrawMountedMonster=[&](vf2d scale={1.f,1.f},Pixel col=WHITE){
game->view.DrawPartialRotatedDecal(drawPos+mountedSprOffset,GetMountedFrame().value().GetSourceImage()->Decal(),finalSpriteRot,GetMountedFrame().value().GetSourceRect().size/2,GetMountedFrame().value().GetSourceRect().pos,GetMountedFrame().value().GetSourceRect().size,scale,col);
@ -1822,4 +1824,5 @@ void Monster::SetTransparency(uint8_t alpha){
uint8_t Monster::GetTransparency()const{
return transparency;
}
}

View File

@ -85,6 +85,7 @@ class Monster:public IAttributable{
friend class MonsterTests::MonsterTest;
friend struct MonsterData;
friend const bool Entity::IsBoss()const;
friend class Entity;
public:
enum MonsterStrategy{
RUN_TOWARDS,
@ -128,6 +129,7 @@ public:
SKELETON_CAPTAIN_FLAG,
SKELETON_MAGE,
SPIDER,
WARRIORTHIEF,
////////////////////////////////////
////////////////////////////////////
////////////////////////////////////
@ -190,6 +192,7 @@ public:
static void SKELETON_CAPTAIN_FLAG(Monster&m,float fElapsedTime,const std::string&strategy);
static void SKELETON_MAGE(Monster&m,float fElapsedTime,const std::string&strategy);
static void SPIDER(Monster&m,float fElapsedTime,const std::string&strategy);
static void WARRIORTHIEF(Monster&m,float fElapsedTime,const std::string&strategy);
};
struct StrategyFunction{
std::string name;
@ -489,6 +492,7 @@ private:
bool bumpedIntoTerrain=false; //Gets set to true before a strategy executes if the monster runs into some terrain on this frame.
bool attackedByPlayer=false; //Gets set to true before a strategy executes if the monster has been attacked by the player.
uint8_t transparency{255U};
float blockTimer{};
};
struct MonsterSpawner{

View File

@ -184,4 +184,5 @@ enum class Attribute{
FLAG_PTR,
WAIT_ONE_TICK,
ALPHA_AMOUNT,
DEFENSIVE_COOLDOWN,
};

View File

@ -100,6 +100,7 @@ class Player{
friend class Inventory;
friend class ItemInfo;
friend void ItemOverlay::Draw();
friend class Entity;
public:
Player();
//So this is rather fascinating and only exists because we have the ability to change classes which means we need to initialize a class

View File

@ -90,6 +90,7 @@ void Monster::InitializeStrategies(){
ADD_STRATEGY(SKELETON_CAPTAIN_FLAG,"Skeleton Captain Flag");
ADD_STRATEGY(SKELETON_MAGE,"Skeleton Mage");
ADD_STRATEGY(SPIDER,"Spider");
ADD_STRATEGY(WARRIORTHIEF,"Warrior+Thief");
if(!Monster::monsterStrategies.contains(SKELETON_MAGE))ERR("SKELETON_MAGE is a required dependency for Entity::IsSkeleletonMage()! THIS IS REQUIRED!!");

View File

@ -87,7 +87,7 @@ bool Ranger::AutoAttack(){
void Ranger::InitializeClassAbilities(){
#pragma region Ranger Right-click Ability (Retreat)
Ranger::rightClickAbility.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
geom2d::line mouseDir{p->GetWorldAimingLocation(Player::USE_WALK_DIR),p->GetPos()};
float velocity=(0.5f*-p->friction*p->RETREAT_TIME*p->RETREAT_TIME-p->RETREAT_DISTANCE)/-p->RETREAT_TIME; //Derived from kinetic motion formula.
p->SetVelocity(mouseDir.vector().norm()*velocity);
@ -112,7 +112,7 @@ void Ranger::InitializeClassAbilities(){
#pragma endregion
#pragma region Ranger Ability 1 (Rapid Fire)
Ranger::ability1.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
p->remainingRapidFireShots=p->RAPID_FIRE_SHOOT_AMOUNT;
if(p->HasEnchant("Extreme Rapid Fire"))p->remainingRapidFireShots+="Extreme Rapid Fire"_ENC["ARROW COUNT INCREASE"];
p->rapidFireTimer=p->RAPID_FIRE_SHOOT_DELAY;
@ -125,7 +125,7 @@ void Ranger::InitializeClassAbilities(){
#pragma endregion
#pragma region Ranger Ability 2 (Charged Shot)
Ranger::ability2.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
vf2d arrowVelocity=util::pointTo(p->GetPos(),p->GetWorldAimingLocation());
float beamRadius{12*"Ranger.Ability 2.Radius"_F/100};
@ -146,7 +146,7 @@ void Ranger::InitializeClassAbilities(){
#pragma endregion
#pragma region Ranger Ability 3 (Multi Shot)
Ranger::ability3.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
geom2d::line pointTowardsCursor=geom2d::line(p->GetPos(),p->GetWorldAimingLocation());
float shootingDist=pointTowardsCursor.length();
vf2d shootingDirMiddle=pointTowardsCursor.vector();

View File

@ -106,7 +106,7 @@ bool Thief::AutoAttack(){
void Thief::InitializeClassAbilities(){
#pragma region Thief Right-click Ability (Roll)
Thief::rightClickAbility.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
p->SetState(State::ROLL);
p->rolling_timer="Thief.Right Click Ability.Roll Time"_F;
@ -137,7 +137,7 @@ void Thief::InitializeClassAbilities(){
#pragma endregion
#pragma region Thief Ability 1 (Hidden Dagger)
Thief::ability1.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
geom2d::line mouseDir{p->GetWorldAimingLocation(Player::USE_WALK_DIR),p->GetPos()};
float velocity=(0.5f*-p->friction*"Thief.Ability 1.RetreatTime"_F*"Thief.Ability 1.RetreatTime"_F-24.f*"Thief.Ability 1.RetreatDistance"_F/100)/-"Thief.Ability 1.RetreatTime"_F; //Derived from kinetic motion formula.
p->SetVelocity(mouseDir.vector().norm()*velocity);
@ -156,7 +156,7 @@ void Thief::InitializeClassAbilities(){
#pragma endregion
#pragma region Thief Ability 2 (Deadly Dash)
Thief::ability2.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
game->AddEffect(ShineEffect{p->GetPos()+vf2d{4.f,4.f},0.5f,0.5f,"shine.png",1.5f,vf2d{},WHITE,util::random(2*PI),PI/2,true});
p->ApplyIframes("Thief.Ability 2.Initial Wait"_F+"Thief.Ability 2.Ending Wait"_F+"Thief.Ability 2.Completed Dash Extra Iframe Time"_F);
SoundEffect::PlaySFX("Charge Up",p->GetPos());
@ -170,7 +170,7 @@ void Thief::InitializeClassAbilities(){
#pragma endregion
#pragma region Thief Ability 3 (Adrenaline Rush)
Thief::ability3.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
SoundEffect::PlaySFX("Adrenaline Rush",SoundEffect::CENTERED);
float adrenalineRushDuration{"Thief.Ability 3.Duration"_F};

View File

@ -84,7 +84,7 @@ bool Trapper::AutoAttack(){
void Trapper::InitializeClassAbilities(){
#pragma region Trapper Right-click Ability (Sprint)
Trapper::rightClickAbility.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
SoundEffect::PlaySFX("Sprint",SoundEffect::CENTERED);
p->AddBuff(BuffType::SPEEDBOOST,"Trapper.Right Click Ability.Movement Speed Buff"_f[1],"Trapper.Right Click Ability.Movement Speed Buff"_f[0]/100.f);
for(int i:std::ranges::iota_view(0,50)){
@ -96,7 +96,7 @@ void Trapper::InitializeClassAbilities(){
#pragma endregion
#pragma region Trapper Ability 1 (Mark Target)
Trapper::ability1.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
std::optional<std::weak_ptr<Monster>>nearestMonster{Monster::GetNearestMonster(pos,Trapper::ability1.precastInfo.range,p->OnUpperLevel(),p->GetZ())};
vf2d targetPos{pos};
if(nearestMonster.has_value()){
@ -116,7 +116,7 @@ void Trapper::InitializeClassAbilities(){
#pragma endregion
#pragma region Trapper Ability 2 (Bear Trap)
Trapper::ability2.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
CreateBullet(BearTrap)(p->GetPos(),"Trapper.Ability 2.Trap Radius"_I,"Trapper.Ability 2.DamageMult"_F*p->GetAttack(),0.2f,0.5f,p->OnUpperLevel(),false,INFINITE,FRIENDLY,WHITE,{1.f,1.f})EndBullet;
SoundEffect::PlaySFX("Place Down Trap",p->GetPos());
p->SetAnimationBasedOnTargetingDirection("SETTRAP",p->GetFacingDirection());
@ -125,7 +125,7 @@ void Trapper::InitializeClassAbilities(){
#pragma endregion
#pragma region Trapper Ability 3 (Explosive Trap)
Trapper::ability3.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
float trapDamage{"Trapper.Ability 3.DamageMult"_F*p->GetAttack()};
if(p->HasEnchant("Concussive Trap"))trapDamage+=p->GetAttack()*"Concussive Trap"_ENC["ADDITIONAL EXPLOSION DAMAGE"]/100.f;
CreateBullet(ExplosiveTrap)(p->GetPos(),"Trapper.Ability 3.Trap Radius"_I,"Trapper.Ability 3.Explosion Radius"_F/100.f*24,"Trapper.Ability 3.Trap Auto Detonate Time"_F,trapDamage,0.2f,0.5f,"Trapper.Ability 3.Trap Activation Time"_F,p->OnUpperLevel(),false,INFINITE,FRIENDLY,WHITE,{1.f,1.f})EndBullet;

View File

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1
#define VERSION_MINOR 3
#define VERSION_PATCH 0
#define VERSION_BUILD 13243
#define VERSION_BUILD 13266
#define stringify(a) stringify_(a)
#define stringify_(a) #a

View File

@ -0,0 +1,63 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2026 Amy 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 "Monster.h"
#include"AdventuresInLestoria.h"
INCLUDE_game
DEFINE_STRATEGY(WARRIORTHIEF)
enum Phase{
INIT,
RUN,
};
switch(PHASE()){
case INIT:{
m.F(A::DEFENSIVE_COOLDOWN)=0.f;
}break;
case RUN:{
m.F(A::DEFENSIVE_COOLDOWN)-=game->GetElapsedTime();
const float distToPlayer{util::distance(m.GetPos(),game->GetPlayer()->GetPos())};
if(distToPlayer<100.f&&m.F(A::DEFENSIVE_COOLDOWN)<=0.f){
m.F(A::DEFENSIVE_COOLDOWN)=ConfigFloat("Warrior.Right Click Ability.Cooldown");
Warrior::rightClickAbility.action(&m,m.GetPos());
}
}break;
}
END_STRATEGY

View File

@ -104,26 +104,32 @@ bool Warrior::AutoAttack(){
void Warrior::InitializeClassAbilities(){
#pragma region Warrior Right-click Ability (Block)
Warrior::rightClickAbility.action=
[](Player*p,vf2d pos={}){
if(p->GetState()==State::NORMAL||p->GetState()==State::CASTING){
rightClickAbility.cooldown=rightClickAbility.COOLDOWN_TIME;
float blockTime{"Warrior.Right Click Ability.Duration"_F};
if(p->HasEnchant("Heavy Guard"))blockTime*="Heavy Guard"_ENC["BLOCK DURATION MULT"];
if(p->HasEnchant("Advance Shield")){
p->AddShield(p->GetMaxHealth()*"Advance Shield"_ENC["SHIELD AMOUNT"]/100.f,"Advance Shield"_ENC["SHIELD DURATION"],PlayerTimerType::ADVANCE_SHIELD_TIMER);
}else{
p->blockTimer=blockTime;
p->SetState(State::BLOCK);
p->AddBuff(BuffType::BLOCK_SLOWDOWN,p->blockTimer,"Warrior.Right Click Ability.SlowAmt"_F);
[](Entity e,vf2d pos={}){
auto GetFloat{[](const char*key,std::size_t len){
AiL::OutputDebugInfo(key,len);
return float(DATA.GetProperty(std::string(key,len)).GetReal());
}};
if(e.IsFriendly()&&(e.ToPlayer()->GetState()==State::NORMAL||e.ToPlayer()->GetState()==State::CASTING)||
!e.IsFriendly()&&e.CanMove()){
rightClickAbility.cooldown=rightClickAbility.COOLDOWN_TIME;
float blockTime{e.IsFriendly() ? GetFloat(std::string{"Warrior.Right Click Ability.Duration"}.data(),std::string{"Warrior.Right Click Ability.Duration"}.length()) : Monster::STRATEGY::_GetFloat(*e.ToMonster(),"Warrior.Right Click Ability.Duration",(*e.ToMonster()).GetStrategyName())};
if(e.HasEnchant("Heavy Guard"))blockTime*="Heavy Guard"_ENC["BLOCK DURATION MULT"];
if(e.HasEnchant("Advance Shield")){
if(e.IsFriendly())e.ToPlayer()->AddShield(e.ToPlayer()->GetMaxHealth()*"Advance Shield"_ENC["SHIELD AMOUNT"]/100.f,"Advance Shield"_ENC["SHIELD DURATION"],PlayerTimerType::ADVANCE_SHIELD_TIMER);
}else{
float&blockTimer{e.GetBlockTimer()};
blockTimer=blockTime;
if(e.IsFriendly())e.ToPlayer()->SetState(State::BLOCK);
e.AddBuff(BuffType::BLOCK_SLOWDOWN,blockTimer,e.IsFriendly() ? GetFloat(std::string{"Warrior.Right Click Ability.SlowAmt"}.data(),std::string{"Warrior.Right Click Ability.SlowAmt"}.length()) : Monster::STRATEGY::_GetFloat(*e.ToMonster(),"Warrior.Right Click Ability.SlowAmt",(*e.ToMonster()).GetStrategyName()));
}
return true;
}
return true;
}
return false;
};
#pragma endregion
#pragma region Warrior Ability 1 (Battlecry)
Warrior::ability1.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
game->AddEffect(Effect{p->GetPos(),"Warrior.Ability 1.EffectLifetime"_F,"battlecry_effect.png",p->upperLevel,"Warrior.Ability 1.Range"_F/350,"Warrior.Ability 1.EffectFadetime"_F});
p->AddBuff(BuffType::STAT_UP,"Warrior.Ability 1.AttackUpDuration"_F,"Warrior.Ability 1.AttackIncrease"_F,{"Attack %"});
p->AddBuff(BuffType::DAMAGE_REDUCTION,"Warrior.Ability 1.DamageReductionDuration"_F,"Warrior.Ability 1.DamageReduction"_F);
@ -139,7 +145,7 @@ void Warrior::InitializeClassAbilities(){
#pragma endregion
#pragma region Warrior Ability 2 (Ground Slam)
Warrior::ability2.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
p->Spin(GROUND_SLAM_SPIN_TIME,"Warrior.Ability 2.SpinSpd"_F*PI);
p->iframe_time="Warrior.Ability 2.IframeTime"_F;
return true;
@ -147,7 +153,7 @@ void Warrior::InitializeClassAbilities(){
#pragma endregion
#pragma region Warrior Ability 3 (Sonic Slash)
Warrior::ability3.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
p->SetState(State::SWING_SONIC_SWORD);
p->AddBuff(BuffType::SLOWDOWN,"Warrior.Ability 3.StuckTime"_F,1);
vf2d bulletVel={};

View File

@ -116,7 +116,7 @@ bool Witch::AutoAttack(){
void Witch::InitializeClassAbilities(){
#pragma region Witch Right-click Ability (Transform)
Witch::rightClickAbility.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
p->SetupAfterImage();
p->afterImagePos=p->leapStartingPos=p->GetPos();
geom2d::line<float>targetLine{p->GetPos(),p->GetWorldAimingLocation(Player::USE_WALK_DIR,Player::INVERTED)};
@ -136,7 +136,7 @@ void Witch::InitializeClassAbilities(){
#pragma endregion
#pragma region Witch Ability 1 (Curse of Pain)
Witch::ability1.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
std::optional<std::weak_ptr<Monster>>curseTarget{Monster::GetNearestMonster(pos,"Witch.Ability 1.Casting Range"_F/100.f*24,p->OnUpperLevel(),p->GetZ())};
if(curseTarget.has_value()&&!curseTarget.value().expired()){
//NOTE: If we have to change/modify Curse of Pain, we must also modify it in Monster::OnDeath (Monster.cpp)
@ -165,7 +165,7 @@ void Witch::InitializeClassAbilities(){
#pragma endregion
#pragma region Witch Ability 2 (Throw Poison)
Witch::ability2.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
int additionalBounceCount{0};
if(p->HasEnchant("Poison Bounce"))additionalBounceCount+="Poison Bounce"_ENC["BOUNCE COUNT"];
const float totalFallTime{util::lerp(1/30.f*(additionalBounceCount+1),0.3f,util::distance(p->GetPos(),pos)/("Witch.Ability 2.Casting Range"_F/100.f*24))};
@ -175,7 +175,7 @@ void Witch::InitializeClassAbilities(){
#pragma endregion
#pragma region Witch Ability 3 (Curse of Death)
Witch::ability3.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
std::optional<std::weak_ptr<Monster>>curseTarget{Monster::GetNearestMonster(pos,"Witch.Ability 3.Casting Range"_F/100.f*24,p->OnUpperLevel(),p->GetZ())};
if(curseTarget.has_value()&&!curseTarget.value().expired()){
const float curseDuration{"Witch.Ability 3.Curse Duration"_F};

View File

@ -113,7 +113,7 @@ bool Wizard::AutoAttack(){
void Wizard::InitializeClassAbilities(){
#pragma region Wizard Right-click Ability (Teleport)
Wizard::rightClickAbility.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
float pointMouseDirection=atan2(p->GetWorldAimingLocation(Player::USE_WALK_DIR,Player::INVERTED).y-p->GetPos().y,p->GetWorldAimingLocation(Player::USE_WALK_DIR,Player::INVERTED).x-p->GetPos().x);
vf2d pointTowardsMouse={cos(pointMouseDirection),sin(pointMouseDirection)};
float dist=std::clamp(geom2d::line<float>{p->GetPos(),p->GetWorldAimingLocation(Player::USE_WALK_DIR,Player::INVERTED)}.length(),0.f,"Wizard.Right Click Ability.TeleportRange"_F/100*24);
@ -180,7 +180,7 @@ void Wizard::InitializeClassAbilities(){
#pragma endregion
#pragma region Wizard Ability 1 (Fire Bolt)
Wizard::ability1.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
float angleToCursor=atan2(p->GetWorldAimingLocation().y-p->GetPos().y,p->GetWorldAimingLocation().x-p->GetPos().x);
CreateBullet(FireBolt)(p->GetPos(),{cos(angleToCursor)*"Wizard.Ability 1.BulletSpeed"_F,sin(angleToCursor)*"Wizard.Ability 1.BulletSpeed"_F},"Wizard.Ability 1.Radius"_F/100*12,int(p->GetAttack()*"Wizard.Ability 1.InitialDamageMult"_F),p->upperLevel,FRIENDLY,"Wizard.Ability 1.BulletColor"_Pixel)EndBullet;
return true;
@ -188,7 +188,7 @@ void Wizard::InitializeClassAbilities(){
#pragma endregion
#pragma region Wizard Ability 2 (Lightning Bolt)
Wizard::ability2.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
float angleToCursor=atan2(p->GetWorldAimingLocation().y-p->GetPos().y,p->GetWorldAimingLocation().x-p->GetPos().x);
CreateBullet(LightningBolt)(p->GetPos(),{cos(angleToCursor)*"Wizard.Ability 2.BulletSpeed"_F,sin(angleToCursor)*"Wizard.Ability 2.BulletSpeed"_F},"Wizard.Ability 2.Radius"_F/100*12,int(p->GetAttack()*"Wizard.Ability 2.DamageMult"_F),p->upperLevel,FRIENDLY,"Wizard.Ability 2.BulletColor"_Pixel)EndBullet;
return true;
@ -196,7 +196,7 @@ void Wizard::InitializeClassAbilities(){
#pragma endregion
#pragma region Wizard Ability 3 (Meteor)
Wizard::ability3.action=
[](Player*p,vf2d pos={}){
[](Entity e,vf2d pos={}){Player*p=e.ToPlayer(); //TODO
Meteor::MeteorSetting meteorType{Meteor::METEOR};
if(p->HasEnchant("Summon Comet")&&p->HasEnchant("Solar Flare"))meteorType=Meteor::COMET_FLARE;
else if(p->HasEnchant("Summon Comet"))meteorType=Meteor::COMET;

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.1" class="Map" orientation="orthogonal" renderorder="right-down" width="262" height="167" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="72">
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="262" height="167" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="72">
<properties>
<property name="Backdrop" propertytype="Backdrop" value="mountain_day"/>
<property name="Background Music" propertytype="BGM" value="mountain"/>

View File

@ -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="76" height="85" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="33">
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="76" height="85" tilewidth="24" tileheight="24" infinite="0" nextlayerid="7" nextobjectid="34">
<properties>
<property name="Background Music" propertytype="BGM" value="beach_boss"/>
<property name="Level Type" type="int" propertytype="LevelType" value="1"/>
@ -570,12 +570,12 @@
</properties>
<ellipse/>
</object>
<object id="31" template="../maps/Monsters/Ghost of Pirate Captain.tx" x="774" y="1212">
<object id="32" template="../maps/Monsters/Pirate's Treasure.tx" x="834" y="1506">
<properties>
<property name="spawner" type="object" value="30"/>
</properties>
</object>
<object id="32" template="../maps/Monsters/Pirate's Treasure.tx" x="834" y="1506">
<object id="33" template="../maps/Monsters/Warrior+Thief.tx" x="906" y="1116">
<properties>
<property name="spawner" type="object" value="30"/>
</properties>

View File

@ -1476,4 +1476,10 @@ MonsterStrategy
Hide Range = 500
Hide Fade Speed = 0.5s
}
Warrior+Thief
{
Warrior.Right Click Ability.Duration = 3s
Warrior.Right Click Ability.SlowAmt = 0.3
Warrior.Right Click Ability.Cooldown = 15s
}
}

View File

@ -2499,4 +2499,46 @@ Monsters
# Drop Item Name, Drop Percentage(0-100%), Drop Min Quantity, Drop Max Quantity
# DROP[0] = Frog Skin,50%,1,1
}
Warrior+Thief
{
Health = 14000
Attack = 40
CollisionDmg = 40
MoveSpd = 100%
Size = 100%
XP = 0
# A flag to show an arrow indicator when the boss is off-screen.
ShowBossIndicator = True
Strategy = Warrior+Thief
#Size of each animation frame
SheetFrameSize = 24,24
# 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
Animations
{
# Frame Count, Frame Speed (s), Frame Cycling (Repeat,OneShot,PingPong,Reverse,ReverseOneShot)
# Animations must be defined in the same order as they are in their sprite sheets
# The First Four animations must represent a standing, walking, attack, and death animation. Their names are up to the creator.
IDLE = 4, 0.3, Repeat
HURT = 4, 0.25, Repeat
ATTACKING = 4, 0.15, PingPong
DEATH = 4, 0.15, OneShot
}
# Drop Item Name, Drop Percentage(0-100%), Drop Min Quantity, Drop Max Quantity
DROP[0] = Octopus Ring,100%,1,1
DROP[1] = Takoyaki,100%,1,1
Hurt Sound = Monster Hurt
Death Sound = Slime Dead
Walk Sound = Slime Walk
}
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.2" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="24" tileheight="24" infinite="1" nextlayerid="3" nextobjectid="58">
<map version="1.10" tiledversion="1.10.2" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="24" tileheight="24" infinite="1" nextlayerid="3" nextobjectid="59">
<tileset firstgid="1" source="Monsters.tsx"/>
<layer id="1" name="Tile Layer 1" width="30" height="20">
<data encoding="csv"/>
@ -47,5 +47,6 @@
<object id="52" name="Spider" type="Monster" gid="42" x="498" y="642" width="33.6" height="33.6"/>
<object id="54" template="Monsters/Purple Slime.tx" type="Monster" x="486" y="714"/>
<object id="56" template="Monsters/Giant Purple Slime.tx" type="Monster" x="522" y="684"/>
<object id="58" template="Monsters/Warrior+Thief.tx" type="Monster" x="420" y="816"/>
</objectgroup>
</map>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<template>
<tileset firstgid="1" source="../Monsters.tsx"/>
<object name="Warrior+Thief" type="Monster" gid="45" width="48" height="48"/>
</template>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 30 KiB