mirror of
https://github.com/UOX3DevTeam/UOX3
synced 2026-08-13 12:27:04 -04:00
Fixed an issue with onDyeTarget event where the dyetub and target object parameters were not set properly Fixed some incorrect casting animation-related variable assignments for AI_HEALER and AI_EVIL_HEALER Fixed an issue with pets not going wild from lack of loyalty to their owners when at zero loyalty Addressed issues with idle/fidget animation setup that could cause NPCs to blink out of existence Removed stray semicolon behind control statement related to triggering of OnFacetChange JS event Fixed an issue with Bless spell when cast from item Some minor code cleanup
548 lines
15 KiB
C++
548 lines
15 KiB
C++
#include "uox3.h"
|
|
#include "townregion.h"
|
|
#include "cRaces.h"
|
|
#include "cServerDefinitions.h"
|
|
#include "ssection.h"
|
|
#include "cEffects.h"
|
|
#include "CPacketSend.h"
|
|
#include "regions.h"
|
|
|
|
using namespace std::string_literals;
|
|
|
|
cEffects *Effects;
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - cEffects::PlaySound()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Plays sound effect for player, echo's to all players if allHear is true
|
|
//o------------------------------------------------------------------------------------------------o
|
|
void cEffects::PlaySound( CSocket *mSock, UI16 soundId, bool allHear )
|
|
{
|
|
if( mSock == nullptr )
|
|
return;
|
|
|
|
CChar *mChar = mSock->CurrcharObj();
|
|
if( !ValidateObject( mChar ))
|
|
return;
|
|
|
|
CPPlaySoundEffect toSend = ( *mChar );
|
|
toSend.Model( soundId );
|
|
|
|
if( allHear )
|
|
{
|
|
auto nearbyChars = FindNearbyPlayers( mChar );
|
|
std::for_each( nearbyChars.begin(), nearbyChars.end(), [&toSend]( CSocket *entry )
|
|
{
|
|
entry->Send( &toSend );
|
|
});
|
|
}
|
|
mSock->Send( &toSend );
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - cEffects::PlaySound()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Plays sound effect originating from object for all players nearby(default) or originator only
|
|
//o------------------------------------------------------------------------------------------------o
|
|
void cEffects::PlaySound( CBaseObject *baseObj, UI16 soundId, bool allHear )
|
|
{
|
|
if( !ValidateObject( baseObj ))
|
|
return;
|
|
|
|
CPPlaySoundEffect toSend = ( *baseObj );
|
|
toSend.Model( soundId );
|
|
|
|
if( allHear )
|
|
{
|
|
auto nearbyChars = FindPlayersInVisrange( baseObj );
|
|
std::for_each( nearbyChars.begin(), nearbyChars.end(), [&toSend]( CSocket *entry )
|
|
{
|
|
entry->Send( &toSend );
|
|
});
|
|
}
|
|
else
|
|
{
|
|
if( baseObj->GetObjType() == OT_CHAR )
|
|
{
|
|
CSocket *mSock = ( static_cast<CChar *>( baseObj ))->GetSocket();
|
|
if( mSock != nullptr )
|
|
{
|
|
mSock->Send( &toSend );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - cEffects::ItemSound()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Play item drop sound based on ID (Gems, Gold, or Default)
|
|
//o------------------------------------------------------------------------------------------------o
|
|
void cEffects::ItemSound( CSocket *s, CItem *item, bool allHear )
|
|
{
|
|
UI16 itemId = item->GetId();
|
|
if( itemId >= 0x0F0F && itemId <= 0x0F20 ) // Large Gem Stones
|
|
{
|
|
PlaySound( s, 0x0034, allHear );
|
|
}
|
|
else if( itemId >= 0x0F21 && itemId <= 0x0F30 ) // Small Gem Stones
|
|
{
|
|
PlaySound( s, 0x0033, allHear );
|
|
}
|
|
else if( itemId >= 0x0EEA && itemId <= 0x0EF2 ) // Gold
|
|
{
|
|
GoldSound( s, item->GetAmount(), allHear );
|
|
}
|
|
else
|
|
{
|
|
UI16 effectId = 0x0042;
|
|
CBaseObject *getCont = item->GetCont();
|
|
if( getCont != nullptr )
|
|
{
|
|
if( getCont->GetObjType() == OT_ITEM )
|
|
{
|
|
CItem *iCont = static_cast<CItem *>( getCont );
|
|
switch( iCont->GetId() )
|
|
{
|
|
case 0x0E75: // backpack
|
|
case 0x0E76: // leather bag
|
|
case 0x0E79: // pouch
|
|
case 0x09B0: // pouch
|
|
case 0x2AF8: // Shopkeeper buy, sell and sold layers
|
|
case 0x2256: // bagball
|
|
case 0x2257: // bagball
|
|
effectId = 0x0048;
|
|
break;
|
|
case 0x0E7A: // picnic basket
|
|
case 0x24D5: // SE basket
|
|
case 0x24D6: // SE basket
|
|
case 0x24D9: // SE basket
|
|
case 0x24DA: // SE basket
|
|
case 0x0990: // basket
|
|
case 0x09AC: // bushel
|
|
case 0x09B1: // basket
|
|
case 0x24D7: // SE basket
|
|
case 0x24D8: // SE basket
|
|
case 0x24DD: // SE basket
|
|
case 0x24DB: // SE basket
|
|
case 0x24DC: // SE basket
|
|
effectId = 0x004F;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
effectId = 0x0048;
|
|
}
|
|
}
|
|
PlaySound( s, effectId, allHear );
|
|
}
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - cEffects::GoldSound()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Play gold drop sound based on Amount
|
|
//o------------------------------------------------------------------------------------------------o
|
|
void cEffects::GoldSound( CSocket *s, UI32 goldTotal, bool allHear )
|
|
{
|
|
if( goldTotal <= 1 )
|
|
{
|
|
PlaySound( s, 0x0035, allHear );
|
|
}
|
|
else if( goldTotal < 6 )
|
|
{
|
|
PlaySound( s, 0x0036, allHear );
|
|
}
|
|
else
|
|
{
|
|
PlaySound( s, 0x0037, allHear );
|
|
}
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - cEffects::PlayDeathSound()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Send death sound to all sockets in range
|
|
//o------------------------------------------------------------------------------------------------o
|
|
void cEffects::PlayDeathSound( CChar *i )
|
|
{
|
|
if( i->GetOrgId() == 0x0191 || i->GetOrgId() == 0x025E || i->GetOrgId() == 0x029B || i->GetOrgId() == 0x00B8 || i->GetOrgId() == 0x00BA || i->GetId() == 0x02EF ) // Female Death (human/elf/gargoyle/savage)
|
|
{
|
|
UI16 deathSound = 0x0150 + RandomNum( 0, 3 );
|
|
PlaySound( i, deathSound );
|
|
}
|
|
else if( i->GetOrgId() == 0x0190 || i->GetOrgId() == 0x025D || i->GetOrgId() == 0x029A || i->GetOrgId() == 0x00B7 || i->GetOrgId() == 0x00B9 || i->GetOrgId() == 0x02EE ) // Male Death (human/elf/gargoyle/savage)
|
|
{
|
|
UI16 deathSound = 0x015A + RandomNum( 0, 3 );
|
|
PlaySound( i, deathSound );
|
|
}
|
|
else
|
|
{
|
|
const UI16 toPlay = cwmWorldState->creatures[i->GetOrgId()].GetSound( SND_DIE );
|
|
if( toPlay != 0x00 )
|
|
{
|
|
Effects->PlaySound( i, toPlay );
|
|
}
|
|
}
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - cEffects::PlayMusic()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Plays midi/mp3 in client
|
|
//o------------------------------------------------------------------------------------------------o
|
|
void cEffects::PlayMusic( CSocket *s, UI16 number )
|
|
{
|
|
CPPlayMusic toSend( number );
|
|
s->Send( &toSend );
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - cEffects::PlayBGSound()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Play background sounds based on location
|
|
//o------------------------------------------------------------------------------------------------o
|
|
void cEffects::PlayBGSound( CSocket& mSock, CChar& mChar )
|
|
{
|
|
std::vector<CChar *> inRange;
|
|
inRange.reserve( 11 );
|
|
|
|
for( auto &MapArea : MapRegion->PopulateList( &mChar ))
|
|
{
|
|
if( MapArea )
|
|
{
|
|
auto regChars = MapArea->GetCharList();
|
|
for( const auto &tempChar : regChars->collection() )
|
|
{
|
|
if( ValidateObject( tempChar ) && !tempChar->IsFree() && ( tempChar->GetInstanceId() == mChar.GetInstanceId() ))
|
|
{
|
|
if( tempChar->IsNpc() && !tempChar->IsDead() && !tempChar->IsAtWar() && CharInRange(( &mChar ), tempChar ))
|
|
{
|
|
inRange.push_back( tempChar );
|
|
}
|
|
|
|
if( inRange.size() == 11 )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UI16 basesound = 0;
|
|
if( !inRange.empty() )
|
|
{
|
|
CChar *soundSrc = inRange[RandomNum( static_cast<size_t>( 0 ), inRange.size() - 1 )];
|
|
UI16 xx = soundSrc->GetId();
|
|
if( cwmWorldState->creatures.find( xx ) == cwmWorldState->creatures.end() )
|
|
return;
|
|
|
|
//Play creature sounds, but add a small chance that they won't, to give players a break every now and then
|
|
UI08 soundChance = static_cast<UI08>( RandomNum( static_cast<size_t>( 0 ), inRange.size() + 9 ));
|
|
if( soundChance > 5 )
|
|
{
|
|
basesound = cwmWorldState->creatures[xx].GetSound( SND_IDLE );
|
|
if( basesound != 0x00 )
|
|
{
|
|
CPPlaySoundEffect toSend = ( *soundSrc );
|
|
toSend.Model( basesound );
|
|
mSock.Send( &toSend );
|
|
}
|
|
}
|
|
}
|
|
else // play random mystic-sounds also if no creature is in range
|
|
{
|
|
if( RandomNum( 0, 3332 ) == 33 )
|
|
{
|
|
switch( RandomNum( 0, 6 ))
|
|
{
|
|
case 0: basesound = 595; break; // gnome sound
|
|
case 1: basesound = 287; break; // bigfoot 1
|
|
case 2: basesound = 288; break; // bigfoot 2
|
|
case 3: basesound = 639; break; // old snake sound
|
|
case 4: basesound = 179; break; // lion sound
|
|
case 5: basesound = 246; break; // mystic
|
|
case 6: basesound = 253; break; // mystic II
|
|
default: break;
|
|
}
|
|
if( basesound != 0 )
|
|
{
|
|
CPPlaySoundEffect toSend = mChar;
|
|
toSend.Model( basesound );
|
|
mSock.Send( &toSend );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - cEffects::DoSocketMusic()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Sends message to client to play specific midi/mp3
|
|
//o------------------------------------------------------------------------------------------------o
|
|
auto cEffects::DoSocketMusic( CSocket *s ) -> void
|
|
{
|
|
// Return if the socket is not valid
|
|
if( s == nullptr )
|
|
return;
|
|
|
|
SI32 i = 0;
|
|
char musicArray[50];
|
|
std::string sect;
|
|
|
|
CChar *mChar = s->CurrcharObj();
|
|
|
|
CTownRegion *mReg = nullptr;
|
|
|
|
// Fetch subregion player is in, if any
|
|
auto subRegionNum = mChar->GetSubRegion();
|
|
if( subRegionNum != 0 )
|
|
{
|
|
if( cwmWorldState->townRegions.find( subRegionNum ) != cwmWorldState->townRegions.end() )
|
|
{
|
|
mReg = cwmWorldState->townRegions[subRegionNum];
|
|
}
|
|
}
|
|
|
|
if( mReg == nullptr )
|
|
{
|
|
// Otherwise, fetch the actual region player is in
|
|
mReg = mChar->GetRegion();
|
|
if( mReg == nullptr )
|
|
return;
|
|
}
|
|
|
|
UI16 musicList = mReg->GetMusicList();
|
|
if( musicList != 0 )
|
|
{
|
|
if( mChar->IsDead() )
|
|
{
|
|
sect = "MUSICLIST DEATH";
|
|
}
|
|
else if( mChar->IsAtWar() )
|
|
{
|
|
sect = "MUSICLIST COMBAT";
|
|
}
|
|
else
|
|
{
|
|
sect = std::string( "MUSICLIST " ) + oldstrutil::number( musicList );
|
|
}
|
|
|
|
auto MusicList = FileLookup->FindEntry( sect, regions_def );
|
|
if( MusicList )
|
|
{
|
|
for( const auto &sec : MusicList->collection() )
|
|
{
|
|
if( oldstrutil::upper( sec->tag ) == "MUSIC" )
|
|
{
|
|
musicArray[i++] = static_cast<SI08>( std::stoi( sec->data, nullptr, 0 ));
|
|
}
|
|
}
|
|
if( i != 0 )
|
|
{
|
|
i = RandomNum( 0, i - 1 );
|
|
PlayMusic( s, musicArray[i] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - cEffects::PlayTileSound()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Play a sound based on the tile character is on
|
|
//o------------------------------------------------------------------------------------------------o
|
|
auto cEffects::PlayTileSound( CChar *mChar, CSocket *mSock ) -> void
|
|
{
|
|
if( !ValidateObject( mChar ))
|
|
return;
|
|
|
|
if( mChar->GetVisible() != VT_VISIBLE || ( mChar->IsGM() || mChar->IsCounselor() ))
|
|
return;
|
|
|
|
if( mChar->IsFlying() ) // No footstep sounds for flying gargoyles
|
|
return;
|
|
|
|
enum TileType
|
|
{
|
|
TT_NORMAL = 0,
|
|
TT_WATER,
|
|
TT_STONE,
|
|
TT_OTHER,
|
|
TT_WOODEN,
|
|
TT_GRASS
|
|
};
|
|
TileType tileType = TT_NORMAL;
|
|
|
|
bool onHorse = mChar->IsOnHorse();
|
|
bool isRunning = ( mChar->GetRunning() > 0 );
|
|
auto artwork = Map->ArtAt( mChar->GetX(), mChar->GetY(), mChar->WorldNumber() );
|
|
std::sort( artwork.begin(), artwork.end(), []( const Tile_st &lhs, const Tile_st &rhs )
|
|
{
|
|
return ( lhs.altitude + lhs.artInfo->Height() ) < ( rhs.altitude + rhs.artInfo->Height() );
|
|
});
|
|
|
|
// now find the static that works for us
|
|
// start from the bottom of the vector, as we want the first one that is below us
|
|
auto alt = mChar->GetZ(); // so we dont keep calling it
|
|
auto iter = std::find_if( artwork.rbegin(), artwork.rend(), [alt]( const Tile_st &value )
|
|
{
|
|
return alt >= ( value.altitude + value.artInfo->Height() );
|
|
});
|
|
|
|
if( iter != artwork.rend() )
|
|
{
|
|
if( iter->artInfo->CheckFlag( TF_WET ))
|
|
{
|
|
tileType = TT_WATER;
|
|
}
|
|
if( iter->artInfo->CheckFlag( TF_SURFACE) || iter->artInfo->CheckFlag( TF_CLIMBABLE ))
|
|
{
|
|
auto pos = iter->artInfo->Name().find( "wood" );
|
|
if( pos != std::string::npos )
|
|
{
|
|
tileType = TT_WOODEN;
|
|
}
|
|
else
|
|
{
|
|
pos = iter->artInfo->Name().find( "ston" );
|
|
if( pos != std::string::npos )
|
|
{
|
|
tileType = TT_STONE;
|
|
}
|
|
else
|
|
{
|
|
pos = iter->artInfo->Name().find( "gras" );
|
|
if( pos != std::string::npos )
|
|
{
|
|
tileType = TT_GRASS;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UI16 soundId = 0;
|
|
switch( mChar->GetStep() ) // change step info
|
|
{
|
|
case 0:
|
|
if(( !isRunning && !onHorse) || ( isRunning && onHorse ))
|
|
{
|
|
mChar->SetStep( 1 ); // step 2
|
|
}
|
|
else if( isRunning || ( !isRunning && onHorse ))
|
|
{
|
|
mChar->SetStep( 2 ); // Running step 2
|
|
}
|
|
switch( tileType )
|
|
{
|
|
case TT_NORMAL:
|
|
if( onHorse )
|
|
{
|
|
if( isRunning )
|
|
{
|
|
soundId = 0x012A;
|
|
}
|
|
else
|
|
{
|
|
soundId = 0x024C;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
soundId = 0x012B;
|
|
}
|
|
break;
|
|
case TT_WATER: // water
|
|
break;
|
|
case TT_STONE: // stone
|
|
soundId = 0x0130;
|
|
break;
|
|
case TT_WOODEN: // wooden
|
|
soundId = 0x0123;
|
|
break;
|
|
case TT_OTHER: // other
|
|
[[fallthrough]];
|
|
case TT_GRASS: // grass
|
|
soundId = 0x012D;
|
|
break;
|
|
}
|
|
break;
|
|
case 1:
|
|
if( !isRunning || ( isRunning && onHorse ))
|
|
{
|
|
mChar->SetStep( 0 ); // step 1
|
|
}
|
|
else if( isRunning || ( !isRunning && onHorse ))
|
|
{
|
|
mChar->SetStep( 3 ); // Running step 4
|
|
}
|
|
switch( tileType )
|
|
{
|
|
case TT_NORMAL:
|
|
if( onHorse )
|
|
{
|
|
if( isRunning )
|
|
{
|
|
soundId = 0x0129;
|
|
}
|
|
else
|
|
{
|
|
soundId = 0x024B;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
soundId = 0x012C;
|
|
}
|
|
break;
|
|
case TT_WATER: // water
|
|
break;
|
|
case TT_STONE: // stone
|
|
soundId = 0x012F;
|
|
break;
|
|
case TT_WOODEN: // wooden
|
|
soundId = 0x0122;
|
|
break;
|
|
case TT_OTHER: // other
|
|
[[fallthrough]];
|
|
case TT_GRASS: // grass
|
|
soundId = 0x012E;
|
|
break;
|
|
}
|
|
break;
|
|
case 2:
|
|
mChar->SetStep( 1 ); // Running step 3
|
|
break;
|
|
case 3:
|
|
mChar->SetStep( 0 ); // Running step 0
|
|
break;
|
|
default:
|
|
mChar->SetStep( 1 ); // pause
|
|
break;
|
|
}
|
|
|
|
if( soundId ) // if we have a valid sound
|
|
{
|
|
if( mSock == nullptr && ValidateObject( mChar ))
|
|
{
|
|
// It's an NPC!
|
|
auto nearbyChars = FindNearbyPlayers( mChar );
|
|
std::for_each( nearbyChars.begin(), nearbyChars.end(), [soundId, this]( CSocket *entry )
|
|
{
|
|
PlaySound( entry, soundId, false );
|
|
});
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// It's a player!
|
|
PlaySound( mSock, soundId, true );
|
|
}
|
|
}
|
|
}
|
|
|