uox3/source/skills.cpp
2023-04-07 11:17:51 -04:00

3397 lines
111 KiB
C++

#include "uox3.h"
#include "magic.h"
#include "skills.h"
#include "combat.h"
#include "townregion.h"
#include "cRaces.h"
#include "cServerDefinitions.h"
#include "cMagic.h"
#include "ssection.h"
#include "CJSMapping.h"
#include "scriptc.h"
#include "cScript.h"
#include "cEffects.h"
#include "CPacketSend.h"
#include "classes.h"
#include "regions.h"
#include "Dictionary.h"
#include "movement.h"
#include "StringUtility.hpp"
#include "strutil.hpp"
#include "weight.h"
#include <algorithm>
using namespace std::string_literals;
bool CheckItemRange( CChar *mChar, CItem *i );
CSkills *Skills = nullptr;
const UI16 CREATE_MENU_OFFSET = 5000; // This is how we differentiate a menu button from an item button (and the limit on ITEM=# in create.dfn)
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::CalcRankAvg()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Calculate average rank of item based on player's skillpoints in skills
//| required to craft item
//o------------------------------------------------------------------------------------------------o
SI32 CSkills::CalcRankAvg( CChar *player, CreateEntry_st& skillMake )
{
// If rank system is not enabled, assume perfectly crafted item each time
if( !cwmWorldState->ServerData()->RankSystemStatus() )
return 10;
R32 rankSum = 0;
SI32 rk_range, rank;
R32 sk_range, randnum, randnum1;
// Calculate base chance of success at crafting based on primary skill, to use as a modifier for exceptional chance later
R32 chanceSkillSuccess = 0;
chanceSkillSuccess = static_cast<R32>( player->GetSkill( skillMake.skillReqs[0].skillNumber ) - skillMake.skillReqs[0].minSkill );
chanceSkillSuccess /= ( static_cast<R32>( skillMake.skillReqs[0].maxSkill ) - static_cast<R32>( skillMake.skillReqs[0].minSkill ));
// Loop through all skills required to craft the item and calculate average rank value for each
for( size_t i = 0; i < skillMake.skillReqs.size(); ++i )
{
// Fetch the range of rank (min/max) values the item being crafted can have
rk_range = skillMake.maxRank - skillMake.minRank;
// Fetch range of skill we have to work with for this skill based on player's skill value and minimum skill required to craft the item
sk_range = static_cast<R32>( player->GetSkill( skillMake.skillReqs[i].skillNumber ) - skillMake.skillReqs[i].minSkill );
if( sk_range <= 0 )
{
sk_range = skillMake.minRank * 10;
}
else if( sk_range >= 1000 )
{
sk_range = skillMake.maxRank * 10;
}
// If generated number is under skill range value...
randnum = static_cast<R32>( RandomNum( 0, 999 ));
if( randnum <= sk_range )
{
rank = skillMake.maxRank; // ...assume perfectly crafted item!
}
else
{
// ...otherwise, generate a random number modified by skill range and difficulty level of rank system from uox.ini
randnum1 = static_cast<R32>( RandomNum( 0, 999 )) - (( randnum - sk_range ) / ( 11 - cwmWorldState->ServerData()->SkillLevel() ));
// Modify random number based on base chance of success at crafting
randnum1 *= chanceSkillSuccess;
// Calculate rank value for this skill
rank = static_cast<SI32>(( randnum1 * rk_range ) / 1000 );
rank += skillMake.minRank - 1;
if( rank > skillMake.maxRank )
{
rank = skillMake.maxRank;
}
if( rank < skillMake.minRank )
{
rank = skillMake.minRank;
}
}
// Add rank value for this skill to the total
rankSum += rank;
}
// Return the average rank value of all skills required to craft the item
return static_cast<SI32>( rankSum / skillMake.skillReqs.size() );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::ApplyRank()
//| Date - 24 August 1999
//o------------------------------------------------------------------------------------------------o
//| Purpose - Modify item properties based on item's rank.
//o------------------------------------------------------------------------------------------------o
void CSkills::ApplyRank( CSocket *s, CItem *c, UI08 rank, UI08 maxrank )
{
char tmpmsg[512];
*tmpmsg='\0';
// Only apply rank system if enabled in ini, and only for non-pileable items!
if( cwmWorldState->ServerData()->RankSystemStatus() && !c->IsPileable() )
{
c->SetRank( rank );
if( c->GetLoDamage() > 0 )
{
c->SetLoDamage( static_cast<SI16>(( rank * c->GetLoDamage() ) / 10 ));
}
if( c->GetHiDamage() > 0 )
{
c->SetHiDamage( static_cast<SI16>(( rank * c->GetHiDamage() ) / 10 ));
}
if( c->GetResist( PHYSICAL ) > 0 )
{
c->SetResist( static_cast<UI16>(( rank * c->GetResist( PHYSICAL )) / 10 ), PHYSICAL );
}
if( c->GetHP() > 0 )
{
c->SetHP( static_cast<SI16>(( rank * c->GetHP() ) / 10 ));
}
if( c->GetMaxHP() > 0 )
{
c->SetMaxHP( static_cast<SI16>(( rank * c->GetMaxHP() ) / 10 ));
}
if( c->GetBuyValue() > 0 )
{
c->SetBuyValue( static_cast<UI32>(( rank * c->GetBuyValue() ) / 10 ));
}
if( c->GetMaxUses() > 0 )
{
c->SetUsesLeft( static_cast<UI16>(( rank * c->GetMaxUses() ) / 10 ));
}
if( c->GetId() == 0x22c5 && c->GetMaxHP() > 0 ) // Runebook
{
// Max charges for runebook stored in maxHP property, defaults to 10, ranges from 5-10 based on rank
c->SetMaxHP( static_cast<UI16>( std::max( static_cast<UI16>( 5 ), static_cast<UI16>(( rank * c->GetMaxHP() ) / 10 ))));
}
// Convert item's rank to a value between 1 and 10, to fit rank system messages
UI08 tempRank = floor( static_cast<R32>((( rank * 100 ) / maxrank ) / 10 ));
if( tempRank >= 1 && tempRank <= 10 )
{
s->SysMessage( 783 + tempRank ); // =You cannot use that material for carpentry.
}
else if( tempRank < 1 )
{
s->SysMessage( 784 ); // You made an item with no quality!
}
}
else
{
c->SetRank( rank );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::RegenerateOre()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Regenerate Ore in a resource region based on UOX.INI Ore respawn settings
//o------------------------------------------------------------------------------------------------o
void CSkills::RegenerateOre( SI16 grX, SI16 grY, UI08 worldNum )
{
MapResource_st *orePart = MapRegion->GetResource( grX, grY, worldNum );
SI16 oreCeiling = cwmWorldState->ServerData()->ResOre();
UI16 oreTimer = cwmWorldState->ServerData()->ResOreTime();
if( static_cast<UI32>( orePart->oreTime) <= cwmWorldState->GetUICurrentTime() ) // regenerate some more?
{
for( SI16 counter = 0; counter < oreCeiling; ++counter ) // keep regenerating ore
{
if( orePart->oreAmt < oreCeiling && static_cast<UI32>( orePart->oreAmt + counter * oreTimer * 1000 ) < cwmWorldState->GetUICurrentTime() )
{
++orePart->oreAmt;
}
else
{
break;
}
}
orePart->oreTime = BuildTimeValue( static_cast<R32>( oreTimer ));
}
if( orePart->oreAmt > oreCeiling )
{
orePart->oreAmt = oreCeiling;
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - MakeOre()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Spawn Ore in players pack when he successfully uses mining skill
//o------------------------------------------------------------------------------------------------o
void MakeOre( CSocket& mSock, CChar *mChar, CTownRegion *targRegion )
{
if( targRegion == nullptr || !ValidateObject( mChar ))
return;
const UI16 getSkill = mChar->GetSkill( MINING );
const SI32 findOreChance = RandomNum( static_cast<SI32>( 0 ), targRegion->GetOreChance() ); // find our base ore
SI32 sumChance = 0;
bool oreFound = false;
const OrePref_st *toFind = nullptr;
const MiningData_st *found = nullptr;
for( size_t currentOre = 0; currentOre < targRegion->GetNumOrePreferences(); ++currentOre )
{
toFind = targRegion->GetOrePreference( currentOre );
if( toFind == nullptr )
continue;
found = toFind->oreIndex;
if( found == nullptr )
continue;
sumChance += toFind->percentChance;
if( sumChance > findOreChance )
{
if( getSkill >= found->minSkill )
{
UI08 amtToMake = 1;
if( targRegion->GetChanceBigOre() >= RandomNum( 1, 100 ))
{
amtToMake = 5;
}
// Randomize the size of ore found
UI16 oreId = RandomNum( 0x19B7, 0x19BA );
CItem *oreItem = Items->CreateItem( &mSock, mChar, oreId, amtToMake, found->colour, OT_ITEM, true );
if( ValidateObject( oreItem ))
{
const std::string oreName = found->name + " ore";
oreItem->SetName( oreName );
oreItem->AddScriptTrigger( found->scriptID );
if( oreItem->GetCont() != nullptr )
{
mSock.SysMessage( 982, oreName.c_str() ); // You place some %s in your pack.
}
}
oreFound = true;
break;
}
}
}
if( !oreFound )
{
if( getSkill >= 850 )
{
Items->CreateRandomItem( &mSock, "digginggems" );
mSock.SysMessage( 983 ); // You place a gem in your pack.
}
else
{
mSock.SysMessage( 1772 ); // You do not have enough skill to mine that ore!
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - MineCheck()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Check where player is attempting to mine versus MINECHECK setting in UOX.INI
//o------------------------------------------------------------------------------------------------o
bool MineCheck( CSocket& mSock, CChar *mChar, SI16 targetX, SI16 targetY, SI08 targetZ, UI08 targetId1, UI08 targetId2 )
{
switch( cwmWorldState->ServerData()->MineCheck() )
{
case 0:
return true;
case 1:
if( targetZ == 0 ) // check to see if we're targetting a dungeon floor
{
if( targetId1 == 0x05 )
{
if(( targetId2 >= 0x3B && targetId2 <= 0x4F ) || ( targetId2 >= 0x51 && targetId2 <= 0x53 ) || ( targetId2 == 0x6A ))
{
return true;
}
}
}
if( targetZ >= 0 ) // mountain not likely to be below 0 (but you never know, do you? =)
{
if( targetId1 != 0 && targetId2 != 0 ) // we might have a static rock or mountain
{
auto artwork = Map->ArtAt( targetX, targetY, mChar->WorldNumber() );
for( auto &tile : artwork )
{
auto name = tile.name();
if( targetZ == tile.altitude && (( name.find( "rock" ) != std::string::npos )
|| ( name.find( "mountain" ) != std::string::npos ) || ( name.find( "cave" ) != std::string::npos )))
{
return true;
}
}
}
else // or it could be a map only
{
// manually calculating the ID's if a maptype
auto map1 = Map->SeekMap( targetX, targetY, mChar->WorldNumber() );
if(( map1.name().find( "rock" ) != std::string::npos )
|| ( map1.name().find( "mountain" ) != std::string::npos ) || ( map1.name().find( "cave" ) != std::string::npos ))
{
return true;
}
}
}
break;
case 2: // need to modify scripts to support this!
return true;
default:
mSock.SysMessage( 800 ); // Unknown mine check option, contact your shard op!
return true;
}
return false;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::Mine()
//| Modified - (February 19, 2000)
//o------------------------------------------------------------------------------------------------o
//| Purpose - Handles player's attempts to use mining skill
//| Notes - Skill checking now implemented. You cannot mine colored ore unless you have the
//| proper mining skill for each ore type. -Cork
//| Rewrote most of it to clear up some of the mess
//o------------------------------------------------------------------------------------------------o
void CSkills::Mine( CSocket *s )
{
VALIDATESOCKET( s );
CSocket& mSock = ( *s );
CChar *mChar = s->CurrcharObj();
// Check if item used to initialize target cursor is still within range
CItem *miningTool = static_cast<CItem *>( s->TempObj() );
s->TempObj( nullptr );
if( ValidateObject( miningTool ))
{
if( !CheckItemRange( mChar, miningTool ))
{
s->SysMessage( 400 ); // That is too far away!
return;
}
}
// Don't allow mining without a valid tool
if( !ValidateObject( miningTool ))
return;
if( mSock.GetDWord( 11 ) == INVALIDSERIAL )
return;
if( !ValidateObject( mChar ))
return;
// Check if player targeted an item, and if so, if he's in the same world/instance as said item
CItem *targetItem = CalcItemObjFromSer( mSock.GetDWord( 7 ));
if( ValidateObject( targetItem ))
{
if( mChar->WorldNumber() != targetItem->WorldNumber() )
return;
if( mChar->GetInstanceId() != targetItem->GetInstanceId() )
return;
}
const SI16 targetX = mSock.GetWord( 11 );
const SI16 targetY = mSock.GetWord( 13 );
const SI08 targetZ = mSock.GetByte( 16 );
const SI16 distX = abs( mChar->GetX() - targetX );
const SI16 distY = abs( mChar->GetY() - targetY );
if( distX > 5 || distY > 5 )
{
mSock.SysMessage( 799 ); // You are too far away to mine that!
return;
}
const UI08 targetId1 = mSock.GetByte( 17 );
const UI08 targetId2 = mSock.GetByte( 18 );
if( targetZ < 28 && targetId1 == 0x0E )
{
switch( targetId2 )
{
case 0xD3:
case 0xDF:
case 0xE0:
case 0xE1:
case 0xE8:
GraveDig( s ); // check to see if we targeted a grave, if so, check it
break;
default:
break;
}
}
if( !MineCheck( mSock, mChar, targetX, targetY, targetZ, targetId1, targetId2 ))
{
mSock.SysMessage( 801 ); // You cannot mine there.
return;
}
RegenerateOre( targetX, targetY, mChar->WorldNumber() );
MapResource_st *orePart = MapRegion->GetResource( targetX, targetY, mChar->WorldNumber() );
if( orePart->oreAmt <= 0 )
{
mSock.SysMessage( 802 ); // There is no metal to mine here.
return;
}
// do action and sound
if( mChar->GetBodyType() == BT_GARGOYLE
|| ( cwmWorldState->ServerData()->ForceNewAnimationPacket() && ( mChar->GetSocket() == nullptr || mChar->GetSocket()->ClientType() >= CV_SA2D )))
{
Effects->PlayNewCharacterAnimation( mChar, N_ACT_ATT, S_ACT_1H_BASH ); // action 0x00, subAction 0x03
}
else if( mChar->IsOnHorse() ) // Human/Elf, mounted, pre-v7.0.0.0
{
Effects->PlayCharacterAnimation( mChar, ACT_MOUNT_ATT_1H, 0, 5 ); // 0x1A
}
else // Human/Elf, on foot, pre-v7.0.0.0
{
Effects->PlayCharacterAnimation( mChar, ACT_ATT_1H_BASH, 0, 7 ); // 0x0B
}
Effects->PlaySound( &mSock, 0x0125, true );
// Consume uses on mining tool, if tool use limits are enabled in ini
if( cwmWorldState->ServerData()->ToolUseLimit() )
{
auto usesLeft = miningTool->GetUsesLeft();
if( usesLeft > 0 )
{
usesLeft -= 1;
miningTool->SetUsesLeft( usesLeft );
if( usesLeft == 0 )
{
mSock.SysMessage( 10202 ); // You have worn out your tool!
// If no uses left, break tool if tool breakage is enabled in ini
if( cwmWorldState->ServerData()->ToolUseBreak() )
{
miningTool->Delete();
// SFX of tool breaking
}
}
}
}
if( CheckSkill( mChar, MINING, 0, 1000 )) // check to see if our skill is good enough
{
if( orePart->oreAmt > 0 )
{
--orePart->oreAmt;
}
#if defined( UOX_DEBUG_MODE )
Console << "DBG: Mine(\"" << mChar->GetName() << "\"[" << mChar->GetSerial()
<< "]); --> MINING: " << mChar->GetSkill( MINING ) << " RaceID: " << mChar->GetRace() << myendl;
#endif
CTownRegion *targetReg = CalcRegionFromXY( targetX, targetY, mChar->WorldNumber(), mChar->GetInstanceId() );
if( targetReg == nullptr )
return;
MakeOre( mSock, mChar, targetReg );
}
else
{
mSock.SysMessage( 803 ); // You sifted through the dirt and rocks, but found nothing usable.
if( orePart->oreAmt > 0 && RandomNum( 0, 1 ))
{
--orePart->oreAmt;
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::GraveDig()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Do GraveDigging Stuff (Should probably be replaced with an OSI-like Treasure
//| Hunting System based off a Resource System much like Ore and Logs)
//o------------------------------------------------------------------------------------------------o
void CSkills::GraveDig( CSocket *s )
{
VALIDATESOCKET( s );
SI16 nFame;
CItem * nItemId = nullptr;
CChar *nCharId = s->CurrcharObj();
Karma( nCharId, nullptr, -2000 ); // Karma loss no lower than the -2 pier
// do action and sound
if( nCharId->GetBodyType() == BT_GARGOYLE
|| ( cwmWorldState->ServerData()->ForceNewAnimationPacket() && ( nCharId->GetSocket() == nullptr || nCharId->GetSocket()->ClientType() >= CV_SA2D )))
{
Effects->PlayNewCharacterAnimation( nCharId, N_ACT_ATT, S_ACT_1H_BASH ); // Action 0x00, subAction 0x03
}
else if( nCharId->IsOnHorse() ) // Human/Elf, mounted
{
Effects->PlayCharacterAnimation( nCharId, ACT_MOUNT_ATT_1H, 0, 5 ); // Action 0x1A
}
else // Human/Elf, on foot
{
Effects->PlayCharacterAnimation( nCharId, ACT_ATT_1H_BASH, 0, 7 ); // Action 0x0B
}
Effects->PlaySound( s, 0x0125, true );
if( !CheckSkill( nCharId, MINING, 0, 800 ))
{
s->SysMessage( 805 ); // You sifted through the dirt and found nothing.
return;
}
nFame = nCharId->GetFame();
// do action and sound (again?)
if( nCharId->GetBodyType() == BT_GARGOYLE
|| ( cwmWorldState->ServerData()->ForceNewAnimationPacket() && ( nCharId->GetSocket() == nullptr || nCharId->GetSocket()->ClientType() >= CV_SA2D )))
{
Effects->PlayNewCharacterAnimation( nCharId, N_ACT_ATT, S_ACT_1H_BASH ); // Action 0x00, subAction 0x03
}
else if( nCharId->IsOnHorse() ) // Human/Elf, mounted
{
Effects->PlayCharacterAnimation( nCharId, ACT_MOUNT_ATT_1H, 0, 5 ); // Action 0x1A
}
else // Human/Elf, on foot
{
Effects->PlayCharacterAnimation( nCharId, ACT_ATT_1H_BASH, 0, 7 ); // Action 0x0B
}
Effects->PlaySound( s, 0x0125, true );
CChar *spawnCreature = nullptr;
switch( RandomNum( 0, 12 ))
{
case 2:
spawnCreature = Npcs->CreateRandomNPC( "weakundeadlist" ); // Low level Undead - Random
s->SysMessage( 806 ); // You have disturbed the rest of a vile undead creature.
break;
case 4:
nItemId = Items->CreateRandomItem( s, "diggingarmor" ); // Armor and shields - Random
if( nItemId == nullptr )
break;
if( nItemId->GetId() >= 7026 && nItemId->GetId() <= 7035 )
{
s->SysMessage( 807 ); // You unearthed an old shield and placed it in your pack.
}
else
{
s->SysMessage( 808 ); // You have found an old piece armour and placed it in your pack.
}
break;
case 5:
//Random treasure between gems and gold
if( RandomNum( 0, 1 ))
{ // randomly create a gem and place in backpack
Items->CreateRandomItem( s, "digginggems" );
s->SysMessage( 809 ); // You place a gem in your pack.
}
else
{ // Create between 1 and 15 goldpieces and place directly in backpack
UI08 nAmount = RandomNum( 1, 15 );
Items->CreateScriptItem( s, nCharId, "0x0EED", nAmount, OT_ITEM, true );
Effects->GoldSound( s, nAmount );
s->SysMessage( 810, nAmount ); // You unearthed %i gold coins.
}
break;
case 6:
spawnCreature = Npcs->CreateRandomNPC( "weakundeadlist" ); // Low level Undead - Random
s->SysMessage( 806 ); // You have disturbed the rest of a vile undead creature.
break;
case 8:
Items->CreateRandomItem( s, "diggingweapons" );
s->SysMessage( 811 ); // You unearthed a old weapon and placed it in your pack.
break;
case 10:
case 12:
if( nFame < 1000 )
{
spawnCreature = Npcs->CreateRandomNPC( "weakundeadlist" ); // Med level Undead - Random
}
else
{
spawnCreature = Npcs->CreateRandomNPC( "strongundeadlist" ); // High level Undead - Random
}
s->SysMessage( 806 ); // You have disturbed the rest of a vile undead creature.
break;
default:
if( RandomNum( 0, 1 ))
{
Items->CreateRandomItem( s, "diggingbones" ); // Random Bones
s->SysMessage( 812 ); // You have unearthed some old bones and placed them in your pack.
}
else // found an empty grave
{
s->SysMessage( 813 ); // This grave seems to be empty.
}
break;
}
if( spawnCreature != nullptr )
{
spawnCreature->SetLocation( nCharId );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::SmeltOre();
//| Modified - (February 19, 2000)
//o------------------------------------------------------------------------------------------------o
//| Purpose - Rewritten to use case and structure, you'll find it is easier to make it
//| scriptable now. The structure is pretty much all that'd be needed for any
//| future ore->ingot conversions. Scripting the ore would probably be even
//| simpler, requires less info
//o------------------------------------------------------------------------------------------------o
void CSkills::SmeltOre( CSocket *s )
{
VALIDATESOCKET( s );
CChar *chr = s->CurrcharObj();
CItem *itemToSmelt = static_cast<CItem *>( s->TempObj() );
s->TempObj( nullptr );
CItem *forge = CalcItemObjFromSer( s->GetDWord( 7 ));
if( itemToSmelt->IsHeldOnCursor() )
{
s->SysMessage( 400 ); // That is too far away!
return;
}
if( ValidateObject( forge )) // if we have a forge
{
CItem *playerPack = chr->GetPackItem();
if( ValidateObject( playerPack ))
{
if( FindRootContainer( itemToSmelt ) != playerPack && FindRootContainer( forge ) != playerPack )
{
if( !ObjInRange( chr, forge, DIST_NEARBY ) || !CheckItemRange( chr, itemToSmelt )) // Check if forge & item to melt are in range
{
s->SysMessage( 400 ); // That is too far away!
return;
}
}
}
UI16 smeltItemId = itemToSmelt->GetId();
UI16 forgeId = forge->GetId();
// Is player trying to combine ore?
//if( forgeId == 0x19B7 || forgeId == 0x19B8 || forgeId == 0x19BA )
if( forgeId >= 0x19B7 && forgeId <= 0x19BA )
{
if( forgeId == smeltItemId )
{
s->SysMessage( 1806 ); // You cannot combine these ore types
return;
}
if( forge->GetColour() != itemToSmelt->GetColour() )
{
s->SysMessage( 1807 ); // You cannot combine ores of different metals.
return;
}
if( forge->GetMovable() == 2 || forge->GetMovable() == 3 )
{
s->SysMessage( 1808 ); // You cannot do that.
return;
}
// Player targeted some small or medium ore! Combine ores, if possible
SI32 sourceAmount = itemToSmelt->GetAmount();
SI32 targetAmount = forge->GetAmount();
UI08 amountMultiplier = 1;
if( smeltItemId == 0x19B9 && forgeId == 0x19B7 ) // Large ore, targeted small ore
{
amountMultiplier = 4;
}
else if( smeltItemId == 0x19B9 && ( forgeId == 0x19B8 || forgeId == 0x19BA )) // Large ore, targeted medium ore
{
amountMultiplier = 2;
}
else if(( smeltItemId == 0x19B8 || smeltItemId == 0x19BA ) && forgeId == 0x19B7 ) // Medium ore, targeted small ore
{
amountMultiplier = 2;
}
else
{
// Small ore, targeted at larger ore
s->SysMessage( 1809 ); // You can only combine larger ores with smaller ores!
return;
}
// Combine source stack into target stack, if it fits
if( targetAmount + ( sourceAmount * amountMultiplier ) <= MAX_STACK )
{
CChar *forgeOwner = FindItemOwner( forge );
SI32 newTargetWeight = ( targetAmount + ( sourceAmount * amountMultiplier )) * forge->GetBaseWeight();
SI32 subtractWeight = ( targetAmount * forge->GetBaseWeight() ) + ( sourceAmount * itemToSmelt->GetBaseWeight() );
if( ValidateObject( forgeOwner ) && forgeOwner == chr )
{
if(( newTargetWeight + chr->GetWeight() - subtractWeight ) > 200000 ) // 2000 stones
{
s->SysMessage( 1743 ); // That person can not possibly hold more weight
return;
}
}
else if( forge->GetCont() != nullptr )
{
if(( newTargetWeight + forge->GetCont()->GetWeight() - subtractWeight ) > 200000 ) // 2000 stones
{
s->SysMessage( 1810 ); // The weight is too great to combine in a container.
return;
}
}
// Good to go, combine ores and remove source stack!
forge->SetAmount( targetAmount + ( sourceAmount * amountMultiplier ));
// Remove from multi before deleting
if( itemToSmelt->IsLockedDown() && ValidateObject( itemToSmelt->GetMultiObj() ))
{
itemToSmelt->GetMultiObj()->ReleaseItem( itemToSmelt );
}
itemToSmelt->Delete();
s->SysMessage( 1811 ); // You combine the ore with the stack of smaller ore.
}
else if( targetAmount < MAX_STACK )
{
// New targetStack would exceed MAX_STACK, but there might still be some room left! Cap amount at MAX_STACK
UI16 amountLeft = MAX_STACK - targetAmount;
if( amountLeft < amountMultiplier )
{
// Not enough space left in stack to combine any more ore
s->SysMessage( 1812 ); // That stack cannot hold any more items
return;
}
else
{
// There's still some room
UI16 amountToRemove = static_cast<UI16>( floor( amountLeft / amountMultiplier ));
itemToSmelt->SetAmount( sourceAmount - amountToRemove );
forge->SetAmount( MAX_STACK );
s->SysMessage( 1813 ); // You combine a portion of your ore into the stack of smaller ore.
}
}
else
{
// targetStack is already at max cap and cannot hold any more ore
s->SysMessage( 1812 ); // That stack cannot hold any more items
return;
}
return;
}
switch( forgeId ) // Check to ensure it is a forge
{
case 0x0FB1:
case 0x197A:
case 0x19A9:
case 0x197E:
case 0x1982:
case 0x1986:
case 0x198A:
case 0x198E:
case 0x1992:
case 0x1996:
case 0x199A:
case 0x19A6:
case 0x19A2:
case 0x199E:
if( ObjInRange( chr, forge, DIST_NEARBY )) //Check if the forge is in range
{
UI16 targColour = itemToSmelt->GetColour();
MiningData_st *oreType = FindOre( targColour );
if( oreType == nullptr )
{
s->SysMessage( 814 ); // That material is foreign to you.
return;
}
if( chr->GetSkill( MINING ) < oreType->minSkill )
{
s->SysMessage( 815 ); // You have no idea what to do with this strange ore.
return;
}
if( !CheckSkill( chr, MINING, oreType->minSkill, 1000 )) // if we do not have minimum skill to use it
{
if( itemToSmelt->GetAmount() > 1 ) // If more than 1 ore, half it
{
s->SysMessage( 817 ); // Your hand slips and some of your materials are destroyed.
itemToSmelt->SetAmount( itemToSmelt->GetAmount() / 2 );
}
else
{
s->SysMessage( 816 ); // Your hand slips and the last of your materials are destroyed.
// Remove from multi before deleting
if( itemToSmelt->IsLockedDown() && ValidateObject( itemToSmelt->GetMultiObj() ))
{
itemToSmelt->GetMultiObj()->ReleaseItem( itemToSmelt );
}
itemToSmelt->Delete();
}
return;
}
UI16 ingotNum = 1;
switch( smeltItemId )
{
case 0x19B7: // Small ore
ingotNum = itemToSmelt->GetAmount() / 2; // 0.5 ingot per ore
break;
case 0x19B8: // Medium ore
case 0x19BA: // Medium ore
ingotNum = itemToSmelt->GetAmount(); // 1.0 ingot per ore
break;
case 0x19B9: // Large ore
ingotNum = itemToSmelt->GetAmount() * 2; // 2.0 ingot per ore
break;
}
CItem *ingot = Items->CreateScriptItem( s, chr, "0x1BF2", ingotNum, OT_ITEM, true, oreType->colour );
if( ingot != nullptr )
{
ingot->SetName( util::format( "%s Ingot", oreType->name.c_str() ));
}
if( smeltItemId == 0x19B7 && itemToSmelt->GetAmount() % 2 != 0 )
{
itemToSmelt->SetAmount( 1 ); // There's still some ore left, as the pile of small ore is not divisible by two
}
else
{
// Remove from multi before deleting
if( itemToSmelt->IsLockedDown() && ValidateObject( itemToSmelt->GetMultiObj() ))
{
itemToSmelt->GetMultiObj()->ReleaseItem( itemToSmelt );
}
itemToSmelt->Delete(); // delete the ore
}
s->SysMessage( 818 ); // You have smelted your ore.
s->SysMessage( 819, oreType->name.c_str() ); // You place some %s ingots in your pack.
}
break;
default:
s->SysMessage( 820 ); // You can't smelt here.
break;
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::CalculatePetControlChance()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Calculate a player's chance (returned as value between 0 to 1000) of controlling
//| (having commands accepted) a follower
//o------------------------------------------------------------------------------------------------o
UI16 CSkills::CalculatePetControlChance( CChar *mChar, CChar *Npc )
{
SI16 petOrneriness = static_cast<SI16>( Npc->GetOrneriness() );
// If difficulty to control is below a certain treshold (29.1) or it's a summoned creature, 100% chance to control creature
if(( petOrneriness == 0 && Npc->GetTaming() <= 291 ) || petOrneriness <= 291 || Npc->IsDispellable() )
return static_cast<SI16>( 1000 );
// TODO: Check if NPC can be tamed via necromancer Dark Wolf Familiar
//if( petOrneriness > 249 && CheckFamiliarMastery( mChar, Npc ))
//toTame = 249;
// Fetch player's skill values (with modifiers)
SI16 tamingSkill = static_cast<SI16>( mChar->GetSkill( TAMING ));
SI16 loreSkill = static_cast<SI16>( mChar->GetSkill( ANIMALLORE ));
SI16 bonusChance = 0;
SI16 totalChance = 700; // base chance of success
SI16 tamingSkillMod = 6;
SI16 animalLoreMod = 6;
// Base bonuses based on skill vs orneriness
SI16 tamingSkillBonus = static_cast<SI16>( tamingSkill - petOrneriness );
SI16 animalLoreBonus = static_cast<SI16>( loreSkill - petOrneriness );
// Calculated bonus modifiers
if( tamingSkillBonus < 0 )
{
tamingSkillMod = 28;
}
if( animalLoreBonus < 0 )
{
animalLoreMod= 14;
}
tamingSkillBonus *= tamingSkillMod;
animalLoreBonus *= animalLoreMod;
// Bonus chance equals average of bonuses for tamingskill and animallore
bonusChance = static_cast<SI16>(( tamingSkillBonus + animalLoreBonus ) / 2 );
totalChance += bonusChance;
// Modify chance based on NPCs loyalty (or lack thereof)
totalChance -= ( Npc->GetMaxLoyalty() - Npc->GetLoyalty() ) * 10;
// Finally, apply a 15% penalty to chance for friends of the pet trying to control
if( Npcs->CheckPetFriend( mChar, Npc ))
{
totalChance -= ( totalChance * 0.15 );
}
// Clamp lower chance to 20% and upper chance to 99%
totalChance = std::clamp( totalChance, static_cast<SI16>( 200 ), static_cast<SI16>( 990 ));
return static_cast<UI16>( totalChance );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::CheckSkill()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Used to check a players skill based on the highskill and lowskill it was called
//| with. If skill is < than lowskill check will fail, but player will gain in the
//| skill, if the players skill is > than highskill player will not gain
//o------------------------------------------------------------------------------------------------o
bool CSkills::CheckSkill( CChar *s, UI08 sk, SI16 lowSkill, SI16 highSkill, bool isCraftSkill )
{
bool skillCheck = false;
bool exists = false;
std::vector<UI16> scriptTriggers = s->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
// If script returns true/1, allows skillcheck to proceed, but also prevents other scripts with event from running
if( toExecute->OnSkillCheck( s, sk, lowSkill, highSkill, isCraftSkill ) == 1 )
{
exists = true;
break;
}
}
}
// o----------------------------------------------------------------------------o
// | 15 March, 2002 |
// | Comment :Now lets make this more readable, and even more mathematical:) |
// o----------------------------------------------------------------------------o
// | 1. if a player has skill > highskill, checkskill should return true |
// | 2. if a player has skill < lowskill, checkskill returns false |
// | 3. we have statmodifiers defined in skills.dfn, now lets make it influence |
// | checkskill with a certain additional bonus through the players stat. |
// | 4. we dont need to throw the dices for skillgain... we must do this in |
// | AdvanceSkill(...), so just call it after calculation |
// o----------------------------------------------------------------------------o
// | how to calculate if a skill succeeds? |
// | 1. we have a certain range between lowskill and highskill, on lowskill the |
// | chance is 0%, on highskill 100%. |
// | 2. get the statmodifiers and the players stat. make 3 special dices for |
// | each stat. make them a small bonus for skillsuccess |
// | 3. execute the 4 calculated dices one after the other by adding them |
// o----------------------------------------------------------------------------o
if( !exists )
{
CSocket *mSock = s->GetSocket();
R32 chanceSkillSuccess = 0;
if(( highSkill - lowSkill ) <= 0 || !ValidateObject( s ) || s->GetSkill( sk ) < lowSkill )
return false;
if( s->IsDead() && mSock != nullptr )
{
mSock->SysMessage( 1487 ); // You are dead and cannot gain skill.
return false;
}
if( s->GetSkill( sk ) >= highSkill )
return true;
// Calculate base chance of success at using a skill
chanceSkillSuccess = ( static_cast<R32>( s->GetSkill( sk )) - static_cast<R32>( lowSkill ));
// Modify chance based on the range of highSkill vs lowSkill
chanceSkillSuccess /= ( static_cast<R32>( highSkill ) - static_cast<R32>( lowSkill ));
// Let's work with whole numbers again
chanceSkillSuccess *= 1000;
if( isCraftSkill )
{
// Give players at least 50% chance at success if this is a crafting skill and their skill is above minimum requirement
chanceSkillSuccess = std::max( static_cast<R32>( 500 ), chanceSkillSuccess );
}
// Cap chance of success at 1000 (100.0%)
chanceSkillSuccess = std::min( static_cast<R32>( 1000 ), chanceSkillSuccess );
if( cwmWorldState->ServerData()->StatsAffectSkillChecks() )
{
// Modify base chance of success with bonuses from stats, if this feature is enabled in ini
chanceSkillSuccess += static_cast<R32>( s->GetStrength() * cwmWorldState->skill[sk].strength ) / 1000.0f;
chanceSkillSuccess += static_cast<R32>( s->GetDexterity() * cwmWorldState->skill[sk].dexterity ) / 1000.0f;
chanceSkillSuccess += static_cast<R32>( s->GetIntelligence() * cwmWorldState->skill[sk].intelligence ) / 1000.0f;
}
// If player's command-level is equal to Counselor or higher, pass the skill-check automatically
// Same if chance of success is 100% already - no need to proceed!
if( s->GetCommandLevel() > 0 || chanceSkillSuccess == 1000 )
{
if( s->GetCommandLevel() > 0 && mSock != nullptr )
{
// Inform the counselor/gm to make it obvious why skillcheck always succeeds
mSock->SysMessage( 6279 ); // Skill check success guaranteed due to elevated command level!
}
skillCheck = true;
}
else
{
// Generate a random number between 0 and highSkill (if less than 1000) or 1000 (if higher than 1000)
SI16 rnd = RandomNum( 0, std::min( 1000, ( highSkill + 100 )));
// Compare to chanceOfSkillSuccess to see if player succeeds!
skillCheck = ( static_cast<SI16>( chanceSkillSuccess ) >= rnd );
}
if( mSock != nullptr )
{
bool mageryUp = true;
mageryUp = ( mSock->CurrentSpellType() == 0 );
if( s->GetBaseSkill( sk ) < highSkill )
{
if( sk != MAGERY || mageryUp )
{
// Advance player's skill based on result of skillCheck
if( AdvanceSkill( s, sk, skillCheck ))
{
UpdateSkillLevel( s, sk );
mSock->UpdateSkill( sk );
}
}
}
}
else if(( s->IsTamed() && cwmWorldState->ServerData()->PetCombatTraining() ) ||
( !s->IsTamed() && s->CanBeHired() && cwmWorldState->ServerData()->HirelingCombatTraining() ) ||
( !s->IsTamed() && !s->CanBeHired() && s->IsNpc() && cwmWorldState->ServerData()->NpcCombatTraining() ))
{
if( s->GetBaseSkill( sk ) < highSkill )
{
// Advance pet/NPC's skill based on result of skillCheck
if( AdvanceSkill( s, sk, skillCheck ))
{
UpdateSkillLevel( s, sk );
}
}
}
}
else
{
skillCheck = true;
}
return skillCheck;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::HandleSkillChange()
//| Date - Jan 29, 2000
//o------------------------------------------------------------------------------------------------o
//| Purpose - Do atrophy for player c:
//| find sk in our cronological list of atrophy skills, move it to the front, check
//| total aginst skillCap to see if we need to lower a skill, if we do, again search
//| skills for a skill that can be lowered, if one is found lower it and increase
//| sk, if we can't find one, do nothing if atrophy is not need, increase sk.
//o------------------------------------------------------------------------------------------------o
void CSkills::HandleSkillChange( CChar *c, UI08 sk, SI08 skillAdvance, bool success )
{
UI32 totalSkill = 0;
UI08 rem = 0;
UI08 atrop[ALLSKILLS+1];
UI08 toDec = 0xFF;
UI08 counter = 0;
CSocket *mSock = c->GetSocket();
std::vector<UI16> scriptTriggers = c->GetScriptTriggers();
UI08 amtToGain = 1;
if( success )
{
amtToGain = cwmWorldState->skill[sk].advancement[skillAdvance].amtToGain;
}
UI16 skillCap = cwmWorldState->ServerData()->ServerSkillTotalCapStatus();
if( c->IsNpc() )
{
// Check for existence of onSkillGain event for NPC
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
// If retVal is -1, event doesn't exist in script
// If retVal is 0, event exists, but returned false/0, and handles item usage. Don't proceed with hard code (or other scripts!)
// If retVal is 1, event exists, proceed with hard code/other scripts
if( !toExecute->OnSkillGain( c, sk, amtToGain ))
{
return;
}
}
}
// Increase base skill of NPC
c->SetBaseSkill( c->GetBaseSkill( sk ) + amtToGain, sk );
// Check for existence of onSkillChange event for NPC
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
toExecute->OnSkillChange( c, sk, amtToGain );
}
}
return;
}
if( mSock == nullptr )
return;
//srand( GetClock() ); // Randomize
atrop[ALLSKILLS] = 0; // set the last of out copy array
for( counter = 0; counter < ALLSKILLS; ++counter )
{
atrop[counter] = c->GetAtrophy( counter );
}
for( counter = ALLSKILLS; counter > 0; --counter )
{
// add up skills and find the one being increased
UI08 atrSkill = c->GetAtrophy( static_cast<UI08>( counter - 1 ));
if( c->GetBaseSkill( atrSkill ) >= amtToGain && c->GetSkillLock( atrSkill ) == SKILL_DECREASE && atrSkill != sk )
{
toDec = atrSkill; // we found a skill that can be decreased, save it for later.
}
totalSkill += c->GetBaseSkill( static_cast<UI08>( counter - 1 ));
atrop[counter] = atrop[counter-1];
if( atrop[counter] == sk )
{
rem = counter; // remember this number
}
}
atrop[0] = sk; // set the first one to our current skill
//copy it back in
for( counter = 0; counter < rem; ++counter )
{
c->SetAtrophy( atrop[counter], counter );
}
if( rem != ALLSKILLS ) // in the middle somewhere or first
{
for( counter = static_cast<UI08>( rem + 1 ); counter <= ALLSKILLS; ++counter )
{
c->SetAtrophy( atrop[counter], ( counter -1 ));
}
}
if( RandomNum( static_cast<UI16>( 0 ), skillCap ) <= static_cast<UI16>( totalSkill ))
{
if( toDec != 0xFF )
{
// Check for existence of onSkillLoss event for player
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
// If retVal is -1, event doesn't exist in script
// If retVal is 0, event exists, but returned false/0, and handles item usage. Don't proceed with hard code (or other scripts!)
// If retVal is 1, event exists, proceed with hard code/other scripts
if( !toExecute->OnSkillLoss( c, toDec, amtToGain ))
{
return;
}
}
}
// Reduce base skill of player
totalSkill -= amtToGain;
c->SetBaseSkill( c->GetBaseSkill( toDec ) - amtToGain, toDec );
// Check for existence of onSkillChange event for player
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
toExecute->OnSkillChange( c, toDec, ( amtToGain * -1 ));
}
}
mSock->UpdateSkill( toDec );
}
}
if( skillCap > static_cast<UI16>( totalSkill ))
{
// Check for existence of onSkillGain event for player
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
// If retVal is -1, event doesn't exist in script
// If retVal is 0, event exists, but returned false/0, and handles item usage. Don't proceed with hard code (or other scripts!)
// If retVal is 1, event exists, proceed with hard code/other scripts
if( !toExecute->OnSkillGain( c, sk, amtToGain ))
{
return;
}
}
}
// Increase base skill of player
c->SetBaseSkill( c->GetBaseSkill( sk ) + amtToGain, sk );
// Check for existence of onSkillChange event for player
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
toExecute->OnSkillChange( c, sk, amtToGain );
}
}
mSock->UpdateSkill( sk );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::SkillUse()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Called when player uses a skill from the skill list
//o------------------------------------------------------------------------------------------------o
//void ClilocMessage( CSocket *mSock, SpeechType speechType, UI16 hue, UI16 font, UI32 messageNum, const char *types = "", ... );
void CSkills::SkillUse( CSocket *s, UI08 x )
{
VALIDATESOCKET( s );
CChar *mChar = s->CurrcharObj();
if( mChar->IsDead() )
{
//ClilocMessage( s, SYSTEM, cwmWorldState->ServerData()->SysMsgColour(), FNT_NORMAL, 500012 );
s->SysMessage( 9054 ); // You cannot use skills while dead.
return;
}
if(( x != STEALTH && mChar->GetVisible() == VT_TEMPHIDDEN ) || mChar->GetVisible() == VT_INVISIBLE )
{
mChar->ExposeToView();
}
mChar->BreakConcentration( s );
if( mChar->GetTimer( tCHAR_SPELLTIME ) != 0 || mChar->IsCasting() )
{
s->SysMessage( 854 ); // You can't do that while you are casting!
return;
}
if( s->GetTimer( tPC_SKILLDELAY ) <= cwmWorldState->GetUICurrentTime() || mChar->IsGM() )
{
s->SkillDelayMsgShown( false );
bool doSwitch = true;
std::vector<UI16> scriptTriggers = mChar->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
// Loop through attached scripts
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
if( toExecute->OnSkill( mChar, x ))
{
// Look for onSkill JS event in script
doSwitch = false;
}
}
}
// If no onSkill event was found in a script already, check if skill has a script
// attached directly, with onSkill event
if( doSwitch && cwmWorldState->skill[x].jsScript != 0xFFFF )
{
cScript *toExecute = JSMapping->GetScript( cwmWorldState->skill[x].jsScript );
if( toExecute != nullptr )
{
if( toExecute->OnSkill( mChar, x ))
{
doSwitch = false;
}
}
}
// If no onSkill event has run yet, run the hardcoded version
if( doSwitch )
{
switch( x )
{
case STEALING:
// Check if stealing is allowed in player's current region
if( mChar->GetRegion()->IsSafeZone() )
{
// Player is in a safe zone where all aggressive actions are forbidden, disallow
s->SysMessage( 1799 ); // Hostile actions are not permitted in this safe area.
}
else if( cwmWorldState->ServerData()->RogueStatus() )
{
s->SendTargetCursor( 0, TARGET_STEALING, 1, 863 ); // What do you wish to steal?
}
else
{
s->SysMessage( 864 ); // Contact your shard operator if you want stealing available.
}
break;
case TRACKING: TrackingMenu( s, TRACKINGMENUOFFSET ); break;
default: s->SysMessage( 871 ); break; // That skill has not been implemented yet.
}
}
// If skillDelay timer is still below currenttimer, it wasn't modified in onSkill event. So let's update it now!
if( s->GetTimer( tPC_SKILLDELAY ) <= cwmWorldState->GetUICurrentTime() )
{
if( cwmWorldState->skill[x].skillDelay != -1 )
{
// Use skill-specific skill delay if one has been set
s->SetTimer( tPC_SKILLDELAY, BuildTimeValue( static_cast<R32>( cwmWorldState->skill[x].skillDelay )));
}
else
{
// Otherwise use global skill delay from uox.ini
s->SetTimer( tPC_SKILLDELAY, BuildTimeValue( static_cast<R32>( cwmWorldState->ServerData()->ServerSkillDelayStatus() )));
}
}
return;
}
else
{
if( !s->SkillDelayMsgShown() )
{
s->SysMessage( 872 ); // You must wait a few moments before using another skill.
s->SkillDelayMsgShown( true );
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::RandomSteal()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Called when player targets a PC/NPC with stealing skill
//| instead of an item (randomly pics an item in their pack)
//o------------------------------------------------------------------------------------------------o
void CSkills::RandomSteal( CSocket *s )
{
VALIDATESOCKET( s );
CChar *mChar = s->CurrcharObj();
CChar *npc = CalcCharObjFromSer( s->GetDWord( 7 ));
if( !ValidateObject( npc ))
return;
// Check if stealing is allowed in target char's region
if( npc->GetRegion()->IsSafeZone() )
{
// Target is in a safe zone where all aggressive actions are forbidden, disallow
s->SysMessage( 1799 ); // Hostile actions are not permitted in this safe area.
return;
}
CItem *p = npc->GetPackItem();
if( !ValidateObject( p ))
{
s->SysMessage( 875 ); // Bad luck, your victim doesn't have a backpack.
return;
}
CItem *item = nullptr;
GenericList<CItem *> *tcCont = p->GetContainsList();
const size_t numItems = tcCont->Num();
std::vector<CItem *> contList;
contList.reserve( numItems );
if( numItems > 0 )
{
for( item = tcCont->First(); !tcCont->Finished(); item = tcCont->Next() )
{
if( ValidateObject( item ))
{
contList.push_back( item );
}
}
item = contList[RandomNum( static_cast<size_t>( 0 ), ( contList.size() - 1 ))];
}
contList.clear();
if( !ValidateObject( item ))
{
s->SysMessage( 876 ); // Muahaha, your victim doesn't have possessions.
return;
}
DoStealing( s, mChar, npc, item );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::StealingTarget()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Called when player targets an item with stealing skill
//o------------------------------------------------------------------------------------------------o
void CSkills::StealingTarget( CSocket *s )
{
VALIDATESOCKET( s );
CChar *mChar = s->CurrcharObj();
if( s->GetByte( 7 ) < 0x40 )
{
RandomSteal( s );
return;
}
CItem *item = CalcItemObjFromSer( s->GetDWord( 7 ));
if( !ValidateObject( item ))
return;
CChar *npc = FindItemOwner( item );
if( !ValidateObject( npc ))
return;
// Check if stealing is allowed in target char's region
if( npc->GetRegion()->IsSafeZone() )
{
// Target is in a safe zone where all aggressive actions are forbidden, disallow
s->SysMessage( 1799 ); // Hostile actions are not permitted in this safe area.
return;
}
if( item->GetCont() == npc || item->GetCont() == nullptr || item->IsNewbie() )
{
s->SysMessage( 874 ); // You cannot steal that.
return;
}
DoStealing( s, mChar, npc, item );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::DoStealing()
//| Date - 2/13/2003
//o------------------------------------------------------------------------------------------------o
//| Purpose - Do the bulk of stealing stuff rather than having the same
//| code twice in RandomSteal() and StealingTarget()
//o------------------------------------------------------------------------------------------------o
void CSkills::DoStealing( CSocket *s, CChar *mChar, CChar *npc, CItem *item )
{
VALIDATESOCKET( s );
if( npc == mChar )
{
s->SysMessage( 873 ); // You catch yourself red handed.
return;
}
if( npc->GetNpcAiType() == AI_PLAYERVENDOR )
{
s->SysMessage( 874 ); // You cannot steal that.
return;
}
CItem *itemCont = static_cast<CItem *>( item->GetCont() );
if( itemCont != nullptr && itemCont->GetLayer() >= IL_SELLCONTAINER && itemCont->GetLayer() <= IL_BUYCONTAINER ) // is it in the sell or buy layer of a vendor?
{
s->SysMessage( 874 ); // You cannot steal that.
return;
}
std::vector<UI16> scriptTriggers = item->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
// Loop through attached scripts
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
SI08 retVal = toExecute->OnSteal( mChar, item, npc );
if( retVal == 1 ) // Item was not stolen, and we don't want to run hard code
{
return;
}
else if( retVal == 2 ) // item was stolen, and we don't want to run hard code
{
std::vector<UI16> targetScriptTriggers = npc->GetScriptTriggers();
for( auto targScriptTrig : targetScriptTriggers )
{
cScript *targToExecute = JSMapping->GetScript( targScriptTrig );
if( targToExecute != nullptr )
{
if( targToExecute->OnStolenFrom( mChar, npc, item ) == 1 )
{
break;
}
}
}
return;
}
}
}
std::string npcTargetName = GetNpcDictName( npc );
std::string myNpcTargetName = GetNpcDictName( npc, s );
s->SysMessage( 877, myNpcTargetName.c_str() ); // You reach into %s's pack and try to take something...
if( ObjInRange( mChar, npc, DIST_NEXTTILE ))
{
if( Combat->CalcDef( mChar, 0, false ) > 40 )
{
s->SysMessage( 1643 ); // Your armour is too heavy to do that
return;
}
SI16 stealCheck = CalcStealDiff( mChar, item );
if( stealCheck == -1 )
{
s->SysMessage( 1551 ); // That is too heavy
return;
}
if( mChar->GetCommandLevel() < npc->GetCommandLevel() )
{
s->SysMessage( 878 ); // You can't steal from gods.
return;
}
if( item->IsNewbie() )
{
s->SysMessage( 879 ); // That item has no value to you.
return;
}
const SI32 getDefOffset = std::min( stealCheck + ( static_cast<SI32>(( Combat->CalcDef( mChar, 0, false ) - 1 ) / 10 ) * 100 ), 990 );
const bool canSteal = CheckSkill( mChar, STEALING, getDefOffset, 1000);
if( canSteal )
{
CItem *pack = mChar->GetPackItem();
item->SetCont( pack );
s->SysMessage( 880 ); // You successfully steal that item.
std::vector<UI16> targetScriptTriggers = npc->GetScriptTriggers();
for( auto targScriptTrig : targetScriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( targScriptTrig );
if( toExecute != nullptr )
{
if( toExecute->OnStolenFrom( mChar, npc, item ) == 1 )
{
return;
}
}
}
}
else
{
s->SysMessage( 881 ); // You failed to steal that item.
}
if(( !canSteal && RandomNum( 1, 5 ) == 5 ) || mChar->GetSkill( STEALING ) < RandomNum( 0, 1001 ))
{
//Did they get caught? (If they fail 1 in 5 chance, other wise their skill away from 1000 out of 1000 chance)
s->SysMessage( 882 ); // You have been caught!
if( npc->IsNpc() )
{
npc->TextMessage( nullptr, 883, TALK, false ); // Guards!! A thief is among us!
}
if( WillResultInCriminal( mChar, npc ))
{
MakeCriminal( mChar );
}
std::string temp;
std::string temp2;
if( item->GetName()[0] != '#' )
{
temp = oldstrutil::format(512, Dictionary->GetEntry( 884 ), mChar->GetNameRequest( npc ).c_str(), item->GetNameRequest( npc ).c_str() ); // // You notice %s trying to steal %s from you!
temp2 = Dictionary->GetEntry( 885 ); // You notice %s trying to steal %s from %s!
}
else
{
std::string tileName;
tileName.reserve( MAX_NAME );
GetTileName(( *item ), tileName );
temp = oldstrutil::format( 512, Dictionary->GetEntry( 884 ), mChar->GetNameRequest( npc ).c_str(), tileName.c_str() ); // You notice %s trying to steal %s from you!
temp2 = Dictionary->GetEntry( 885 ); // You notice %s trying to steal %s from %s!
}
CSocket *npcSock = npc->GetSocket();
if( npcSock != nullptr )
{
npcSock->SysMessage( temp );
}
for( auto &iSock : FindNearbyPlayers( mChar ))
{
CChar *iChar = iSock->CurrcharObj();
if(( iSock != s ) && (( RandomNum( 10, 20 ) == 17 ) || (( RandomNum( 0, 1 ) == 1 ) && ( iChar->GetIntelligence() >= mChar->GetIntelligence() ))))
{
temp2 = oldstrutil::format( 512, temp2, mChar->GetNameRequest( iChar ).c_str(), item->GetNameRequest( iChar ).c_str(), npcTargetName.c_str() ); // You notice %s trying to steal %s from %s!
iSock->SysMessage( temp2 );
}
}
}
}
else
{
s->SysMessage( 886 ); // You are too far away to steal that item.
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CalcStealDiff()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Check if item is able to be stolen based on item type and weight vs stealing
//| skill, then return the min skill needed to steal the item.
//o------------------------------------------------------------------------------------------------o
SI16 CSkills::CalcStealDiff( CChar *c, CItem *i )
{
if( i->IsLockedDown() )
return -1;
SI16 stealDiff = -1;
SI32 calcDiff, itemWeight;
switch( i->GetType() )
{
case IT_SPELLBOOK: // Spellbook
case IT_SPAWNCONT: // Item spawn container
case IT_LOCKEDSPAWNCONT: // Locked spawn container
case IT_UNLOCKABLESPAWNCONT: // Unlockable item spawn container
case IT_TRASHCONT: // Trash container
break;
case IT_CONTAINER: // Backpack
default:
if( i->GetId() == 0x14F0 ) // Deeds
break;
itemWeight = i->GetWeight();
calcDiff = static_cast<SI32>(( c->GetSkill( STEALING ) * 2 ) + 100 ); // GM thieves can steal up to 21 stones, newbie only 1 stone
if( calcDiff > itemWeight )
{
stealDiff = static_cast<SI16>( std::max( std::min(( static_cast<SI32>(( itemWeight + 9 ) / 20 ) * 10 ), 990 ), 0 ));
}
break;
}
return stealDiff;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::Tracking()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Start tracking selected NPC/PC
//o------------------------------------------------------------------------------------------------o
void CSkills::Tracking( CSocket *s, SI32 selection )
{
VALIDATESOCKET( s );
CChar *i = s->CurrcharObj();
// sets trackingtarget that was selected in the gump
i->SetTrackingTarget( i->GetTrackingTargets( selection ));
// tracking time in seconds ... gm tracker -> basetimer + 1 seconds, 0 tracking -> 1 sec, new calc
s->SetTimer( tPC_TRACKING, BuildTimeValue( static_cast<R32>( cwmWorldState->ServerData()->TrackingBaseTimer() * i->GetSkill( TRACKING ) / 1000 + 1 )));
s->SetTimer( tPC_TRACKINGDISPLAY, BuildTimeValue( static_cast<R32>( cwmWorldState->ServerData()->TrackingRedisplayTime() )));
if( ValidateObject( i->GetTrackingTarget() ))
{
std::string trackingTargetName = GetNpcDictName( i->GetTrackingTarget() );
s->SysMessage( 1644, trackingTargetName.c_str() ); // You are now tracking %s.
}
Track( i );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::CreateTrackingMenu()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Brings up Tracking Menu, Called when player uses Tracking Skill
//o------------------------------------------------------------------------------------------------o
void CSkills::CreateTrackingMenu( CSocket *s, UI16 m )
{
VALIDATESOCKET( s );
const std::string sect = std::string( "TRACKINGMENU " ) + util::ntos( m );
CScriptSection *TrackStuff = FileLookup->FindEntry( sect, menus_def );
if( TrackStuff == nullptr )
return;
enum CreatureTypes
{
CT_ANIMAL = 0,
CT_CREATURE,
CT_PERSON
};
CChar *mChar = s->CurrcharObj();
UI16 id = 0;
CreatureTypes creatureType = CT_ANIMAL;
SI32 type = 887; // You see no signs of any animals.
UI08 MaxTrackingTargets = 0;
const UI16 distance = ( cwmWorldState->ServerData()->TrackingBaseRange() + ( mChar->GetSkill( TRACKING ) / 50 ));
UnicodeTypes mLang = s->Language();
if( m == ( 2 + TRACKINGMENUOFFSET ))
{
creatureType = CT_CREATURE;
type = 888; // You see no signs of any creatures.
}
else if( m == ( 3 + TRACKINGMENUOFFSET ))
{
creatureType = CT_PERSON;
type = 889; // You see no signs of anyone.
}
std::string line;
CPOpenGump toSend( *mChar );
if( m >= TRACKINGMENUOFFSET )
{
toSend.GumpIndex( m );
}
else
{
toSend.GumpIndex( m + TRACKINGMENUOFFSET );
}
std::string tag = TrackStuff->First();
std::string data = TrackStuff->GrabData();
line = tag + " " + data;
toSend.Question( line );
for( auto &MapArea : MapRegion->PopulateList( mChar ))
{
if( MapArea )
{
auto regChars = MapArea->GetCharList();
for( const auto &tempChar : regChars->collection() )
{
if( MaxTrackingTargets < cwmWorldState->ServerData()->TrackingMaxTargets() )
{
if( ValidateObject( tempChar ) && ( tempChar->GetInstanceId() == mChar->GetInstanceId() ))
{
id = tempChar->GetId();
if(( !cwmWorldState->creatures[id].IsHuman() || creatureType == CT_PERSON ) && ( !cwmWorldState->creatures[id].IsAnimal() || creatureType == CT_ANIMAL ))
{
const bool cmdLevelCheck = ( IsOnline(( *tempChar )) && ( mChar->GetCommandLevel() >= tempChar->GetCommandLevel() ));
if( tempChar != mChar && ObjInRange( tempChar, mChar, distance ) && !tempChar->IsDead() && ( cmdLevelCheck || tempChar->IsNpc() ))
{
mChar->SetTrackingTargets( tempChar, MaxTrackingTargets );
++MaxTrackingTargets;
SI32 dirMessage = 898; // right next to you.
switch( Movement->Direction( mChar, tempChar->GetX(), tempChar->GetY() ))
{
case NORTH: dirMessage = 890; break; // to the North.
case NORTHWEST: dirMessage = 891; break; // to the Northwest.
case NORTHEAST: dirMessage = 892; break; // to the Northeast.
case SOUTH: dirMessage = 893; break; // to the South.
case SOUTHWEST: dirMessage = 894; break; // to the Southwest.
case SOUTHEAST: dirMessage = 895; break; // to the Southeast.
case WEST: dirMessage = 896; break; // to the West.
case EAST: dirMessage = 897; break; // to the East.
default: dirMessage = 898; break; // right next to you.
}
auto tempName = GetNpcDictName( tempChar );
line = tempName + " "s + Dictionary->GetEntry( dirMessage, mLang );
toSend.AddResponse( cwmWorldState->creatures[id].Icon(), 0, line );
}
}
}
}
else
{
break;
}
}
}
}
if( MaxTrackingTargets == 0 )
{
s->SysMessage( type );
return;
}
toSend.Finalize();
s->Send( &toSend );
}
void HandleCommonGump( CSocket *mSock, CScriptSection *gumpScript, UI16 gumpIndex );
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::TrackingMenu()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Brings up additional tracking menu with options listed in tracking.dfn
//o------------------------------------------------------------------------------------------------o
void CSkills::TrackingMenu( CSocket *s, UI16 gmindex )
{
VALIDATESOCKET( s );
const std::string sect = std::string( "TRACKINGMENU " ) + util::ntos( gmindex );
CScriptSection *TrackStuff = FileLookup->FindEntry( sect, menus_def );
if( TrackStuff == nullptr )
{
return;
}
HandleCommonGump( s, TrackStuff, gmindex );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::Track()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Tracking stuff for older client
//o------------------------------------------------------------------------------------------------o
void CSkills::Track( CChar *i )
{
CSocket *s = i->GetSocket();
VALIDATESOCKET( s );
CChar *trackTarg = i->GetTrackingTarget();
if( !ValidateObject( trackTarg ) || trackTarg->IsDeleted() || trackTarg->GetY() == -1 )
{
s->SysMessage( 2059 ); // You have lost your quarry.
s->SetTimer( tPC_TRACKING, 0 );
CPTrackingArrow tSend;
tSend.Active( 0 );
if( s->ClientVersion() >= CV_HS2D )
{
tSend.AddSerial( i->GetTrackingTargetSerial() );
}
s->Send( &tSend );
return;
}
CPTrackingArrow tSend = ( *trackTarg );
tSend.Active( 1 );
if( s->ClientType() >= CV_HS2D )
{
tSend.AddSerial( i->GetTrackingTarget()->GetSerial() );
}
s->Send( &tSend );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::UpdateSkillLevel()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Calculate the skill of this character based on the characters baseskill and stats
//o------------------------------------------------------------------------------------------------o
void CSkills::UpdateSkillLevel( CChar *c, UI08 s ) const
{
UI16 sStr = cwmWorldState->skill[s].strength;
SI16 aStr = c->ActualStrength();
UI16 sDex = cwmWorldState->skill[s].dexterity;
SI16 aDex = c->ActualDexterity();
UI16 sInt = cwmWorldState->skill[s].intelligence;
SI16 aInt = c->ActualIntelligence();
UI16 bSkill = c->GetBaseSkill( s );
UI16 temp = ((( sStr * aStr ) / 100 + ( sDex * aDex ) / 100 + ( sInt + aInt ) / 100) * ( 1000 - bSkill )) / 1000 + bSkill;
c->SetSkill( std::max( bSkill, temp ), s );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::Persecute()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Called when a Ghost attacks a Player. If entry in UOX.INI
//| is enabled, players mana decreases each time you try to
//| persecute him
//o------------------------------------------------------------------------------------------------o
void CSkills::Persecute( CSocket *s )
{
VALIDATESOCKET( s );
CChar *c = s->CurrcharObj();
CChar *targChar = c->GetTarg();
if( !ValidateObject( targChar ) || targChar->IsGM() )
return;
SI32 decrease = static_cast<SI32>(( c->GetSkill( SPIRITSPEAK ) / 100 ) + ( c->GetIntelligence() / 20 )) + 3;
if( s->GetTimer( tPC_SKILLDELAY ) <= cwmWorldState->GetUICurrentTime() || c->IsGM() )
{
if(( RandomNum( 0, 19 ) + c->GetIntelligence() ) > 45 ) // not always
{
CSocket *tSock = targChar->GetSocket();
targChar->SetMana( targChar->GetMana() - decrease ); // decrease mana
s->SysMessage( 972 ); // Your spiritual forces disturb the enemy!
if( tSock != nullptr )
{
tSock->SysMessage( 973 ); // A damned soul is disturbing your mind!
}
if( cwmWorldState->skill[SPIRITSPEAK].skillDelay != -1 )
{
// Use skill-specific skill delay if one has been set
s->SetTimer( tPC_SKILLDELAY, BuildTimeValue( static_cast<R32>( cwmWorldState->skill[SPIRITSPEAK].skillDelay )));
}
else
{
// Otherwise use global skill delay from uox.ini
s->SetTimer( tPC_SKILLDELAY, BuildTimeValue( static_cast<R32>( cwmWorldState->ServerData()->ServerSkillDelayStatus() )));
}
std::string targCharName = GetNpcDictName( targChar );
targChar->TextMessage( nullptr, 974, EMOTE, 1, targCharName.c_str() ); // %s is persecuted by a ghost!
}
else
{
s->SysMessage( 975 ); // Your mind is not strong enough to disturb the enemy
}
}
else
{
s->SysMessage( 976 ); // You are unable to persecute him now. Rest a little.
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::Smith()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Called when player attempts to smith an item
//o------------------------------------------------------------------------------------------------o
void CSkills::Smith( CSocket *s )
{
VALIDATESOCKET( s );
CChar *mChar = s->CurrcharObj();
CItem *packnum = mChar->GetPackItem();
// Check if item used to initialize target cursor is still within range
CItem *tempObj = static_cast<CItem *>( s->TempObj() );
s->TempObj( nullptr );
if( ValidateObject( tempObj ))
{
if( tempObj->IsHeldOnCursor() || !CheckItemRange( mChar, tempObj ))
{
s->SysMessage( 400 ); // That is too far away!
return;
}
}
if( !ValidateObject( packnum ))
{
s->SysMessage( 773 ); // Time to buy a backpack.
return;
}
CItem *i = CalcItemObjFromSer( s->GetDWord( 7 ));
if( ValidateObject( i ))
{
if( i->IsHeldOnCursor() || !CheckItemRange( mChar, i ))
{
s->SysMessage( 400 ); // That is too far away!
return;
}
if( i->GetId() >= 0x1BE3 && i->GetId() <= 0x1BF9 ) // is it an ingot?
{
MiningData_st *oreType = FindOre( i->GetColour() );
if( oreType == nullptr )
{
s->SysMessage( 977 ); // Unknown ingot type.
return;
}
if( FindItemOwner( i ) != mChar )
{
s->SysMessage( 978, oreType->name.c_str() ); // You can't smith %s ingots outside your backpack.
}
else
{
AnvilTarget( s, ( *i ), oreType );
}
}
else
{
// something we might repair?
RepairMetal( s );
}
return;
}
s->SysMessage( 979 ); // You cannot use that material for blacksmithing!
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::AnvilTarget()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Check for the presence of an anvil when player attempts to use smithing skill,
//| then verify that there is enough of the chosen ingot type in their backpack(s)
//o------------------------------------------------------------------------------------------------o
void CSkills::AnvilTarget( CSocket *s, CItem& item, MiningData_st *oreType )
{
VALIDATESOCKET( s );
CChar *mChar = s->CurrcharObj();
for( auto &MapArea : MapRegion->PopulateList( mChar ))
{
if( MapArea )
{
auto regItems = MapArea->GetItemList();
for( const auto &tempItem : regItems->collection() )
{
if( ValidateObject( tempItem ) && ( tempItem->GetInstanceId() == mChar->GetInstanceId() ))
{
if( tempItem->GetId() == 0x0FAF || tempItem->GetId() == 0x0FB0 )
{
if( ObjInRange( mChar, tempItem, DIST_NEARBY ))
{
UI32 getAmt = GetItemAmount( mChar, item.GetId(), item.GetColour(), item.GetTempVar( CITV_MORE ));
if( getAmt == 0 )
{
s->SysMessage( 980, oreType->name.c_str() ); // You don't have enough %s ingots to make anything.
return;
}
NewMakeMenu( s, oreType->makemenu, BLACKSMITHING );
return;
}
}
}
}
}
}
s->SysMessage( 981 ); // The anvil is too far away.
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::CSkills()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Default constructor
//o------------------------------------------------------------------------------------------------o
CSkills::CSkills()
{
ores.resize( 0 );
}
CSkills::~CSkills()
{
ores.resize( 0 );
skillMenus.clear();
itemsForMenus.clear();
actualMenus.clear();
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::LoadMiningData()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Load mining resource stuff from scripts
//o------------------------------------------------------------------------------------------------o
bool CSkills::LoadMiningData( void )
{
ores.resize( 0 );
// Let's first get our ore list, in SECTION ORE_LIST
CScriptSection *oreList = FileLookup->FindEntry( "ORE_LIST", skills_def );
bool rValue = false;
if( oreList != nullptr )
{
std::vector<std::string> oreNameList;
std::string tag;
std::string data;
std::string UTag;
for( tag = oreList->First(); !oreList->AtEnd(); tag = oreList->Next() )
{
oreNameList.push_back( tag );
}
if( !oreNameList.empty() )
{
rValue = true;
CScriptSection *individualOre = nullptr;
for( auto &oreName : oreNameList )
{
individualOre = FileLookup->FindEntry( oreName, skills_def );
if( individualOre != nullptr )
{
MiningData_st toAdd;
toAdd.colour = 0;
toAdd.makemenu = 0;
toAdd.minSkill = 0;
toAdd.name = oreName;
toAdd.oreName = oreName;
toAdd.oreChance = 0;
toAdd.scriptID = 0;
for( const auto &sec : individualOre->collection() )
{
tag = sec->tag;
data = sec->data;
UTag = util::upper( tag );
data = util::trim( util::strip( data, "//" ));
switch(( UTag.data()[0] )) // break on tag
{
case 'C':
if( UTag == "COLOUR" )
{
toAdd.colour = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
}
break;
case 'F':
break;
case 'M':
if( UTag == "MAKEMENU" )
{
toAdd.makemenu = std::stoi( data, nullptr, 0 );
}
else if( UTag == "MINSKILL" )
{
toAdd.minSkill = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
}
break;
case 'N':
if( UTag == "NAME" )
{
toAdd.name = data;
}
break;
case 'O':
if( UTag == "ORECHANCE" )
{
toAdd.oreChance = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
}
break;
case 'S':
if( UTag == "SCRIPT" )
{
toAdd.scriptID = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
}
break;
default:
Console << "Unknown mining tag " << tag << " with data " << data << " in SECTION " << oreName << myendl;
break;
}
}
ores.push_back( toAdd );
}
}
}
}
return rValue;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::Load()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Load mining stuff from scripts
//o------------------------------------------------------------------------------------------------o
void CSkills::Load( void )
{
Console << "Loading custom ore data ";
if( !LoadMiningData() )
{
Shutdown( FATAL_UOX3_ORELIST );
return;
}
Console.PrintDone();
Console << "Loading creation menus ";
LoadCreateMenus();
Console.PrintDone();
CJSMappingSection *skillSection = JSMapping->GetSection( SCPT_SKILLUSE );
for( cScript *ourScript = skillSection->First(); !skillSection->Finished(); ourScript = skillSection->Next() )
{
if( ourScript != nullptr )
{
ourScript->ScriptRegistration( "Skill" );
}
}
Console.PrintSectionBegin();
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::GetNumberOfOres()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Returns number of custom ores
//o------------------------------------------------------------------------------------------------o
size_t CSkills::GetNumberOfOres( void )
{
return ores.size();
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::GetOre()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Returns a handle to the data about the ore
//o------------------------------------------------------------------------------------------------o
MiningData_st *CSkills::GetOre( size_t number )
{
if( number >= ores.size() )
return nullptr;
return &ores[number];
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::FindOre()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Returns a handle to the data about the ore based on it's name
//o------------------------------------------------------------------------------------------------o
MiningData_st *CSkills::FindOre( std::string const &name )
{
std::vector<MiningData_st>::iterator oreIter;
for( oreIter = ores.begin(); oreIter != ores.end(); ++oreIter )
{
if(( *oreIter ).oreName == name )
{
return &( *oreIter );
}
}
return nullptr;
}
//o------------------------------------------------------------------------------------------------o
//| Function - *CSkills::FindOre()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Find ore color
//o------------------------------------------------------------------------------------------------o
MiningData_st *CSkills::FindOre( UI16 const &colour )
{
std::vector<MiningData_st>::iterator oreIter;
for( oreIter = ores.begin(); oreIter != ores.end(); ++oreIter )
{
if(( *oreIter ).colour == colour )
{
return &( *oreIter );
}
}
return nullptr;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::LoadCreateMenus()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Load Create menus from the create DFNs
//o------------------------------------------------------------------------------------------------o
auto CSkills::LoadCreateMenus() -> void
{
actualMenus.clear();
skillMenus.clear();
itemsForMenus.clear();
UI16 ourEntry; // our actual entry number
for( Script *ourScript = FileLookup->FirstScript( create_def ); !FileLookup->FinishedScripts( create_def ); ourScript = FileLookup->NextScript( create_def ))
{
if( ourScript == nullptr )
{
continue;
}
for( const auto &[eName1, toSearch] : ourScript->collection() )
{
auto eName = eName1;
if( "SUBMENU" == eName.substr( 0, 7 )) // is it a menu? (not really SUB, just to avoid picking up MAKEMENUs)
{
eName = util::trim( util::strip( eName, "//" ));
auto ssecs = util::parse( eName, " " );
ourEntry = static_cast<UI16>( std::stoul( std::string(util::trim( util::strip( ssecs[1], "//" ))), nullptr, 0 ));
for( const auto &sec : toSearch->collection() )
{
auto tag = sec->tag;
auto data = sec->data;
auto UTag = util::upper( tag );
data = util::trim( util::strip( data, "//" ));
if( UTag == "MENU" )
{
actualMenus[ourEntry].menuEntries.push_back( static_cast<UI16>( std::stoul( data, nullptr, 0 )));
}
else if( UTag == "ITEM" )
{
actualMenus[ourEntry].itemEntries.push_back( static_cast<UI16>( std::stoul( data, nullptr, 0 )));
}
}
}
else if( "ITEM" == eName.substr( 0, 4 )) // is it an item?
{
auto ssecs = util::parse( eName, " " );
ourEntry = static_cast<UI16>( std::stoul( std::string(util::trim( util::strip( ssecs[1], "//" ))), nullptr, 0 ));
CreateEntry_st tmpEntry;
tmpEntry.minRank = 0;
tmpEntry.maxRank = 10;
tmpEntry.colour = 0;
tmpEntry.targId = 1;
tmpEntry.soundPlayed = 0;
for( const auto &sec : toSearch->collection() )
{
auto tag = sec->tag;
auto data = sec->data;
auto UTag = util::upper( tag );
data = util::trim( util::strip( data, "//" ));
if( UTag == "COLOUR" )
{
tmpEntry.colour = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
}
else if( UTag == "ID" )
{
tmpEntry.targId = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
}
else if( UTag == "MINRANK" )
{
tmpEntry.minRank = static_cast<UI08>( std::stoul( data, nullptr, 0 ));
}
else if( UTag == "MAXRANK" )
{
tmpEntry.maxRank = static_cast<UI08>( std::stoul( data, nullptr, 0 ));
}
else if( UTag == "NAME" )
{
tmpEntry.name = data;
}
else if( UTag == "SOUND" )
{
tmpEntry.soundPlayed = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
}
else if( UTag == "ADDITEM" )
{
tmpEntry.addItem = data;
}
else if( UTag == "DELAY" )
{
tmpEntry.delay = static_cast<SI16>( std::stoi( data, nullptr, 0 ));
}
else if( UTag == "RESOURCE" )
{
ResAmountPair_st tmpResource;
auto ssecs = util::parse( data," ");
if( ssecs.size() > 1 )
{
if( ssecs.size() == 4 )
{
tmpResource.moreVal = static_cast<UI32>( std::stoul( std::string(util::trim( util::strip( ssecs[3], "//" ))), nullptr, 0 ));
}
if( ssecs.size() >= 3 )
{
tmpResource.colour = static_cast<UI16>( std::stoul( std::string(util::trim( util::strip( ssecs[2], "//" ))), nullptr, 0 ));
}
if( ssecs.size() >= 2 )
{
tmpResource.amountNeeded = static_cast<UI16>( std::stoul( std::string(util::trim( util::strip( ssecs[1], "//" ))), nullptr, 0 ));
}
}
auto resType = "RESOURCE "s + std::string(util::trim( util::strip( ssecs[0], "//" )));
auto resList = FileLookup->FindEntry( resType, create_def );
if( resList )
{
for( const auto &sec : resList->collection() )
{
tmpResource.idList.push_back( static_cast<UI16>( std::stoul( sec->data, nullptr, 0 )));
}
}
else
{
tmpResource.idList.push_back( static_cast<UI16>( std::stoul( std::string(util::trim( util::strip( ssecs[0], "//" ))), nullptr, 0 )));
}
tmpEntry.resourceNeeded.push_back( tmpResource );
}
else if( UTag == "SKILL" )
{
ResSkillReq_st tmpResource;
auto ssecs = util::parse( data, " " );
if( ssecs.size() == 1 )
{
tmpResource.maxSkill = 1000;
tmpResource.minSkill = 0;
tmpResource.skillNumber = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
}
else
{
if( ssecs.size() == 2 )
{
tmpResource.maxSkill = 1000;
tmpResource.minSkill = static_cast<UI16>( std::stoul( std::string(util::trim( util::strip( ssecs[1], "//" ))), nullptr, 0 ));
}
else
{
tmpResource.minSkill = static_cast<UI16>( std::stoul( std::string(util::trim( util::strip( ssecs[1], "//" ))), nullptr, 0 ));
tmpResource.maxSkill = static_cast<UI16>( std::stoul( std::string(util::trim( util::strip( ssecs[2], "//" ))), nullptr, 0 ));
}
tmpResource.skillNumber = static_cast<UI16>( std::stoul( std::string(util::trim( util::strip( ssecs[0], "//" ))), nullptr, 0 ));
}
tmpEntry.skillReqs.push_back( tmpResource );
}
else if( UTag == "SPELL" )
{
tmpEntry.spell = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
}
}
itemsForMenus[ourEntry] = tmpEntry;
}
else if( "MENUENTRY" == eName.substr( 0, 9 ))
{
auto ssecs = util::parse( eName, " " );
ourEntry = static_cast<UI16>( std::stoul( std::string(util::trim( util::strip( ssecs[1], "//" ))), nullptr, 0 ));
for( const auto &sec : toSearch->collection() )
{
auto tag = sec->tag;
auto data = sec->data;
auto UTag = util::upper( tag );
data = std::string(util::trim( util::strip( data, "//" )));
if( UTag == "ID" )
{
skillMenus[ourEntry].targId = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
}
else if( UTag == "COLOUR" )
{
skillMenus[ourEntry].colour = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
}
else if( UTag == "NAME" )
{
skillMenus[ourEntry].name = data;
}
else if( UTag == "SUBMENU" )
{
skillMenus[ourEntry].subMenu = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
}
}
}
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::AdvanceSkill()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Advance players skill based on success or failure in CheckSkill()
//o------------------------------------------------------------------------------------------------o
bool CSkills::AdvanceSkill( CChar *s, UI08 sk, bool skillUsed )
{
bool advSkill = false;
SI16 skillGain;
SI08 skillAdvance = FindSkillPoint( sk, s->GetBaseSkill( sk ));
if( skillUsed )
{
skillGain = ( cwmWorldState->skill[sk].advancement[skillAdvance].success );
}
else
{
skillGain = ( cwmWorldState->skill[sk].advancement[skillAdvance].failure );
}
if( skillGain > RandomNum( 1, 100 ))
{
advSkill = true;
if( s->GetSkillLock( sk ) == SKILL_INCREASE )
{
HandleSkillChange( s, sk, skillAdvance, skillUsed );
}
}
if( s->GetSkillLock( sk ) != SKILL_LOCKED ) // if it's locked, stats can't advance
{
AdvanceStats( s, sk, skillUsed );
}
return advSkill;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::FindSkillPoint()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Find skillpoint advancement parameters from a skill in skills.dfn,
//| based on a specified skill value
//o------------------------------------------------------------------------------------------------o
SI08 CSkills::FindSkillPoint( UI08 sk, SI32 value )
{
SI08 retVal = -1;
for( size_t iCounter = 0; iCounter < cwmWorldState->skill[sk].advancement.size() - 1; ++iCounter )
{
if( cwmWorldState->skill[sk].advancement[iCounter].base <= value && value < cwmWorldState->skill[sk].advancement[iCounter+1].base )
{
retVal = static_cast<SI08>( iCounter );
break;
}
}
if( retVal == -1 )
{
retVal = static_cast<SI08>( cwmWorldState->skill[sk].advancement.size() - 1 );
}
return retVal;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::AdvanceStats()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Advance players stats
//o------------------------------------------------------------------------------------------------o
void CSkills::AdvanceStats( CChar *s, UI08 sk, bool skillsuccess )
{
CRace *pRace = Races->Race( s->GetRace() );
// If the Race is invalid just use the default race
if( pRace == nullptr )
{
pRace = Races->Race( 0 );
}
//make sure socket is no npc
if( s->IsNpc() )
return;
UI32 serverStatCap = cwmWorldState->ServerData()->ServerStatCapStatus();
UI32 ttlStats = s->ActualStrength() + s->ActualDexterity() + s->ActualIntelligence();
SI16 chanceStatGain = 0; //16bit because of freaks that raises it > 100
SI32 statCount, nCount;
SI32 toDec = 255;
UI16 maxChance = 100;
SI16 ActualStat[3] = { s->ActualStrength() , s->ActualDexterity() , s->ActualIntelligence() };
UI16 StatModifier[3] = { cwmWorldState->skill[sk].strength , cwmWorldState->skill[sk].dexterity , cwmWorldState->skill[sk].intelligence };
SkillLock StatLocks[3] = { s->GetSkillLock( STRENGTH ), s->GetSkillLock( DEXTERITY ), s->GetSkillLock( INTELLECT ) };
std::vector<UI16> skillUpdTriggers = s->GetScriptTriggers();
for( statCount = STRENGTH; statCount <= INTELLECT; ++statCount )
{
nCount = statCount - ALLSKILLS - 1;
// if current stat isn't allowed to increase skip it.
if( StatLocks[nCount] == SKILL_INCREASE )
{
// the following will calculate the chances for str/dex/int to increase
//
// it is divided into 2 "dices":
// first dice: get the success-skillpoint of the stat out of skills.dfn in dependence
// of the racial statCap for this stat... x means stat is x% of statCap
// => get skillpoint for x
// sec. dice: get the chance for this stat to increase when the used skill has been used
// (out of skills.dfn) => get x = statmodifier of skill
// last make it a integer between 0 and 1000 normally (negative or 0==no chance)
// special dice 1: the stat wont increase above x% of the racial statCap. x% is equivalent to dice 2.
// special dice 2: skill failed: decrease chance by 50%
// k, first let us calculate both dices
UI08 modifiedStatLevel = FindSkillPoint( statCount-1, static_cast<SI32>( static_cast<R32>( ActualStat[nCount] ) / static_cast<R32>( pRace->Skill( statCount )) * 100 ));
chanceStatGain = static_cast<SI16>(( static_cast<R32>( cwmWorldState->skill[statCount-1].advancement[modifiedStatLevel].success ) / 100 ) * ( static_cast<R32>( static_cast<R32>( StatModifier[nCount] ) / 10 ) / 100 ) * 1000 );
// some mathematics in it;)
// now, lets implement the special dice 1 and additionally check for onStatGain javascript method
if( StatModifier[nCount] <= static_cast<SI32>( static_cast<R32>( ActualStat[nCount] ) / static_cast<R32>( pRace->Skill( statCount )) * 100 ))
{
chanceStatGain = 0;
}
// special dice 2
if( !skillsuccess )
{
maxChance *= 2;
}
// if stat of char < racial statCap and chance for statgain > random number from 0 to 100
if( ActualStat[nCount] < pRace->Skill( statCount ) && chanceStatGain > RandomNum( static_cast<UI16>( 0 ), maxChance ))
{
// Check if we have to decrease a stat
if(( ttlStats + 1) >= RandomNum( serverStatCap - 10, serverStatCap ))
{
for( SI32 i = 0; i < 3; i++ )
{
if( StatLocks[i] == SKILL_DECREASE )
{
// Decrease the highest stat, that is allowed to decrease
if( toDec == 255 )
{
toDec = i;
}
else
{
if( ActualStat[i] > ActualStat[toDec] )
{
toDec = i;
}
}
}
}
switch( toDec )
{
case 0: // Decrease Strength stat
// First trigger onStatLoss event
for( auto skillTrig : skillUpdTriggers )
{
cScript *toExecute = JSMapping->GetScript( skillTrig );
if( toExecute != nullptr )
{
if( !toExecute->OnStatLoss( s, STRENGTH, 1 ))
return;
}
}
// Do the actual stat decrease
s->IncStrength( -1 );
ttlStats--;
// Trigger onStatChange event if onStatLoss either didn't exist, or returned true
for( auto skillTrig : skillUpdTriggers )
{
cScript *toExecute = JSMapping->GetScript( skillTrig );
if( toExecute != nullptr )
{
toExecute->OnStatChange( s, STRENGTH, -1 );
}
}
break;
case 1: // Decrease Dexterity stat
// First trigger onStatLoss event
for( auto skillTrig : skillUpdTriggers )
{
cScript *toExecute = JSMapping->GetScript( skillTrig );
if( toExecute != nullptr )
{
if( !toExecute->OnStatLoss( s, DEXTERITY, 1 ))
return;
}
}
// Do the actual stat decrease
s->IncDexterity( -1 );
ttlStats--;
// Trigger onStatChange event if onStatLoss either didn't exist, or returned true
for( auto skillTrig : skillUpdTriggers )
{
cScript *toExecute = JSMapping->GetScript( skillTrig );
if( toExecute != nullptr )
{
toExecute->OnStatChange( s, DEXTERITY, -1 );
}
}
break;
case 2: // Decrease Intelligence stat
// First trigger onStatLoss event
for( auto skillTrig : skillUpdTriggers )
{
cScript *toExecute = JSMapping->GetScript( skillTrig );
if( toExecute != nullptr )
{
if( !toExecute->OnStatLoss( s, INTELLECT, 1 ))
return;
}
}
// Do the actual stat decrease
s->IncIntelligence( -1 );
ttlStats--;
// Trigger onStatChange event if onStatLoss either didn't exist, or returned true
for( auto skillTrig : skillUpdTriggers )
{
cScript *toExecute = JSMapping->GetScript( skillTrig );
if( toExecute != nullptr )
{
toExecute->OnStatChange( s, INTELLECT, -1 );
}
}
break;
default:
break;
}
}
// Do we still hit the stat limit?
if(( ttlStats + 1) <= serverStatCap )
{
// First trigger onStatGained
for( auto skillTrig : skillUpdTriggers )
{
cScript *toExecute = JSMapping->GetScript( skillTrig );
if( toExecute != nullptr )
{
if( !toExecute->OnStatGained( s, statCount, sk, 1 ))
return;
}
}
// Do the actual stat increase
switch( statCount )
{
case STRENGTH:
s->IncStrength();
break;
case DEXTERITY:
s->IncDexterity();
break;
case INTELLECT:
s->IncIntelligence();
break;
default:
break;
}
// Trigger onStatChange event if onStatGained either didn't exist, or returned true
for( auto skillTrig : skillUpdTriggers )
{
cScript *toExecute = JSMapping->GetScript( skillTrig );
if( toExecute != nullptr )
{
toExecute->OnStatChange( s, statCount, 1 );
}
}
break; // only one stat at a time fellas
}
}
}
}
s->Dirty( UT_STATWINDOW );
for( UI08 i = 0; i < ALLSKILLS; ++i )
{
UpdateSkillLevel( s, i );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::NewMakeMenu()
//o------------------------------------------------------------------------------------------------o
//| Purpose - New make menu system, based on create DFNs
//o------------------------------------------------------------------------------------------------o
void CSkills::NewMakeMenu( CSocket *s, SI32 menu, [[maybe_unused]] UI08 skill )
{
VALIDATESOCKET( s );
CChar *ourChar = s->CurrcharObj();
s->AddId( menu );
std::map<UI16, CreateMenu_st>::const_iterator p = actualMenus.find( menu );
if( p == actualMenus.end() )
return;
UI16 background = cwmWorldState->ServerData()->BackgroundPic();
UI16 btnCancel = cwmWorldState->ServerData()->ButtonCancel();
[[maybe_unused]] UI16 btnLeft = cwmWorldState->ServerData()->ButtonLeft();
UI16 btnRight = cwmWorldState->ServerData()->ButtonRight();
CPSendGumpMenu toSend;
toSend.UserId( INVALIDSERIAL );
toSend.GumpId( 12 );
CreateMenu_st ourMenu = p->second;
UI32 textCounter = 0;
CScriptSection *GumpHeader = FileLookup->FindEntry( "ADDMENU HEADER", misc_def );
if( GumpHeader == nullptr )
{
toSend.addCommand( util::format( "resizepic 0 0 %i 400 320", background) );
toSend.addCommand( "page 0" );
toSend.addCommand( "text 200 20 0 0" );
toSend.addText( "Create menu" );
++textCounter;
toSend.addCommand( util::format( "button 360 15 %i %i 1 0 1", btnCancel, btnCancel + 1 ));
}
else
{
std::string tag, data, UTag;
for( tag = GumpHeader->First(); !GumpHeader->AtEnd(); tag = GumpHeader->Next() )
{
UTag = util::upper( tag );
data = GumpHeader->GrabData();
data = util::trim( util::strip( data, "//" ));
if( UTag == "BUTTONLEFT" )
{
btnLeft = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
}
else if( UTag == "BUTTONRIGHT" )
{
btnRight = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
}
else if( UTag == "BUTTONCANCEL" )
{
btnCancel = static_cast<UI16>( std::stoul( data, nullptr, 0 ));
}
else
{
std::string built = tag;
if( !data.empty() )
{
built += " ";
built += data;
}
toSend.addCommand( built );
}
}
CScriptSection *GumpText = FileLookup->FindEntry( "ADDMENU TEXT", misc_def );
if( GumpText != nullptr )
{
for( tag = GumpText->First(); !GumpText->AtEnd(); tag = GumpText->Next() )
{
data = GumpText->GrabData();
std::string built = tag;
if( !data.empty() )
{
built += " ";
built += data;
}
toSend.addText( built );
++textCounter;
}
}
}
UI16 xLoc = 60, yLoc = 40;
std::map<UI16, CreateEntry_st>::iterator imIter;
std::map<UI16, CreateMenuEntry_st>::iterator smIter;
SI32 actualItems = 0;
for( ourMenu.iIter = ourMenu.itemEntries.begin(); ourMenu.iIter != ourMenu.itemEntries.end(); ++ourMenu.iIter )
{
if(( actualItems % 6 ) == 0 && actualItems != 0 )
{
xLoc += 180;
yLoc = 40;
}
imIter = itemsForMenus.find(( *ourMenu.iIter ));
if( imIter != itemsForMenus.end() )
{
CreateEntry_st iItem = imIter->second;
bool canMake = true;
for( size_t sCounter = 0; sCounter < iItem.skillReqs.size() && canMake; ++sCounter )
{
UI08 skillNum = iItem.skillReqs[sCounter].skillNumber;
UI16 ourSkill = ourChar->GetSkill( skillNum );
UI16 minSkill = iItem.skillReqs[sCounter].minSkill;
canMake = ( ourSkill >= minSkill );
// it was not thought that way, its not logical, maxSkill should say, that you can get maxRank with maxSkill and higher
}
if( iItem.spell ) // Should only display the spell if character has a spellbook and the spell in his book
{
CItem *spellBook = FindItemOfType( ourChar, IT_SPELLBOOK );
if( !ValidateObject( spellBook ))
{
canMake = false;
}
else
{
SI32 getCir = static_cast<SI32>( iItem.spell * .1 );
SI32 getSpell = iItem.spell - ( getCir * 10 ) + 1;
if( !Magic->CheckBook( getCir, getSpell - 1, spellBook ))
{
canMake = false;
}
}
}
if( canMake )
{
toSend.addCommand( util::format( "button %i %i %i %i 1 0 %i", xLoc - 40, yLoc, btnRight, btnRight + 1, ( *ourMenu.iIter )));
if( iItem.targId )
{
toSend.addCommand( util::format( "tilepic %i %i %i", xLoc - 20, yLoc, iItem.targId ));
}
toSend.addCommand( util::format( "text %i %i 35 %i", xLoc + 20, yLoc, textCounter++ ));
toSend.addText( iItem.name );
yLoc += 40;
++actualItems;
}
}
}
actualItems = 0;
for( ourMenu.mIter = ourMenu.menuEntries.begin(); ourMenu.mIter != ourMenu.menuEntries.end(); ++ourMenu.mIter )
{
if(( actualItems % 6 ) == 0 && actualItems != 0 )
{
xLoc += 180;
yLoc = 40;
}
smIter = skillMenus.find(( *ourMenu.mIter ));
if( smIter != skillMenus.end() )
{
CreateMenuEntry_st iMenu = smIter->second;
toSend.addCommand(util::format( "button %i %i %i %i 1 0 %i", xLoc - 40, yLoc, btnRight, btnRight + 1, CREATE_MENU_OFFSET + ( *ourMenu.mIter )));
if( iMenu.targId )
{
toSend.addCommand( util::format( "tilepic %i %i %i", xLoc - 20, yLoc, iMenu.targId) );
}
toSend.addCommand( util::format( "text %i %i 35 %i", xLoc + 20, yLoc, textCounter++) );
toSend.addText( iMenu.name );
yLoc += 40;
++actualItems;
}
}
toSend.Finalize();
s->Send( &toSend );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::HandleMakeMenu()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Handles the button pressed in the new make menu
//o------------------------------------------------------------------------------------------------o
void CSkills::HandleMakeMenu( CSocket *s, SI32 button, SI32 menu )
{
VALIDATESOCKET( s );
CChar *ourChar = s->CurrcharObj();
s->AddId( 0 );
std::map<UI16, CreateMenu_st>::const_iterator p = actualMenus.find( menu );
if( p == actualMenus.end() )
return;
CreateMenu_st ourMenu = p->second;
if( button >= CREATE_MENU_OFFSET ) // menu pressed
{
std::map<UI16, CreateMenuEntry_st>::const_iterator q = skillMenus.find( button-CREATE_MENU_OFFSET );
if( q == skillMenus.end() )
return;
NewMakeMenu( s, q->second.subMenu, 0 );
}
else // item to make
{
std::map<UI16, CreateEntry_st>::iterator r = itemsForMenus.find( button );
if( r == itemsForMenus.end() )
return;
MakeItem( r->second, ourChar, s, button );
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::FindItem()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Returns a handle to a CreateEntry_st based on an itemNum
//o------------------------------------------------------------------------------------------------o
CreateEntry_st *CSkills::FindItem( UI16 itemNum )
{
std::map<UI16, CreateEntry_st>::iterator r = itemsForMenus.find( itemNum );
if( r == itemsForMenus.end() )
return nullptr;
return &( r->second );
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::MakeItem()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Makes an item selected in the new make menu system
//o------------------------------------------------------------------------------------------------o
void CSkills::MakeItem( CreateEntry_st &toMake, CChar *player, CSocket *sock, UI16 itemEntry, UI16 resourceColour )
{
VALIDATESOCKET( sock );
if( !ValidateObject( player ))
return;
// Get potential tag with ID of a targeted sub-resource
UI16 targetedSubResourceId = 0;
TAGMAPOBJECT tempTagObj = player->GetTempTag( "targetedSubResourceId" );
if( tempTagObj.m_ObjectType == TAGMAP_TYPE_INT && tempTagObj.m_IntValue > 0 )
{
targetedSubResourceId = static_cast<UI16>( tempTagObj.m_IntValue );
}
std::vector<ResAmountPair_st>::const_iterator resCounter;
std::vector<ResSkillReq_st>::const_iterator sCounter;
ResAmountPair_st resEntry;
UI16 toDelete;
UI16 targColour;
UI16 targId;
UI32 targMoreVal;
bool canDelete = true;
//Moved resource-check to top of file to disallow gaining skill by attempting to
//craft items without having enough resources to do so :P
for( resCounter = toMake.resourceNeeded.begin(); resCounter != toMake.resourceNeeded.end(); ++resCounter )
{
resEntry = ( *resCounter );
toDelete = resEntry.amountNeeded;
targColour = resEntry.colour;
targMoreVal = resEntry.moreVal;
if( resCounter == toMake.resourceNeeded.begin() && resourceColour != 0 )
{
// If a specific colour was provided for the function as the intended colour of the material, use a material of that colour
targColour = resourceColour;
}
for( std::vector<UI16>::const_iterator idCounter = resEntry.idList.begin(); idCounter != resEntry.idList.end(); ++idCounter )
{
targId = ( *idCounter );
if( targetedSubResourceId == 0 || resCounter == toMake.resourceNeeded.begin() )
{
// Primary resource, or generic subresource
toDelete -= std::min( GetItemAmount( player, targId, targColour, targMoreVal, true ), static_cast<UI32>( toDelete ));
}
else
{
if( targetedSubResourceId > 0 && targId == targetedSubResourceId )
{
// Player specifically targeted a secondary resource
toDelete -= std::min( GetItemAmount( player, targId, targColour, targMoreVal, true ), static_cast<UI32>( toDelete ));
}
}
if( toDelete == 0 )
break;
}
if( toDelete > 0 )
{
canDelete = false;
}
}
if( !canDelete )
{
sock->SysMessage( 1651 ); // You have insufficient resources on hand to do that
return;
}
bool canMake = true;
// Loop through the skills listed as skill requirement for crafting the item
for( sCounter = toMake.skillReqs.begin(); sCounter != toMake.skillReqs.end(); ++sCounter )
{
if( player->SkillUsed(( *sCounter ).skillNumber ))
{
sock->SysMessage( 1650 ); // You are already using that skill
return;
}
if( sCounter == toMake.skillReqs.begin() )
{
// Only perform skill check for first and main skill in skill requirement
if( !CheckSkill( player, ( *sCounter ).skillNumber, ( *sCounter ).minSkill, ( *sCounter ).maxSkill, true ))
{
canMake = false;
break;
}
}
else
{
// For supporting skills, we only check if player has more than or equal to the minimum skill required
if( player->GetSkill(( *sCounter ).skillNumber ) < ( *sCounter ).minSkill )
{
canMake = false;
break;
}
}
}
if( !canMake )
{
// delete anywhere up to half of the resources needed
if( toMake.soundPlayed )
{
Effects->PlaySound( sock, toMake.soundPlayed, true );
}
sock->SysMessage( 984 ); // You fail to create the item.
}
else
{
// Store temp tag on player with colour of item to craft
TAGMAPOBJECT tagObject;
tagObject.m_Destroy = false;
tagObject.m_StringValue = "";
tagObject.m_IntValue = resourceColour;
tagObject.m_ObjectType = TAGMAP_TYPE_INT;
player->SetTempTag( "craftItemColor", tagObject );
for( sCounter = toMake.skillReqs.begin(); sCounter != toMake.skillReqs.end(); ++sCounter )
{
player->SkillUsed( true, ( *sCounter ).skillNumber );
}
Effects->TempEffect( player, player, 41, toMake.delay, itemEntry, 0 );
if( toMake.soundPlayed )
{
if( toMake.delay > 300 )
{
for( SI16 i = 0; i < ( toMake.delay / 300 ); ++i )
{
Effects->TempEffect( player, player, 42, 300 * i, toMake.soundPlayed, 0 );
}
}
}
}
if( !canMake || !canDelete )
{
// Trigger onMakeItem() JS event for character who tried to craft item, even though they failed
std::vector<UI16> scriptTriggers = player->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
toExecute->OnMakeItem( sock, player, nullptr, itemEntry );
}
}
}
for( resCounter = toMake.resourceNeeded.begin(); resCounter != toMake.resourceNeeded.end(); ++resCounter )
{
resEntry = ( *resCounter );
toDelete = resEntry.amountNeeded;
if( !canMake )
{
toDelete = RandomNum( 0, std::max( 1, toDelete / 2 ));
}
targColour = resEntry.colour;
targMoreVal = resEntry.moreVal;
if( resCounter == toMake.resourceNeeded.begin() && resourceColour != 0 )
{
// If a specific colour was provided for the function as the intended colour of the material, use a material of that colour
targColour = resourceColour;
}
for( std::vector<UI16>::const_iterator idCounter = resEntry.idList.begin(); idCounter != resEntry.idList.end(); ++idCounter )
{
targId = ( *idCounter );
if( targetedSubResourceId == 0 || resCounter == toMake.resourceNeeded.begin() )
{
// Primary resource, or generic subresource
toDelete -= DeleteItemAmount( player, toDelete, targId, targColour, targMoreVal );
}
else
{
if( targetedSubResourceId > 0 && targId == targetedSubResourceId )
{
// Player specifically targeted a secondary resource
toDelete -= DeleteItemAmount( player, toDelete, targId, targColour, targMoreVal );
}
}
if( toDelete == 0 )
break;
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::RepairMetal()
//| Date - October 16, 2000
//| Modified - November 13, 2001 - changed to a metal repair function
//o------------------------------------------------------------------------------------------------o
//| Purpose - Repair armor and weapons.
//o------------------------------------------------------------------------------------------------o
void CSkills::RepairMetal( CSocket *s )
{
SI16 minSkill = 999, maxSkill = 1000;
CChar *mChar = s->CurrcharObj();
// Check if item used to initialize target cursor is still within range
CItem *tempObj = static_cast<CItem *>( s->TempObj() );
s->TempObj( nullptr );
if( ValidateObject( tempObj ))
{
if( tempObj->IsHeldOnCursor() || !CheckItemRange( mChar, tempObj ))
{
s->SysMessage( 400 ); // That is too far away!
return;
}
}
CItem *j = CalcItemObjFromSer( s->GetDWord( 7 ));
if( !ValidateObject( j ) || !j->IsMetalType() )
{
s->SysMessage( 986 ); // You cannot repair that.
return;
}
if( j->IsHeldOnCursor() || !CheckItemRange( mChar, j ))
{
s->SysMessage( 400 ); // That is too far away!
return;
}
if( j->GetHP() < j->GetMaxHP() )
{
if( j->GetResist( PHYSICAL ) > 0 )
{
// Items with > 12 def would have impossible skill req's, with this equation
if( j->GetResist( PHYSICAL ) <= 12 )
{
// Minimum Skill = 61.0 + Defense - 1 / 3 * 100 (0-3 = 61.0, 4-6 = 71.0, ect)
minSkill = ( 610 + static_cast<SI32>(( j->GetResist( PHYSICAL ) - 1 ) / 3 ) * 100 );
// Maximum Skill = 84.9 + Defense - 1 / 3 * 50 (0-3 = 84.9, 4-6 = 89.9, ect)
maxSkill = ( 849 + static_cast<SI32>(( j->GetResist( PHYSICAL ) - 1 ) / 3 ) * 50 );
}
}
else if( j->GetHiDamage() > 0 )
{
SI32 offset = ( j->GetLoDamage() + j->GetHiDamage() ) / 2;
// Items with > 25 Avg Damage would have impossible skill req's, with this equation
if( offset <= 25 )
{
// Minimum Skill = 51.0 + Avg Damage - 1 / 5 * 100 (0-5 = 51.0, 6-10 = 61.0, ect)
minSkill = ( 510 + static_cast<SI32>(( offset - 1 ) / 5 ) * 100 );
// Maximum Skill = 79.9 + Avg Damage - 1 / 5 * 50 (0-5 = 79.9, 6-10 = 84.9, ect)
maxSkill = ( 799 + static_cast<SI32>(( offset - 1 ) / 5 ) * 50 );
}
}
else
{
s->SysMessage( 987 ); // You cannot repair this item.
return;
}
}
else
{
s->SysMessage( 988 ); // That item is already fully repaired.
return;
}
if( CheckSkill( mChar, BLACKSMITHING, minSkill, maxSkill ))
{
j->SetHP( j->GetMaxHP() );
s->SysMessage( 989 ); // You repair the item successfully.
Effects->PlaySound( s, 0x002A, true );
}
else
{
s->SysMessage( 990 ); // You fail to repair the item.
}
}
void CallGuards( CChar *mChar, CChar *targChar );
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::Snooping()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Called when player snoops another PC/NPC's or a tamed animals pack
//o------------------------------------------------------------------------------------------------o
void CSkills::Snooping( CSocket *s, CChar *target, CItem *pack )
{
CChar *mChar = s->CurrcharObj();
CSocket *tSock = target->GetSocket();
std::vector<UI16> scriptTriggers = mChar->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
// If script returns false, prevent hard-code and other scripts with event from running
if( toExecute->OnSnoopAttempt( target, mChar ) == 0 )
{
return;
}
}
}
if( target->GetCommandLevel() > mChar->GetCommandLevel() )
{
s->SysMessage( 991 ); // You failed to peek into that container.
if( tSock != nullptr )
{
tSock->SysMessage( 992, mChar->GetNameRequest( target ).c_str() ); // %s is snooping you!
}
return;
}
// Check if snooping is allowed in player's AND target's regions
if( mChar->GetRegion()->IsSafeZone() || target->GetRegion()->IsSafeZone() )
{
// Target is in a safe zone where all aggressive actions are forbidden, disallow
s->SysMessage( 1799 ); // Hostile actions are not permitted in this safe area.
return;
}
scriptTriggers.clear();
scriptTriggers.shrink_to_fit();
scriptTriggers = target->GetScriptTriggers();
if( CheckSkill( mChar, SNOOPING, 0, 1000 ))
{
s->OpenPack( pack );
s->SysMessage( 993 ); // You successfully peek into that container.
for( auto scriptTrig : scriptTriggers )
{
cScript *successSnoop = JSMapping->GetScript( scriptTrig );
if( successSnoop != nullptr )
{
// If script returns true/1, prevent other scripts with event from running
if( successSnoop->OnSnooped( target, mChar, true ) == 1 )
{
break;
}
}
}
}
else
{
bool doNormal = true;
for( auto scriptTrig : scriptTriggers )
{
cScript *failSnoop = JSMapping->GetScript( scriptTrig );
if( failSnoop != nullptr )
{
// If script returns true/1, prevent hard code and other scripts with event from running
if( failSnoop->OnSnooped( target, mChar, true ) == 1 )
{
doNormal = false;
}
}
}
if( doNormal )
{
s->SysMessage( 991 ); // You failed to peek into that container.
if( target->IsNpc() )
{
if( cwmWorldState->creatures[target->GetId()].IsHuman() && target->GetNpcAiType() != AI_EVIL && target->GetNpcAiType() != AI_EVIL_CASTER && target->GetNpcAiType() != AI_HEALER_E )
{
// 994=Art thou attempting to disturb my privacy?
// 995=Stop that!
// 996=Be aware I am going to call the guards!
target->TextMessage( s, 994 + RandomNum( 0, 2 ), TALK, false );
if( cwmWorldState->ServerData()->SnoopIsCrime() )
{
if( RandomNum( 0, 1 ) == 1 && mChar->IsCriminal() ) // 50% chance of calling guards, on second time
{
CallGuards( target, mChar );
}
}
}
else
{
UI16 toPlay = cwmWorldState->creatures[target->GetId()].GetSound( SND_IDLE );
if( toPlay != 0x00 )
{
Effects->PlaySound( target, toPlay );
}
}
}
else if( tSock != nullptr )
{
tSock->SysMessage( 997, mChar->GetNameRequest( target ).c_str() ); // You notice %s trying to peek into your pack!
}
if( cwmWorldState->ServerData()->SnoopIsCrime() )
{
MakeCriminal( mChar );
}
if( mChar->GetKarma() <= 1000 )
{
mChar->SetKarma( mChar->GetKarma() - 10 );
s->SysMessage( 998 ); // You've lost a small bit of karma.
}
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - CSkills::MakeNecroReg()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Create reagents for necromancer spells
//o------------------------------------------------------------------------------------------------o
void CSkills::MakeNecroReg( CSocket *nSocket, CItem *nItem, UI16 itemId )
{
CItem *iItem = nullptr;
CChar *iCharId = nSocket->CurrcharObj();
if( itemId >= 0x1B11 && itemId <= 0x1B1C ) // Make bone powder.
{
iCharId->TextMessage( nullptr, 741, EMOTE, 1, iCharId->GetName().c_str() ); // %s is grinding some bone into powder
Effects->TempEffect( iCharId, iCharId, 9, 0, 0, 0 );
Effects->TempEffect( iCharId, iCharId, 9, 0, 3, 0 );
Effects->TempEffect( iCharId, iCharId, 9, 0, 6, 0 );
Effects->TempEffect( iCharId, iCharId, 9, 0, 9, 0 );
iItem = Items->CreateItem( nSocket, iCharId, 0x0F8F, 1, 0, OT_ITEM, true );
if( iItem == nullptr )
return;
iItem->SetName( "bone powder" );
iItem->SetTempVar( CITV_MOREX, 666 );
iItem->SetTempVar( CITV_MORE, 1, 1 ); // this will fill more with info to tell difference between ash and bone
nItem->Delete();
}
if( itemId == 0x0E24 ) // Make vial of blood.
{
if( nItem->GetTempVar( CITV_MORE, 1 ) == 1 )
{
iItem = Items->CreateItem( nSocket, iCharId, 0x0F82, 1, 0, OT_ITEM, true );
if( iItem == nullptr )
return;
iItem->SetBuyValue( 15 );
iItem->SetTempVar( CITV_MOREX, 666 );
}
else
{
iItem = Items->CreateItem( nSocket, iCharId, 0x0F7D, 1, 0, OT_ITEM, true );
if( iItem == nullptr )
return;
iItem->SetBuyValue( 10 );
iItem->SetTempVar( CITV_MOREX, 666 );
}
nItem->IncAmount( -1 );
}
}