diff --git a/src/servers/Server_Common/Exd/ExdData.cpp b/src/servers/Server_Common/Exd/ExdData.cpp index 17007e55..3eb295fd 100644 --- a/src/servers/Server_Common/Exd/ExdData.cpp +++ b/src/servers/Server_Common/Exd/ExdData.cpp @@ -345,30 +345,20 @@ bool Core::Data::ExdData::loadActionInfo() uint8_t points_type = getField< uint8_t >( fields, 30 ); // 30 uint16_t points_cost = getField< uint16_t >( fields, 31 ); // 31 -<<<<<<< HEAD + bool is_instant = getField< bool >( fields, 35 ); // 35 uint16_t cast_time = getField< uint16_t >( fields, 36 ); // 36 uint16_t recast_time = getField< uint16_t >( fields, 37 ); // 37 int8_t model = getField< int8_t >( fields, 39 ); // 39 uint8_t aspect = getField< uint8_t >( fields, 40 ); // 40 - + + uint16_t toggle_status_id = getField< uint16_t >( fields, 42 ); // 42 + bool affects_position = getField< bool >( fields, 47 ); // 47 info->id = id; info->name = name; info->category = category; -======= - bool is_instant = getField< bool >( fields, 35 ); // 35 - uint16_t cast_time = getField< uint16_t >( fields, 36 ); // 36 - uint16_t recast_time = getField< uint16_t >( fields, 37 ); // 37 - - int8_t model = getField< int8_t >( fields, 39 ); // 39: Action model - uint8_t aspect = getField< uint8_t >( fields, 40 ); // 40: Action aspect - - info->id = id; - info->name = name; - info->category = category; ->>>>>>> 08f4c7651fafdaf8f4d98868a94ab4688eb71379 info->class_job = class_job; info->unlock_level = unlock_level; @@ -382,6 +372,7 @@ bool Core::Data::ExdData::loadActionInfo() info->is_ground_aoe = is_ground_aoe; + info->aoe_type = aoe_type; info->aoe_range = aoe_range; info->aoe_width = aoe_width; @@ -396,6 +387,12 @@ bool Core::Data::ExdData::loadActionInfo() info->model = model; info->aspect = aspect; + info->toggle_status_id = toggle_status_id; + info->affects_position = affects_position; + + // If action type is SingleTarget with an AoE radius set, or if action type isn't SingleTarget + info->is_aoe = ( info->aoe_type == 1 && info->aoe_width != 0 ) || ( info->aoe_type != 1 ); + m_actionInfoMap.emplace( std::make_pair( info->id, info ) ); } diff --git a/src/servers/Server_Common/Exd/ExdData.h b/src/servers/Server_Common/Exd/ExdData.h index a23d21ff..f34e6b35 100644 --- a/src/servers/Server_Common/Exd/ExdData.h +++ b/src/servers/Server_Common/Exd/ExdData.h @@ -250,6 +250,12 @@ namespace Core { int8_t model; // 39 uint8_t aspect; // 40 + + uint16_t toggle_status_id; // 42 + + bool affects_position; // 47 + + bool is_aoe; // Internal only }; struct EventItemInfo diff --git a/src/servers/Server_Zone/Action/ActionCollision.cpp b/src/servers/Server_Zone/Action/ActionCollision.cpp index 4c193fd7..a0ab372e 100644 --- a/src/servers/Server_Zone/Action/ActionCollision.cpp +++ b/src/servers/Server_Zone/Action/ActionCollision.cpp @@ -11,46 +11,46 @@ using namespace Core::Entity; using namespace Core::Common; -// todo: add filters for allies, enemies only etc +// todo: add AoE actor limits (16, 32) -bool ActionCollision::isActorCollisionValid( ActorPtr actorPtr, AoeFilter aoeFilter ) +bool ActionCollision::isActorApplicable( ActorPtr actorPtr, TargetFilter targetFilter ) { - bool collisionApplicable = false; - switch ( aoeFilter ) + bool actorApplicable = false; + switch ( targetFilter ) { - case AoeFilter::All: + case TargetFilter::All: { - collisionApplicable = true; + actorApplicable = true; break; } - case AoeFilter::Players: + case TargetFilter::Players: { - collisionApplicable = actorPtr->isPlayer(); + actorApplicable = actorPtr->isPlayer(); break; } - case AoeFilter::Allies: + case TargetFilter::Allies: { // todo: implement ally NPCs - collisionApplicable = !actorPtr->isMob(); + actorApplicable = !actorPtr->isMob(); break; } - case AoeFilter::Party: + case TargetFilter::Party: { // todo: implement party - collisionApplicable = actorPtr->isPlayer(); + actorApplicable = actorPtr->isPlayer(); break; } - case AoeFilter::Enemies: + case TargetFilter::Enemies: { - collisionApplicable = actorPtr->isMob(); + actorApplicable = actorPtr->isMob(); break; } } - return ( collisionApplicable && actorPtr->isAlive() ); + return ( actorApplicable && actorPtr->isAlive() ); } -std::set< Core::Entity::ActorPtr > ActionCollision::getActorsHitFromAction( FFXIVARR_POSITION3 aoePosition, std::set< ActorPtr > actorsInRange, boost::shared_ptr< Core::Data::ActionInfo > actionInfo, AoeFilter aoeFilter ) +std::set< Core::Entity::ActorPtr > ActionCollision::getActorsHitFromAction( FFXIVARR_POSITION3 aoePosition, std::set< ActorPtr > actorsInRange, boost::shared_ptr< Core::Data::ActionInfo > actionInfo, TargetFilter targetFilter ) { std::set< ActorPtr > actorsCollided; @@ -61,15 +61,14 @@ std::set< Core::Entity::ActorPtr > ActionCollision::getActorsHitFromAction( FFXI { // This is actually needed. There is "splash damage" in actions marked as single target. // Notice how we're using aoe_width. How collision works for SingleTarget is unknown as of now. - // TODO: Isn't it possible to stack 2 players in the same spot and glitch the action collision this way? Investigate for ( auto pActor : actorsInRange ) { // Make sure actor exists. If it doesn't we done goofed. assert( pActor ); // Don't bother wasting on collision if actor doesn't apply for it - if ( !isActorCollisionValid( pActor, aoeFilter ) ) - break; + if ( !isActorApplicable( pActor, targetFilter ) ) + continue; // Test our collision from actor with the area generated by the action from the AoE data if ( radiusCollision( pActor->getPos(), aoePosition, actionInfo->aoe_width ) ) @@ -86,8 +85,8 @@ std::set< Core::Entity::ActorPtr > ActionCollision::getActorsHitFromAction( FFXI { assert( pActor ); - if ( !isActorCollisionValid( pActor, aoeFilter ) ) - break; + if ( !isActorApplicable( pActor, targetFilter ) ) + continue; if ( radiusCollision( pActor->getPos(), aoePosition, actionInfo->aoe_range ) ) { @@ -102,8 +101,8 @@ std::set< Core::Entity::ActorPtr > ActionCollision::getActorsHitFromAction( FFXI { assert( pActor ); - if ( !isActorCollisionValid( pActor, aoeFilter ) ) - break; + if ( !isActorApplicable( pActor, targetFilter ) ) + continue; if ( boxCollision( pActor->getPos(), aoePosition, actionInfo->aoe_width, actionInfo->aoe_range ) ) { diff --git a/src/servers/Server_Zone/Action/ActionCollision.h b/src/servers/Server_Zone/Action/ActionCollision.h index 9cd92534..cff25934 100644 --- a/src/servers/Server_Zone/Action/ActionCollision.h +++ b/src/servers/Server_Zone/Action/ActionCollision.h @@ -9,7 +9,7 @@ namespace Core { namespace Entity { - enum class AoeFilter + enum class TargetFilter { All, // All actors in the AoE are applicable for collision Players, // Only players @@ -22,8 +22,8 @@ namespace Core { { public: - static bool isActorCollisionValid( ActorPtr actorPtr, AoeFilter aoeFilter ); - static std::set< ActorPtr > getActorsHitFromAction( Common::FFXIVARR_POSITION3 aoePosition, std::set< ActorPtr > actorsInRange, boost::shared_ptr< Data::ActionInfo > actionInfo, AoeFilter aoeFilter ); + static bool isActorApplicable( ActorPtr actorPtr, TargetFilter targetFilter ); + static std::set< ActorPtr > getActorsHitFromAction( Common::FFXIVARR_POSITION3 aoePosition, std::set< ActorPtr > actorsInRange, boost::shared_ptr< Data::ActionInfo > actionInfo, TargetFilter targetFilter ); private: static bool radiusCollision( Common::FFXIVARR_POSITION3 actorPosition, Common::FFXIVARR_POSITION3 aoePosition, uint16_t radius ); diff --git a/src/servers/Server_Zone/Actor/Actor.cpp b/src/servers/Server_Zone/Actor/Actor.cpp index 8a5b61a2..b9d9e210 100644 --- a/src/servers/Server_Zone/Actor/Actor.cpp +++ b/src/servers/Server_Zone/Actor/Actor.cpp @@ -638,54 +638,67 @@ void Core::Entity::Actor::handleScriptSkill( uint32_t type, uint32_t actionId, u getAsPlayer()->sendDebug( std::to_string( pTarget.getId() ) ); getAsPlayer()->sendDebug( "Handle script skill type: " + std::to_string( type ) ); } - auto actionInfoPtr = g_exdData.getActionInfo( actionId ); + // Todo: Effect packet generator. 90% of this is basically setting params and it's basically unreadable. // Prepare packet. This is seemingly common for all packets in the action handler. GamePacketNew< FFXIVIpcEffect, ServerZoneIpcType > effectPacket( getId() ); effectPacket.data().targetId = pTarget.getId(); effectPacket.data().actionAnimationId = actionId; + effectPacket.data().unknown_62 = 1; // Affects displaying action name next to number in floating text effectPacket.data().unknown_2 = 1; // This seems to have an effect on the "double-cast finish" animation - // effectPacket.data().unknown_3 = 1; effectPacket.data().actionTextId = actionId; effectPacket.data().numEffects = 1; effectPacket.data().rotation = Math::Util::floatToUInt16Rot( getRotation() ); effectPacket.data().effectTarget = pTarget.getId(); - effectPacket.data().effects[0].value = 0; - effectPacket.data().effects[0].effectType = ActionEffectType::Damage; - effectPacket.data().effects[0].hitSeverity = ActionHitSeverityType::NormalDamage; - effectPacket.data().effects[0].unknown_3 = 7; + // Todo: for each actor, calculate how much damage the calculated value should deal to them - 2-step damage calc. we only have 1-step switch ( type ) { case ActionEffectType::Damage: { - - effectPacket.data().effects[0].value = static_cast< int16_t >( param1 ); + effectPacket.data().effects[0].value = param1; effectPacket.data().effects[0].effectType = ActionEffectType::Damage; effectPacket.data().effects[0].hitSeverity = ActionHitSeverityType::NormalDamage; + effectPacket.data().effects[0].unknown_3 = 7; - std::set< ActorPtr > actorsCollided = ActionCollision::getActorsHitFromAction( pTarget.getPos(), getInRangeActors( true ), actionInfoPtr, AoeFilter::Enemies ); - - for ( auto pHitActor : actorsCollided ) + if ( !actionInfoPtr->is_aoe ) { - effectPacket.data().targetId = pHitActor->getId(); - effectPacket.data().unknown_1 = 1; // the magic trick for getting it to work - effectPacket.data().unknown_5 = 1; - effectPacket.data().unknown_8 = 1; - effectPacket.data().actionTextId = 0; - effectPacket.data().effectTarget = pHitActor->getId(); - effectPacket.data().effects[0].value = param1 + ( rand() % 15 ); + // If action on this specific target is valid... + if ( isPlayer() && !ActionCollision::isActorApplicable( pTarget.shared_from_this(), TargetFilter::Enemies ) ) + break; - pHitActor->sendToInRangeSet( effectPacket, true ); // todo: send to range of what? ourselves? when mob script hits this is going to be lacking - pHitActor->takeDamage( static_cast< uint32_t >( param1 ) ); - pHitActor->onActionHostile( shared_from_this() ); + pTarget.takeDamage( static_cast< uint32_t >( param1 ) ); + pTarget.onActionHostile( shared_from_this() ); + sendToInRangeSet( effectPacket, true ); + } + else + { - if ( isPlayer() ) - getAsPlayer()->sendDebug( "AoE hit actor " + pHitActor->getName() ); + std::set< ActorPtr > actorsCollided = ActionCollision::getActorsHitFromAction( pTarget.getPos(), getInRangeActors( true ), actionInfoPtr, TargetFilter::Enemies ); + + for ( auto pHitActor : actorsCollided ) + { + effectPacket.data().targetId = pHitActor->getId(); + effectPacket.data().effectTarget = pHitActor->getId(); + + sendToInRangeSet( effectPacket, true ); // todo: send to range of what? ourselves? when mob script hits this is going to be lacking + pHitActor->takeDamage( static_cast< uint32_t >( param1 ) ); + pHitActor->onActionHostile( shared_from_this() ); + + // Debug + if ( isPlayer() ) + { + if ( pHitActor->isPlayer() ) { + getAsPlayer()->sendDebug( "AoE hit actor " + std::to_string( pHitActor->getId() ) + " (" + pHitActor->getName() + ")" ); + } + else + getAsPlayer()->sendDebug( "AoE hit actor " + std::to_string( pHitActor->getId() ) ); + } + } } break; @@ -699,29 +712,39 @@ void Core::Entity::Actor::handleScriptSkill( uint32_t type, uint32_t actionId, u effectPacket.data().effects[0].effectType = ActionEffectType::Heal; effectPacket.data().effects[0].hitSeverity = ActionHitSeverityType::NormalHeal; - sendToInRangeSet( effectPacket, true ); - - // todo: get proper packets: the following was just kind of thrown together from what we know - - std::set< ActorPtr > actorsCollided = ActionCollision::getActorsHitFromAction( pTarget.getPos(), getInRangeActors( true ), actionInfoPtr, AoeFilter::Allies ); - - for ( auto pHitActor : actorsCollided ) + if ( !actionInfoPtr->is_aoe ) { - effectPacket.data().targetId = pHitActor->getId(); - effectPacket.data().unknown_1 = 1; // the magic trick for getting it to work - effectPacket.data().unknown_5 = 1; - effectPacket.data().unknown_8 = 1; - effectPacket.data().actionTextId = 0; - effectPacket.data().effectTarget = pHitActor->getId(); - effectPacket.data().effects[0].value = calculatedHeal + ( rand() % 15 ); + if ( isPlayer() && !ActionCollision::isActorApplicable( pTarget.shared_from_this(), TargetFilter::Allies ) ) + break; - pHitActor->sendToInRangeSet( effectPacket, true ); - pHitActor->heal( calculatedHeal ); - - if ( isPlayer() ) - getAsPlayer()->sendDebug( "AoE hit actor " + pHitActor->getName() ); + sendToInRangeSet( effectPacket, true ); + pTarget.heal( calculatedHeal ); } + else + { + // todo: get proper packets: the following was just kind of thrown together from what we know. atm buggy (packets look "delayed" from client) + std::set< ActorPtr > actorsCollided = ActionCollision::getActorsHitFromAction( pTarget.getPos(), getInRangeActors( true ), actionInfoPtr, TargetFilter::Allies ); + + for ( auto pHitActor : actorsCollided ) + { + effectPacket.data().targetId = pTarget.getId(); + effectPacket.data().effectTarget = pHitActor->getId(); + + sendToInRangeSet( effectPacket, true ); + pHitActor->heal( calculatedHeal ); + + // Debug + if ( isPlayer() ) + { + if ( pHitActor->isPlayer() ) { + getAsPlayer()->sendDebug( "AoE hit actor " + std::to_string( pHitActor->getId() ) + " (" + pHitActor->getName() + ")" ); + } + else + getAsPlayer()->sendDebug( "AoE hit actor " + std::to_string( pHitActor->getId() ) ); + } + } + } break; } diff --git a/src/servers/Server_Zone/Actor/Player.h b/src/servers/Server_Zone/Actor/Player.h index 11104463..568419f9 100644 --- a/src/servers/Server_Zone/Actor/Player.h +++ b/src/servers/Server_Zone/Actor/Player.h @@ -103,7 +103,7 @@ public: /*! load data for currently active quests */ bool loadActiveQuests(); /*! update quest ( register it as active quest if new ) */ - void updateQuest( uint16_t questId, uint16_t sequence ); + void updateQuest( uint16_t questId, uint8_t sequence ); /*! return true if quest is currently active */ bool hasQuest( uint16_t questId ); /*! return the current quest sequence */ diff --git a/src/servers/Server_Zone/Actor/PlayerQuest.cpp b/src/servers/Server_Zone/Actor/PlayerQuest.cpp index c6038f61..668460a7 100644 --- a/src/servers/Server_Zone/Actor/PlayerQuest.cpp +++ b/src/servers/Server_Zone/Actor/PlayerQuest.cpp @@ -74,7 +74,7 @@ bool Core::Entity::Player::loadActiveQuests() void Core::Entity::Player::finishQuest( uint16_t questId ) { - int8_t idx = getQuestIndex( static_cast< uint16_t >( questId ) ); + int16_t idx = getQuestIndex( questId ); if( ( idx != -1 ) && ( m_activeQuests[idx] != nullptr ) ) { @@ -100,7 +100,7 @@ void Core::Entity::Player::finishQuest( uint16_t questId ) m_questTracking[ii] = -1; } - boost::shared_ptr pQuest = m_activeQuests[idx]; + boost::shared_ptr< QuestActive > pQuest = m_activeQuests[idx]; m_activeQuests[idx].reset(); m_freeQuestIdxQueue.push( idx ); @@ -123,7 +123,7 @@ void Core::Entity::Player::unfinishQuest( uint16_t questId ) void Core::Entity::Player::removeQuest( uint16_t questId ) { - int8_t idx = getQuestIndex( static_cast< uint16_t >( questId ) ); + int16_t idx = getQuestIndex( questId ); if( ( idx != -1 ) && ( m_activeQuests[idx] != nullptr ) ) { @@ -973,7 +973,7 @@ uint8_t Core::Entity::Player::getQuestSeq( uint16_t questId ) return 0; } -void Core::Entity::Player::updateQuest( uint16_t questId, uint16_t sequence ) +void Core::Entity::Player::updateQuest( uint16_t questId, uint8_t sequence ) { if( hasQuest( questId ) ) { diff --git a/src/servers/Server_Zone/Inventory/Inventory.cpp b/src/servers/Server_Zone/Inventory/Inventory.cpp index bffe043b..2b552583 100644 --- a/src/servers/Server_Zone/Inventory/Inventory.cpp +++ b/src/servers/Server_Zone/Inventory/Inventory.cpp @@ -440,14 +440,14 @@ bool Core::Inventory::removeCrystal( CrystalType type, uint32_t amount ) return true; } -bool Core::Inventory::isObtainable( uint32_t catalogId, uint16_t quantity ) +bool Core::Inventory::isObtainable( uint32_t catalogId, uint8_t quantity ) { return true; } -int16_t Core::Inventory::addItem( uint16_t inventoryId, int8_t slotId, uint32_t catalogId, uint16_t quantity ) +int16_t Core::Inventory::addItem( uint16_t inventoryId, int8_t slotId, uint32_t catalogId, uint8_t quantity ) { auto itemInfo = g_exdData.getItemInfo( catalogId ); @@ -587,7 +587,7 @@ void Core::Inventory::swapItem( uint16_t fromInventoryId, uint8_t fromSlotId, ui { updateContainer( fromInventoryId, fromSlotId, nullptr ); fromInventoryId = getArmoryToEquipSlot( toSlot ); - fromSlotId = m_inventoryMap[fromInventoryId]->getFreeSlot(); + fromSlotId = static_cast < uint8_t >( m_inventoryMap[fromInventoryId]->getFreeSlot() ); } auto containerTypeFrom = getContainerType( fromInventoryId ); diff --git a/src/servers/Server_Zone/Inventory/Inventory.h b/src/servers/Server_Zone/Inventory/Inventory.h index adc99715..63dab641 100644 --- a/src/servers/Server_Zone/Inventory/Inventory.h +++ b/src/servers/Server_Zone/Inventory/Inventory.h @@ -140,7 +140,7 @@ public: InvSlotPairVec getSlotsOfItemsInInventory( uint32_t catalogId ); InvSlotPair getFreeBagSlot(); - int16_t addItem( uint16_t inventoryId, int8_t slotId, uint32_t catalogId, uint16_t quantity = 1 ); + int16_t addItem( uint16_t inventoryId, int8_t slotId, uint32_t catalogId, uint8_t quantity = 1 ); void moveItem( uint16_t fromInventoryId, uint8_t fromSlotId, uint16_t toInventoryId, uint8_t toSlot ); void swapItem( uint16_t fromInventoryId, uint8_t fromSlotId, uint16_t toInventoryId, uint8_t toSlot ); void discardItem( uint16_t fromInventoryId, uint8_t fromSlotId ); @@ -175,7 +175,7 @@ public: bool addCrystal( CrystalType type, uint32_t amount ); /*! remove amount from the crystals of type */ bool removeCrystal( CrystalType type, uint32_t amount ); - bool isObtainable( uint32_t catalogId, uint16_t quantity ); + bool isObtainable( uint32_t catalogId, uint8_t quantity ); void updateCrystalDb();