uox3/source/skills.cpp
2022-06-08 10:38:16 -04:00

3148 lines
109 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 "weight.h"
#include <algorithm>
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 - SI32 CalcRankAvg( CChar *player, createEntry& skillMake )
//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& skillMake )
{
// If rank system is not enabled, assume perfectly crafted item each time
if( !cwmWorldState->ServerData()->RankSystemStatus() )
return 10;
R32 rankSum = 0;
// R32 successSum = 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 = (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 = (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 (SI32)(rankSum / skillMake.skillReqs.size());
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void ApplyRank( CSocket *s, CItem *c, UI08 rank, UI08 maxrank )
//| 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( (SI16)( ( rank * c->GetLoDamage() ) / 10 ) );
if( c->GetHiDamage() > 0 )
c->SetHiDamage( (SI16)( ( rank * c->GetHiDamage() ) / 10 ) );
if( c->GetResist( PHYSICAL ) > 0 )
c->SetResist( (UI16)( ( rank * c->GetResist( PHYSICAL ) ) / 10 ), PHYSICAL );
if( c->GetHP() > 0 )
c->SetHP( (SI16)( ( rank * c->GetHP() ) / 10 ) );
if( c->GetMaxHP() > 0 )
c->SetMaxHP( (SI16)( ( rank * c->GetMaxHP() ) / 10 ) );
if( c->GetBuyValue() > 0 )
c->SetBuyValue( (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 );
else if( tempRank < 1 )
s->sysmessage( 784 );
}
else
c->SetRank( rank );
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void RegenerateOre( SI16 grX, SI16 grY )
//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 - void MakeOre( UI08 Region, CChar *actor, CSocket *s )
//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 *toFind = nullptr;
const miningData *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( 0, 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 );
if( oreItem->GetCont() != nullptr )
mSock.sysmessage( 982, oreName.c_str() );
}
oreFound = true;
break;
}
}
}
if( !oreFound )
{
if( getSkill >= 850 )
{
Items->CreateRandomItem( &mSock, "digginggems" );
mSock.sysmessage( 983 );
}
else
mSock.sysmessage( 1772 );
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool MineCheck( CSocket& mSock, CChar *mChar, SI16 targetX, SI16 targetY, SI08 targetZ, UI08 targetID1, UI08 targetID2 )
//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
{
CStaticIterator msi( targetX, targetY, mChar->WorldNumber() );
for( Static_st *stat = msi.Next(); stat != nullptr; stat = msi.Next() )
{
CTile& tile = Map->SeekTile( stat->itemid );
if( targetZ == stat->zoff && ( !strcmp( tile.Name(), "rock" ) || !strcmp( tile.Name(), "mountain" ) || !strcmp( tile.Name(), "cave" ) ) )
return true;
}
}
else // or it could be a map only
{
// manually calculating the ID's if a maptype
const map_st map1 = Map->SeekMap( targetX, targetY, mChar->WorldNumber() );
CLand& land = Map->SeekLand( map1.id );
if( !strcmp( "rock", land.Name() ) || !strcmp( land.Name(), "mountain" ) || !strcmp( land.Name(), "cave" ) )
return true;
}
}
break;
case 2: // need to modify scripts to support this!
return true;
default:
mSock.sysmessage( 800 );
return true;
}
return false;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void cSkills::Mine( CSocket *s )
//| 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 - void cSkills::GraveDig( CSocket *s )
//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 );
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 );
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 );
else
s->sysmessage( 808 );
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 );
}
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 );
}
break;
case 6:
spawnCreature = Npcs->CreateRandomNPC( "weakundeadlist" ); // Low level Undead - Random
s->sysmessage( 806 );
break;
case 8:
Items->CreateRandomItem( s, "diggingweapons" );
s->sysmessage( 811 );
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 );
break;
default:
if( RandomNum( 0, 1 ) )
{
Items->CreateRandomItem( s, "diggingbones" ); // Random Bones
s->sysmessage( 812 );
}
else // found an empty grave
s->sysmessage( 813 );
break;
}
if( spawnCreature != nullptr )
spawnCreature->SetLocation( nCharID );
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void cSkills::SmeltOre( CSocket *s );
//| 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 ) ); // Let's find our forge
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 *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( oldstrutil::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 - UI16 cSkills::CalculatePetControlChance( CChar *mChar, CChar *Npc )
//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 - bool cSkills::CheckSkill( CChar *s, UI08 sk, SI16 lowSkill, SI16 highSkill, bool isCraftSkill )
//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 )
{
R32 chanceSkillSuccess = 0;
if( ( highSkill - lowSkill ) <= 0 || !ValidateObject( s ) || s->GetSkill( sk ) <= lowSkill )
return false;
if( s->IsDead() )
{
CSocket *sSock = s->GetSocket();
sSock->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 )
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 );
}
CSocket *mSock = s->GetSocket();
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
skillCheck = true;
return skillCheck;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void cSkills::HandleSkillChange( CChar *c, UI16 sk )
//| 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 - void cSkills::SkillUse( CSocket *s, UI08 x )
//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->target( 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;
}
}
// 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 - void cSkills::RandomSteal( CSocket *s )
//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 - void cSkills::StealingTarget( CSocket *s )
//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 - void cSkills::doStealing( CSocket *s, CChar *mChar, CChar *npc, CItem *item )
//| 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 + ( (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 ) )
criminal( 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 - SI16 calcStealDiff( CChar *c, CItem *i )
//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 = (SI32)((c->GetSkill( STEALING ) * 2) + 100); // GM thieves can steal up to 21 stones, newbie only 1 stone
if( calcDiff > itemWeight )
stealDiff = (SI16)std::max( std::min( ((SI32)((itemWeight + 9) / 20) * 10), 990 ), 0 );
break;
}
return stealDiff;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void cSkills::Tracking( CSocket *s, SI32 selection )
//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 - void cSkills::CreateTrackingMenu( CSocket *s, UI16 m )
//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 ") + oldstrutil::number( m );
ScriptSection *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;
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;
}
else if( m == ( 3 + TRACKINGMENUOFFSET ) )
{
creatureType = CT_PERSON;
type = 889;
}
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){
GenericList< CChar * > *regChars = MapArea->GetCharList();
regChars->Push();
for( CChar *tempChar = regChars->First(); !regChars->Finished() && MaxTrackingTargets < cwmWorldState->ServerData()->TrackingMaxTargets(); tempChar = regChars->Next() )
{
if( !ValidateObject( tempChar ) || tempChar->GetInstanceID() != mChar->GetInstanceID() )
continue;
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;
switch( Movement->Direction( mChar, tempChar->GetX(), tempChar->GetY() ) )
{
case NORTH: dirMessage = 890; break;
case NORTHWEST: dirMessage = 891; break;
case NORTHEAST: dirMessage = 892; break;
case SOUTH: dirMessage = 893; break;
case SOUTHWEST: dirMessage = 894; break;
case SOUTHEAST: dirMessage = 895; break;
case WEST: dirMessage = 896; break;
case EAST: dirMessage = 897; break;
default: dirMessage = 898; break;
}
std::string tempName = getNpcDictName( tempChar );
line = tempName + " " + Dictionary->GetEntry( dirMessage, mLang );
toSend.AddResponse( cwmWorldState->creatures[id].Icon(), 0, line );
}
}
}
regChars->Pop();
}
}
if( MaxTrackingTargets == 0 )
{
s->sysmessage( type );
return;
}
toSend.Finalize();
s->Send( &toSend );
}
void HandleCommonGump( CSocket *mSock, ScriptSection *gumpScript, UI16 gumpIndex );
//o-----------------------------------------------------------------------------------------------o
//| Function - void cSkills::TrackingMenu( CSocket *s, UI16 gmindex )
//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 ") + oldstrutil::number( gmindex );
ScriptSection *TrackStuff = FileLookup->FindEntry( sect, menus_def );
if( TrackStuff == nullptr )
{
return;
}
HandleCommonGump( s, TrackStuff, gmindex );
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void cSkills::Track( CChar *i )
//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 - void cSkills::updateSkillLevel( CChar *c, UI08 s )
//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 - void cSkills::Persecute( CSocket *s )
//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 - void cSkills::Smith( CSocket *s )
//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 *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 - void cSkills::AnvilTarget( CSocket *s, CItem& item, SI16 oreType )
//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 *oreType )
{
VALIDATESOCKET( s );
CChar *mChar = s->CurrcharObj();
for (auto &MapArea : MapRegion->PopulateList( mChar )){
if( MapArea){
GenericList< CItem * > *regItems = MapArea->GetItemList();
regItems->Push();
for( CItem *tempItem = regItems->First(); !regItems->Finished(); tempItem = regItems->Next() )
{
if( !ValidateObject( tempItem ) || tempItem->GetInstanceID() != mChar->GetInstanceID() )
continue;
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.
regItems->Pop();
return;
}
NewMakeMenu( s, oreType->makemenu, BLACKSMITHING );
regItems->Pop();
return;
}
}
}
regItems->Pop();
}
}
s->sysmessage( 981 ); // The anvil is too far away.
}
//o-----------------------------------------------------------------------------------------------o
//| Function - cSkills::cSkills( void )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Default constructor
//o-----------------------------------------------------------------------------------------------o
cSkills::cSkills( void )
{
ores.resize( 0 );
}
cSkills::~cSkills( void )
{
ores.resize( 0 );
skillMenus.clear();
itemsForMenus.clear();
actualMenus.clear();
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool cSkills::LoadMiningData( void )
//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
ScriptSection *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;
ScriptSection *individualOre = nullptr;
for (auto &oreName : oreNameList){
individualOre = FileLookup->FindEntry( oreName, skills_def );
if( individualOre != nullptr )
{
miningData toAdd;
toAdd.colour = 0;
toAdd.makemenu = 0;
toAdd.minSkill = 0;
toAdd.name = oreName;
toAdd.oreName = oreName;
toAdd.oreChance = 0;
for( tag = individualOre->First(); !individualOre->AtEnd(); tag = individualOre->Next() )
{
UTag = oldstrutil::upper( tag );
data = individualOre->GrabData();
data = oldstrutil::trim( oldstrutil::removeTrailing( 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;
default:
Console << "Unknown mining tag " << tag << " with data " << data << " in SECTION " << oreName << myendl;
break;
}
}
ores.push_back( toAdd );
}
}
}
}
return rvalue;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void cSkills::Load( void )
//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 - size_t cSkills::GetNumberOfOres( void )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Returns number of custom ores
//o-----------------------------------------------------------------------------------------------o
size_t cSkills::GetNumberOfOres( void )
{
return ores.size();
}
//o-----------------------------------------------------------------------------------------------o
//| Function - miningData *cSkills::GetOre( size_t number )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Returns a handle to the data about the ore
//o-----------------------------------------------------------------------------------------------o
miningData *cSkills::GetOre( size_t number )
{
if( number >= ores.size() )
return nullptr;
return &ores[number];
}
//o-----------------------------------------------------------------------------------------------o
//| Function - miningData *cSkills::FindOre( string name )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Returns a handle to the data about the ore based on it's name
//o-----------------------------------------------------------------------------------------------o
miningData *cSkills::FindOre( std::string const &name )
{
std::vector< miningData >::iterator oreIter;
for( oreIter = ores.begin(); oreIter != ores.end(); ++oreIter )
{
if( (*oreIter).oreName == name )
return &(*oreIter);
}
return nullptr;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - miningData *cSkills::FindOre( UI16 colour)
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Find ore color
//o-----------------------------------------------------------------------------------------------o
miningData *cSkills::FindOre( UI16 const &colour )
{
std::vector< miningData >::iterator oreIter;
for( oreIter = ores.begin(); oreIter != ores.end(); ++oreIter )
{
if( (*oreIter).colour == colour )
return &(*oreIter);
}
return nullptr;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void cSkills::LoadCreateMenus( void )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Load Create menus from the create DFNs
//o-----------------------------------------------------------------------------------------------o
void cSkills::LoadCreateMenus( void )
{
actualMenus.clear();
skillMenus.clear();
itemsForMenus.clear();
std::string tag, data, UTag;
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( ScriptSection *toSearch = ourScript->FirstEntry(); toSearch != nullptr; toSearch = ourScript->NextEntry() )
{
std::string eName = ourScript->EntryName();
if( "SUBMENU" == eName.substr( 0, 7 ) ) // is it a menu? (not really SUB, just to avoid picking up MAKEMENUs)
{
eName = oldstrutil::trim( oldstrutil::removeTrailing( eName, "//" ));
auto ssecs = oldstrutil::sections( eName, " " );
ourEntry = static_cast<UI16>(std::stoul(oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 0));
for( tag = toSearch->First(); !toSearch->AtEnd(); tag = toSearch->Next() )
{
UTag = oldstrutil::upper( tag );
data = toSearch->GrabData();
data = oldstrutil::trim( oldstrutil::removeTrailing( 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 = oldstrutil::sections( eName, " " );
ourEntry = static_cast<UI16>(std::stoul(oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 0));
createEntry tmpEntry;
tmpEntry.minRank = 0;
tmpEntry.maxRank = 10;
tmpEntry.colour = 0;
tmpEntry.targID = 1;
tmpEntry.soundPlayed = 0;
for( tag = toSearch->First(); !toSearch->AtEnd(); tag = toSearch->Next() )
{
UTag = oldstrutil::upper( tag );
data = toSearch->GrabData();
data = oldstrutil::trim( oldstrutil::removeTrailing( 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 tmpResource;
auto ssecs = oldstrutil::sections(data," ");
if( ssecs.size() > 1 )
{
if( ssecs.size() == 4 )
{
tmpResource.moreVal = static_cast<UI32>(std::stoul(oldstrutil::trim( oldstrutil::removeTrailing( ssecs[3], "//" )), nullptr, 0));
}
if( ssecs.size() >= 3 )
{
tmpResource.colour = static_cast<UI16>(std::stoul(oldstrutil::trim( oldstrutil::removeTrailing( ssecs[2], "//" )), nullptr, 0));
}
if( ssecs.size() >= 2 )
{
tmpResource.amountNeeded = static_cast<UI16>(std::stoul(oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 0));
}
}
std::string resType = "RESOURCE " + oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" ));
ScriptSection *resList = FileLookup->FindEntry( resType, create_def );
if( resList != nullptr )
{
std::string resData;
for( std::string resTag = resList->First(); !resList->AtEnd(); resTag = resList->Next() )
{
resData = resList->GrabData();
tmpResource.idList.push_back( static_cast<UI16>(std::stoul(resData, nullptr, 0 )));
}
}
else
{
tmpResource.idList.push_back( static_cast<UI16>(std::stoul(oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )) );
}
tmpEntry.resourceNeeded.push_back( tmpResource );
}
else if( UTag == "SKILL" )
{
resSkillReq tmpResource;
auto ssecs = oldstrutil::sections( 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(oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 0 ));
}
else
{
tmpResource.minSkill = static_cast<UI16>(std::stoul(oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 0 ));
tmpResource.maxSkill = static_cast<UI16>(std::stoul(oldstrutil::trim( oldstrutil::removeTrailing( ssecs[2], "//" )), nullptr, 0 ));
}
tmpResource.skillNumber = static_cast<UI16>(std::stoul(oldstrutil::trim( oldstrutil::removeTrailing( 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 = oldstrutil::sections( eName, " " );
ourEntry = static_cast<UI16>(std::stoul(oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 0));
for( tag = toSearch->First(); !toSearch->AtEnd(); tag = toSearch->Next() )
{
UTag = oldstrutil::upper( tag );
data = toSearch->GrabData();
data = oldstrutil::trim( oldstrutil::removeTrailing( 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 - bool cSkills::AdvanceSkill( CChar *s, UI08 sk, bool skillUsed )
//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( 0, 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 - SI08 cSkills::FindSkillPoint( UI08 sk, SI32 value )
//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 - void cSkills::AdvanceStats( CChar *s, UI08 sk, bool skillsuccess )
//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 ServStatCap = 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, (SI32)( (R32)ActualStat[nCount] / (R32)pRace->Skill( StatCount ) * 100 ) );
chanceStatGain = (SI16)(((R32)cwmWorldState->skill[StatCount-1].advancement[modifiedStatLevel].success / 100) * ((R32)( (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] <= (SI32)( (R32)ActualStat[nCount] / (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( ServStatCap-10, ServStatCap ) )
{
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:
// 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:
// 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:
// 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) <= ServStatCap )
{
// 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 - void cSkills::NewMakeMenu( CSocket *s, SI32 menu, UI08 skill )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - New make menu system, based on create DFNs
//o-----------------------------------------------------------------------------------------------o
void cSkills::NewMakeMenu( CSocket *s, SI32 menu, UI08 skill )
{
VALIDATESOCKET( s );
CChar *ourChar = s->CurrcharObj();
s->AddID( menu );
std::map< UI16, createMenu >::const_iterator p = actualMenus.find( menu );
if( p == actualMenus.end() )
return;
UI16 background = cwmWorldState->ServerData()->BackgroundPic();
UI16 btnCancel = cwmWorldState->ServerData()->ButtonCancel();
UI16 btnLeft = cwmWorldState->ServerData()->ButtonLeft();
UI16 btnRight = cwmWorldState->ServerData()->ButtonRight();
CPSendGumpMenu toSend;
toSend.UserID( INVALIDSERIAL );
toSend.GumpID( 12 );
createMenu ourMenu = p->second;
UI32 textCounter = 0;
ScriptSection *GumpHeader = FileLookup->FindEntry( "ADDMENU HEADER", misc_def );
if( GumpHeader == nullptr )
{
toSend.addCommand( oldstrutil::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( oldstrutil::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 = oldstrutil::upper( tag );
data = GumpHeader->GrabData();
data = oldstrutil::trim( oldstrutil::removeTrailing( 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 );
}
}
ScriptSection *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 >::iterator imIter;
std::map< UI16, createMenuEntry >::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 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 = (SI32)( iItem.spell * .1 );
SI32 getSpell = iItem.spell - ( getCir * 10 ) + 1;
if( !Magic->CheckBook( getCir, getSpell - 1, spellBook ) )
canMake = false;
}
}
if( canMake )
{
toSend.addCommand( oldstrutil::format("button %i %i %i %i 1 0 %i", xLoc - 40, yLoc, btnRight, btnRight + 1, (*ourMenu.iIter)) );
if( iItem.targID )
toSend.addCommand( oldstrutil::format("tilepic %i %i %i", xLoc - 20, yLoc, iItem.targID ));
toSend.addCommand( oldstrutil::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 iMenu = smIter->second;
toSend.addCommand(oldstrutil::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( oldstrutil::format("tilepic %i %i %i", xLoc - 20, yLoc, iMenu.targID) );
toSend.addCommand( oldstrutil::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 - void cSkills::HandleMakeMenu( CSocket *s, SI32 button, SI32 menu )
//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 >::const_iterator p = actualMenus.find( menu );
if( p == actualMenus.end() )
return;
createMenu ourMenu = p->second;
if( button >= CREATE_MENU_OFFSET ) // menu pressed
{
std::map< UI16, createMenuEntry >::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 >::iterator r = itemsForMenus.find( button );
if( r == itemsForMenus.end() )
return;
MakeItem( r->second, ourChar, s, button );
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - createEntry *cSkills::FindItem( UI16 itemNum )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Returns a handle to a createEntry based on an itemNum
//o-----------------------------------------------------------------------------------------------o
createEntry *cSkills::FindItem( UI16 itemNum )
{
std::map< UI16, createEntry >::iterator r = itemsForMenus.find( itemNum );
if( r == itemsForMenus.end() )
return nullptr;
return &(r->second);
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void cSkills::MakeItem( createEntry &toMake, CChar *player, CSocket *sock, UI16 resourceColour )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Makes an item selected in the new make menu system
//o-----------------------------------------------------------------------------------------------o
void cSkills::MakeItem( createEntry &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 >::const_iterator resCounter;
std::vector< resSkillReq >::const_iterator sCounter;
resAmountPair 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, 0 );
}
}
}
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 - void cSkills::RepairMetal( CSocket *s )
//| 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 );
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 + (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 + (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 + (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 + (SI32)((offset-1) / 5) * 50);
}
}
else
{
s->sysmessage( 987 );
return;
}
}
else
{
s->sysmessage( 988 );
return;
}
if( CheckSkill( mChar, BLACKSMITHING, minSkill, maxSkill ) )
{
j->SetHP( j->GetMaxHP() );
s->sysmessage( 989 );
Effects->PlaySound( s, 0x002A, true );
}
else
s->sysmessage( 990 );
}
void callGuards( CChar *mChar, CChar *targChar );
//o-----------------------------------------------------------------------------------------------o
//| Function - void cSkills::Snooping( CSocket *s, CChar *target, CItem *pack )
//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_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() )
criminal( mChar );
if( mChar->GetKarma() <= 1000 )
{
mChar->SetKarma( mChar->GetKarma() - 10 );
s->sysmessage( 998 ); // You've lost a small bit of karma.
}
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void cSkills::MakeNecroReg( CSocket *nSocket, CItem *nItem, UI16 itemID )
//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 );
}
}