Dirty Hack: Criminal NPCs

Want to discuss changes to the UOX3 source code? Got a code-snippet you'd like to post? Anything related to coding/programming goes here!
Grimson
Developer
Posts: 802
Joined: Sat Jun 04, 2005 1:52 am
Location: Germany
Has thanked: 0
Been thanked: 0

Dirty Hack: Criminal NPCs

Post by Grimson »

After trying to turn NPCs into criminals for my thief script: viewtopic.php?t=472 I noticed that UOX doesn't really support it. So I made this dirty hack:

Open uox3.cpp and find the "void checkNPC( CChar& mChar, bool checkAI, bool doRestock )" function. In this function add:

Code: Select all

	if( mChar.IsCriminal() && mChar.GetTimer( tCHAR_CRIMFLAG ) && ( mChar.GetTimer( tCHAR_CRIMFLAG ) <= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() ) )
	{
		mChar.SetTimer( tCHAR_CRIMFLAG, 0 );
		setcharflag( &mChar );
	}
	if( mChar.GetTimer( tCHAR_MURDERRATE ) && ( mChar.GetTimer( tCHAR_MURDERRATE ) <= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() ) )
	{
		mChar.SetTimer( tCHAR_MURDERRATE, 0 );
		if( mChar.GetKills() )
			mChar.SetKills( static_cast<SI16>( mChar.GetKills() - 1 ) );
		if( mChar.GetKills() )
			mChar.SetTimer( tCHAR_MURDERRATE, cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_MURDERDECAY ) );
		else
			mChar.SetTimer( tCHAR_MURDERRATE, 0 );
		if( mChar.GetKills() == cwmWorldState->ServerData()->RepMaxKills() )
		setcharflag( &mChar );
	}
or a criminal NPC won't become a normal NPC anymore.

Then find the "void setcharflag( CChar *c )" function, and there the following:

Code: Select all

	if( !c->IsNpc() )
	{
		if( c->GetKills() > cwmWorldState->ServerData()->RepMaxKills() )
			c->SetFlagRed();
		else if( c->GetTimer( tCHAR_CRIMFLAG ) != 0 )
			c->SetFlagGray();
		else
			c->SetFlagBlue();
	} 
	else 
	{
		switch( c->GetNPCAiType() )
		{
			case aiEVIL:		// Evil
			case aiHEALER_E:	// Evil healer
			case aiCHAOTIC:		// BS/EV
				c->SetFlagRed();
				break;
			case aiHEALER_G:	// Good Healer
			case aiPLAYERVENDOR:// Player Vendor
			case aiGUARD:		// Guard
			case aiBANKER:		// Banker
			case aiFIGHTER:		// Fighter
				c->SetFlagBlue();
				break;
			default:
				if( c->GetID() == 0x0190 || c->GetID() == 0x0191 )
				{
					c->SetFlagBlue();
					break;
				}
				else if( cwmWorldState->ServerData()->CombatAnimalsGuarded() && cwmWorldState->creatures[c->GetID()].IsAnimal() )
				{
					if( c->GetRegion()->IsGuarded() )	// in a guarded region, with guarded animals, animals == blue
						c->SetFlagBlue();
					else
						c->SetFlagGray();
				}
				else	// if it's not a human form, and animal's aren't guarded, then they're gray
					c->SetFlagGray();
				if( ValidateObject( c->GetOwnerObj() ) && c->IsTamed() )
				{
					CChar *i = c->GetOwnerObj();
					if( ValidateObject( i ) )
						c->SetFlag( i->GetFlag() );
					else
						c->SetFlagBlue();
					if( c->IsInnocent() && !cwmWorldState->ServerData()->CombatAnimalsGuarded() )
						c->SetFlagBlue();
				}
				break;

		}
       }
modify it so that it looks like that:

Code: Select all

	if( !c->IsNpc() )
	{
		if( c->GetKills() > cwmWorldState->ServerData()->RepMaxKills() )
			c->SetFlagRed();
		else if( c->GetTimer( tCHAR_CRIMFLAG ) != 0 )
			c->SetFlagGray();
		else if( c->IsNeutral() )
			return;
		else
			c->SetFlagBlue();
	} 
	else 
	{
		if( c->GetKills() > cwmWorldState->ServerData()->RepMaxKills() )
			c->SetFlagRed();
		else if( c->GetTimer( tCHAR_CRIMFLAG ) != 0 )
			c->SetFlagGray();
		else {
			switch( c->GetNPCAiType() )
			{
				case aiEVIL:		// Evil
				case aiHEALER_E:	// Evil healer
				case aiCHAOTIC:		// BS/EV
					if( !c->IsNeutral() )
						c->SetFlagRed();
					break;
				case aiHEALER_G:	// Good Healer
				case aiPLAYERVENDOR:// Player Vendor
				case aiGUARD:		// Guard
				case aiBANKER:		// Banker
				case aiFIGHTER:		// Fighter
					if( !c->IsNeutral() )
						c->SetFlagBlue();
				default:
					if( c->GetID() == 0x0190 || c->GetID() == 0x0191 )
					{
						if( !c->IsNeutral() )
							c->SetFlagBlue();
						break;
					}
					else if( cwmWorldState->ServerData()->CombatAnimalsGuarded() && cwmWorldState->creatures[c->GetID()].IsAnimal() )
					{
						if( c->GetRegion()->IsGuarded() )	// in a guarded region, with guarded animals, animals == blue
							c->SetFlagBlue();
						else
							c->SetFlagNeutral();
					}
					else	// if it's not a human form, and animal's aren't guarded, then they're gray
						c->SetFlagNeutral();
					if( ValidateObject( c->GetOwnerObj() ) && c->IsTamed() )
					{
						CChar *i = c->GetOwnerObj();
						if( ValidateObject( i ) )
							c->SetFlag( i->GetFlag() );
						else
							c->SetFlagBlue();
						if( c->IsInnocent() && !cwmWorldState->ServerData()->CombatAnimalsGuarded() )
							c->SetFlagBlue();
					}
					break;
			}
		}
	}
Now you can turn NPCs via "myNPC.criminal = true" into a criminal using the script engine, just like you can do with a player.

It's still not complete, for example calling a guard near such an NPC does nothing. I'm also not shure if it may break some stuff as I just came up with it and haven't tested it much. But at least it's a start. Any improvements from the devs are welcome ;).

Edit:
Improved the above code to support npc murderers, and also changed the setcharflag function a bit.

To let guards and fighters work on npc criminals or murders make the following changes:

finf the "void callGuards( CChar *mChar )" function in uox3.cpp and there this code:

Code: Select all

		if( !tempChar->IsDead() && ( tempChar->IsCriminal() || tempChar->IsMurderer() ) )
		{
			SI16 aiType = tempChar->GetNPCAiType();
			if( !tempChar->IsNpc() || ( aiType == aiEVIL || aiType == aiCHAOTIC || aiType == aiHEALER_E ) )
			{
				if( charInRange( tempChar, mChar ) )
				{
					Combat->SpawnGuard( mChar, tempChar, tempChar->GetX(), tempChar->GetY(), tempChar->GetZ() );
					break;
				}
			}
		}
change it this way:

Code: Select all

		if( !tempChar->IsDead() && ( tempChar->IsCriminal() || tempChar->IsMurderer() ) )
		{
				if( charInRange( tempChar, mChar ) )
				{
					Combat->SpawnGuard( mChar, tempChar, tempChar->GetX(), tempChar->GetY(), tempChar->GetZ() );
					break;
				}
		}
find "void HandleGuardAI( CChar& mChar )" in ai.cpp and change it this way:

Code: Select all

void HandleGuardAI( CChar& mChar )
{
	if( !mChar.IsAtWar() )
	{
		REGIONLIST nearbyRegions = MapRegion->PopulateList( &mChar );
		for( REGIONLIST_CITERATOR rIter = nearbyRegions.begin(); rIter != nearbyRegions.end(); ++rIter )
		{
			SubRegion *MapArea = (*rIter);
			if( MapArea == NULL )	// no valid region
				continue;
			CDataList< CChar * > *regChars = MapArea->GetCharList();
			regChars->Push();
			for( CChar *tempChar = regChars->First(); !regChars->Finished(); tempChar = regChars->Next() )
			{
				if( isValidAttackTarget( mChar, tempChar ) )
				{
					if( !tempChar->IsDead() && ( tempChar->IsCriminal() || tempChar->IsMurderer() ) )
					{
						Combat->AttackTarget( &mChar, tempChar );
						mChar.talkAll( 313, true );
						regChars->Pop();
						return;
					}
				}
			}
			regChars->Pop();
		}
	}
}
find "void HandleFighterAI( CChar& mChar )" and change it this way:

Code: Select all

void HandleFighterAI( CChar& mChar )
{
	if( !mChar.IsAtWar() )
	{
		REGIONLIST nearbyRegions = MapRegion->PopulateList( &mChar );
		for( REGIONLIST_CITERATOR rIter = nearbyRegions.begin(); rIter != nearbyRegions.end(); ++rIter )
		{
			SubRegion *MapArea = (*rIter);
			if( MapArea == NULL )	// no valid region
				continue;
			CDataList< CChar * > *regChars = MapArea->GetCharList();
			regChars->Push();
			for( CChar *tempChar = regChars->First(); !regChars->Finished(); tempChar = regChars->Next() )
			{
				if( isValidAttackTarget( mChar, tempChar ) )
				{
					if( !tempChar->IsDead() && ( tempChar->IsCriminal() || tempChar->IsMurderer() ) )
					{
						Combat->AttackTarget( &mChar, tempChar );
						regChars->Pop();
						return;
					}
				}
			}
			regChars->Pop();
		}
	}
}
Thanks to lingo for pointing me into the right direction.

Edit: Changes to support the neutral flag:
open cChar.h and find:

Code: Select all

	void		SetFlagRed( void );
	void		SetFlagBlue( void );
	void		SetFlagGray( void );
add this directly after:

Code: Select all

void		SetFlagNeutral( void );
find:

Code: Select all

	bool	IsMurderer( void ) const;
	bool	IsCriminal( void ) const;
	bool	IsInnocent( void ) const;
add this directly after:

Code: Select all

bool	IsNeutral(void) const;
now open cChar.cpp and find:

Code: Select all

		else
		{
			if( IsMurderer() )		// Murderer
				rFlag = 6;
			else if( IsCriminal() )	// Criminal
				rFlag = 3;
		}
		toSend.SetRepFlag( rFlag );
change it this way:

Code: Select all

		else
		{
			if( IsMurderer() )		// Murderer
				rFlag = 6;
			else if( IsCriminal() )	// Criminal
				rFlag = 4;
			else if( IsNeutral() )	// Neutral
				rFlag = 3;
		}
		toSend.SetRepFlag( rFlag );
find:

Code: Select all

bool CChar::IsInnocent( void ) const
{
	return ( (GetFlag()&0x04) == 0x04 );
}
add this directly after:

Code: Select all

//o---------------------------------------------------------------------------o
//|   Function    -  bool IsNeutral( void ) const
//|   Date        -  18 July 2005
//|   Programmer  -  Grimson
//o---------------------------------------------------------------------------o
//|   Purpose     -  Returns true if the character is neutral
//o---------------------------------------------------------------------------o
bool CChar::IsNeutral( void ) const
{
	return ( (GetFlag()&0x20) == 0x20 );
}
find:

Code: Select all

void CChar::SetFlagGray( void )
{
	flag |= 0x02;
	flag &= 0x1B;
}
add this directly after:

Code: Select all

//o---------------------------------------------------------------------------o
//|   Function    -  void SetFlagNeutral( void )
//|   Date        -  18th July, 2005
//|   Programmer  -  Grimson
//o---------------------------------------------------------------------------o
//|   Purpose     -  Updates the character's flag to reflect neutrality
//o---------------------------------------------------------------------------o
void CChar::SetFlagNeutral( void )
{
	flag |= 0x20;
	flag &= 0x38;
}
open movement.cpp and find the "FlagColour( CChar *a, CChar *b )" function, there:

Code: Select all

	if( a->IsMurderer() )
		return 6;		// If a bad, show as red.
	else if( a->IsInnocent() )
		return 1;		// If a good, show as blue.
	else
		return 3;		// grey
change it this way:

Code: Select all

	if( a->IsMurderer() )
		return 6;		// If a bad, show as red.
	else if( a->IsInnocent() )
		return 1;		// If a good, show as blue.
	else if( a->IsCriminal() )
		return 4;		// If a criminal show grey
	else
		return 3;		// neutral grey
open cSocket.cpp and find the "CSocket::GetFlagColour( CChar *src, CChar *trg )" function, there:

Code: Select all

	else if( trg->IsCriminal()  )
		retVal = 0x03B2;//grey
and change it this way:

Code: Select all

	else if( trg->IsCriminal() || trg->IsNeutral() )
		retVal = 0x03B2;//grey
Edit: Changed the setcharflag function again, so that neutrality can override some of the flags, removed the change to the "flag &= 0xXX;" lines, as they were wrong and corrected the "flag &= 0x3B;" to "flag &= 0x38;" in setFlagNeutral.

And here now the code for the scripting implementation:
open UOXJSPropertyEnums.h and find:

Code: Select all

	CCP_CRIMINAL,
	CCP_MURDERER,
	CCP_INNOCENT,
	CCP_MURDERCOUNT,
add this directly after:

Code: Select all

	CCP_NEUTRAL,
open UOXJSPropertySpecs.h and find:

Code: Select all

	{ "criminal",		CCP_CRIMINAL,		JSPROP_ENUMANDPERM },
	{ "murderer",		CCP_MURDERER,		JSPROP_ENUMPERMRO  },
	{ "innocent",		CCP_INNOCENT,		JSPROP_ENUMANDPERM },
	{ "murdercount",	CCP_MURDERCOUNT,	JSPROP_ENUMANDPERM },
add this directly after

Code: Select all

	{ "neutral",		CCP_NEUTRAL,		JSPROP_ENUMANDPERM },
open UOXJSPropertyFuncs.cpp and find:

Code: Select all

				case CCP_CRIMINAL:		*vp = BOOLEAN_TO_JSVAL( gPriv->IsCriminal() );	break;
				case CCP_MURDERER:		*vp = BOOLEAN_TO_JSVAL( gPriv->IsMurderer() );	break;
				case CCP_INNOCENT:		*vp = BOOLEAN_TO_JSVAL( gPriv->IsInnocent() );	break;
				case CCP_MURDERCOUNT:	*vp = INT_TO_JSVAL( gPriv->GetKills() );		break;
add this directly after:

Code: Select all

				case CCP_NEUTRAL:		*vp = BOOLEAN_TO_JSVAL( gPriv->IsNeutral() );	break;
find:

Code: Select all

				case CCP_MURDERCOUNT:
					gPriv->SetKills( (SI16)encaps.toInt() );
					setcharflag( gPriv );
					break;
add this directly after:

Code: Select all

				case CCP_NEUTRAL:
					if( encaps.toBool() ) {
						gPriv->SetTimer( tCHAR_CRIMFLAG, 0 );
						gPriv->SetFlagNeutral();
					}
					else
					{
						gPriv->SetTimer( tCHAR_CRIMFLAG, 0 );
						gPriv->SetFlagBlue();
						setcharflag( gPriv );
					}
					break;
Now every char as the neutral property which can be used to get/set the neutral status via the script engine.
Last edited by Grimson on Mon Jul 18, 2005 10:26 am, edited 8 times in total.
lingo
UOX3 Novice
Posts: 55
Joined: Thu Jul 07, 2005 12:26 pm
Location: Taipei, Taiwan
Has thanked: 0
Been thanked: 0

Post by lingo »

Just browse through the file. I think the problem is you are changing the status of npc, and the npc did changed it status, but I don't think other AI,like guardAI, check on NPC's status. So it does nothing. we have to go and find what guardAI is doing. But it just a guess :)
lingo
UOX3 Novice
Posts: 55
Joined: Thu Jul 07, 2005 12:26 pm
Location: Taipei, Taiwan
Has thanked: 0
Been thanked: 0

Post by lingo »

Let check out the guard AI. On line 94 of ai.cpp

Code: Select all

					if( ( tempChar->IsCriminal() || tempChar->IsMurderer() ) && ( !tempChar->IsNpc() || 
						( tempChar->GetNPCAiType() == aiEVIL || tempChar->GetNPCAiType() == aiCHAOTIC || 
						tempChar->GetNPCAiType() == aiHEALER_E ) ) )
So a guard won't attack a NPC unless the NPC has GetNPCAiType() of aiEViL, aiCHAOTIC, or aiHEALER_E, IsCriminal() or IsMurderer() is not sufficient condition for a NPC. You have to set the NPCAiType() to the correct type first.
ShadowBranch
UOX3 Neophyte
Posts: 49
Joined: Sat Jan 01, 2005 7:13 am
Location: Hartsville, South Carolina, USA
Has thanked: 0
Been thanked: 0
Contact:

Post by ShadowBranch »

Grimson, the function for guards is simple and requires you to set an NPC with two settings.

If the player is a criminal, the guards will go after them.
If the NPC is set to criminal, the guards will not go after them.
However if you set the NPC to criminal and change the AI Type to Evil, then the guards will go after them.

The function has that built in as a checksum to make sure that it is an NPC and that if it is set to Murderer or Criminal and is Evil or chaotic or and Evil Healer, then attack.
--== Programming is not a job, it's a life! ==--
giwo
Developer
Posts: 1780
Joined: Fri Jun 18, 2004 4:17 pm
Location: California
Has thanked: 0
Been thanked: 0

Post by giwo »

the NPC AI shouldn't need to be evil for guards to go after him...

For one thing that would cause the NPC to be aggressive, attacking players and such.

I still haven't gotten a chance to look at this functionality, been absorbed in the messageboards rewrite, but I think much can be done to remove the dissimilarities between PC and NPC handling.
Scott
lingo
UOX3 Novice
Posts: 55
Joined: Thu Jul 07, 2005 12:26 pm
Location: Taipei, Taiwan
Has thanked: 0
Been thanked: 0

Post by lingo »

Grimson

can you change ai.cpp line 94 from

Code: Select all

               if( ( tempChar->IsCriminal() || tempChar->IsMurderer() ) && ( !tempChar->IsNpc() ||
                  ( tempChar->GetNPCAiType() == aiEVIL || tempChar->GetNPCAiType() == aiCHAOTIC ||
                  tempChar->GetNPCAiType() == aiHEALER_E ) ) )
to

Code: Select all

               if(  tempChar->IsCriminal() || tempChar->IsMurderer() ) 

See if there is any other sideeffect? I think the desirable behavior is from the new code.
Grimson
Developer
Posts: 802
Joined: Sat Jun 04, 2005 1:52 am
Location: Germany
Has thanked: 0
Been thanked: 0

Post by Grimson »

lingo wrote:Grimson

can you change ai.cpp line 94 from

Code: Select all

               if( ( tempChar->IsCriminal() || tempChar->IsMurderer() ) && ( !tempChar->IsNpc() ||
                  ( tempChar->GetNPCAiType() == aiEVIL || tempChar->GetNPCAiType() == aiCHAOTIC ||
                  tempChar->GetNPCAiType() == aiHEALER_E ) ) )
to

Code: Select all

               if(  tempChar->IsCriminal() || tempChar->IsMurderer() ) 

See if there is any other sideeffect? I think the desirable behavior is from the new code.
Thanks, that pointed me into the right direction, see my edit to the first post. I'm not shure if it really matters, but I also included the check wheter the char is dead or not into the if statement, so it now looks like this:

Code: Select all

if( !tempChar->IsDead() && ( tempChar->IsCriminal() || tempChar->IsMurderer() ) )
Grimson
Developer
Posts: 802
Joined: Sat Jun 04, 2005 1:52 am
Location: Germany
Has thanked: 0
Been thanked: 0

Post by Grimson »

I changed the if statement again, so guards and fighters won't attack harmless animals you also can't call the guards on them:

Code: Select all

if( !tempChar->IsDead() && ( tempChar->IsCriminal() || tempChar->IsMurderer() ) && (!cwmWorldState->creatures[tempChar->GetID()].IsAnimal() || ( tempChar->GetNPCAiType() == aiEVIL || tempChar->GetNPCAiType() == aiCHAOTIC || tempChar->GetNPCAiType() == aiHEALER_E ) ) )
giwo
Developer
Posts: 1780
Joined: Fri Jun 18, 2004 4:17 pm
Location: California
Has thanked: 0
Been thanked: 0

Post by giwo »

I think distinguishing animals as non-guardable would be a bad thing.


We should keep the system as general as we can, using flagging or AI to show them as evil.. As you have it, someone could never add scriptable "killer wolves" or an evil grizzly bear. (If one remembers way back to the original OSI servers, grizzly bears were actually evil, as were a few other "animals" including dire wolves).
Scott
Grimson
Developer
Posts: 802
Joined: Sat Jun 04, 2005 1:52 am
Location: Germany
Has thanked: 0
Been thanked: 0

Post by Grimson »

giwo wrote:I think distinguishing animals as non-guardable would be a bad thing.


We should keep the system as general as we can, using flagging or AI to show them as evil.. As you have it, someone could never add scriptable "killer wolves" or an evil grizzly bear. (If one remembers way back to the original OSI servers, grizzly bears were actually evil, as were a few other "animals" including dire wolves).
The Problem is harmless animals are flagged criminal by default, so you can kill them without becoming criminal yourself. So we would need a flag for NPCs that are neutral but can be killed without becoming criminal.
giwo
Developer
Posts: 1780
Joined: Fri Jun 18, 2004 4:17 pm
Location: California
Has thanked: 0
Been thanked: 0

Post by giwo »

Well, these are the acceptable flags for the client:

Code: Select all

Note: notoriety:
     0 = invalid/across server line
     1 = innocent (blue)
     2 = guilded/ally (green)
     3 = attackable but not criminal (gray)
     4 = criminal (gray)
     5 = enemy (orange)
     6 = murderer (red)
     7 = unknown use (translucent (like 0x4000 hue))
It seems as though OSI made provisions for such a creature, it has simply never been implemented....


Try poking around CChar.cpp having a look at GetFlag() / SetFlag() Perhaps we need a new flag (Neutral).
Scott
Grimson
Developer
Posts: 802
Joined: Sat Jun 04, 2005 1:52 am
Location: Germany
Has thanked: 0
Been thanked: 0

Post by Grimson »

giwo wrote:

Code: Select all

Note: notoriety:
     0 = invalid/across server line
     1 = innocent (blue)
     2 = guilded/ally (green)
     3 = attackable but not criminal (gray)
     4 = criminal (gray)
     5 = enemy (orange)
     6 = murderer (red)
     7 = unknown use (translucent (like 0x4000 hue))
How are those flags encoded, binary, left to right or right to left? How are they related to the way they are set withing uox3, for ex criminal is 0x02 and innocent is 0x04. Somehow I don't get it atm.
giwo
Developer
Posts: 1780
Joined: Fri Jun 18, 2004 4:17 pm
Location: California
Has thanked: 0
Been thanked: 0

Post by giwo »

The flags we set in UOX3 are different. No idea why they weren't made as a mirror image of the client flags, but they weren't

As for the client flag itself, it is basically self explanatory

Here is the code section where we send it to the client

Code: Select all

		UI08 rFlag = 1;
		GUILDRELATION guild = GuildSys->Compare( mCharObj, this );
		SI08 raceCmp		= Races->Compare( mCharObj, this );
		if( GetKills() > cwmWorldState->ServerData()->RepMaxKills() )
			rFlag = 6;
		else if( guild == GR_ALLY || guild == GR_SAME || raceCmp > 0 ) // Same guild (Green), racial ally, allied guild
			rFlag = 2;
		else if( guild == GR_WAR || raceCmp < 0 ) // Enemy guild.. set to orange
			rFlag = 5;
		else
		{
			if( IsMurderer() )		// Murderer
				rFlag = 6;
			else if( IsCriminal() )	// Criminal
				rFlag = 3;
		}
		toSend.SetRepFlag( rFlag );
Just keep in mind that as far as innocent/murderer/criminal/neutral, a character can only be one type at a time (thus why it does the bitmasking).
Scott
Grimson
Developer
Posts: 802
Joined: Sat Jun 04, 2005 1:52 am
Location: Germany
Has thanked: 0
Been thanked: 0

Post by Grimson »

giwo wrote: Here is the code section where we send it to the client

Code: Select all

		UI08 rFlag = 1;
		GUILDRELATION guild = GuildSys->Compare( mCharObj, this );
		SI08 raceCmp		= Races->Compare( mCharObj, this );
		if( GetKills() > cwmWorldState->ServerData()->RepMaxKills() )
			rFlag = 6;
		else if( guild == GR_ALLY || guild == GR_SAME || raceCmp > 0 ) // Same guild (Green), racial ally, allied guild
			rFlag = 2;
		else if( guild == GR_WAR || raceCmp < 0 ) // Enemy guild.. set to orange
			rFlag = 5;
		else
		{
			if( IsMurderer() )		// Murderer
				rFlag = 6;
			else if( IsCriminal() )	// Criminal
				rFlag = 3;
		}
		toSend.SetRepFlag( rFlag );
I found two more locations where they are sent, one in movement.cpp starting at line 138 called "UI08 FlagColour( CChar *a, CChar *b )" which seems similiar to the one above and one in cSocket.cpp starting at line 1834 called "COLOUR CSocket::GetFlagColour( CChar *src, CChar *trg )" which uses quite different return values. Well, I'm confused and it's late. But I'll try to play a bit around with them.
giwo
Developer
Posts: 1780
Joined: Fri Jun 18, 2004 4:17 pm
Location: California
Has thanked: 0
Been thanked: 0

Post by giwo »

*nod* I figured it would be used elsewhere as there are several different "update" packets depending on the situation.

The one you were looking at in CSocket is actually for text coloring (speech). There are, of course, quite a few more options for the color of text, thus it's not the same flags as it is with object flagging.
Scott
Grimson
Developer
Posts: 802
Joined: Sat Jun 04, 2005 1:52 am
Location: Germany
Has thanked: 0
Been thanked: 0

Post by Grimson »

I edited the first post to reflect the changes for the neutral flag. It seems to work so far.
Maarc
Developer
Posts: 576
Joined: Sat Mar 27, 2004 6:22 am
Location: Fleet, UK
Has thanked: 0
Been thanked: 0
Contact:

Post by Maarc »

The reason for the difference is because of the simultaneous nature of some of the flags.

A person can be a criminal and a murderer at the same time, for instance, and some of them (like orange/green) are viewer specific.
Grimson
Developer
Posts: 802
Joined: Sat Jun 04, 2005 1:52 am
Location: Germany
Has thanked: 0
Been thanked: 0

Post by Grimson »

I now added the neutral status to the jscript properties of the chars, changed the setcharflag function again and corrected the bitmasking. It seems to work so far.
Sydius
UOX3 Apprentice
Posts: 171
Joined: Thu Mar 25, 2004 3:22 am
Has thanked: 0
Been thanked: 0

Post by Sydius »

I definitely think that there should be a distinction between “criminal” and “legally attackable”…

All criminals are freely attackable, but not everything that is freely attackable is a criminal.

I think there should be a base flag that can be one of the following: evil, neutral, good. Evil would mean normally red, neutral normally grey, and good would be normally blue. Then have a separate flag that reads “criminal” which is either true or false.

That way, the only thing guards would have to care about is whether something is evil or a criminal.

I definitely do not think it should be based on AI type! I believe some AIs might potentially require different statuses (such as a thief that has not been exposed yet), or an animal that suddenly goes crazed, etc. – how an NPC thinks should not determine its status, it should be the actions of the NPC as a result of that thinking.
Grimson
Developer
Posts: 802
Joined: Sat Jun 04, 2005 1:52 am
Location: Germany
Has thanked: 0
Been thanked: 0

Post by Grimson »

Sydius wrote:I definitely think that there should be a distinction between “criminal” and “legally attackable”…

All criminals are freely attackable, but not everything that is freely attackable is a criminal.

I think there should be a base flag that can be one of the following: evil, neutral, good. Evil would mean normally red, neutral normally grey, and good would be normally blue. Then have a separate flag that reads “criminal” which is either true or false.

That way, the only thing guards would have to care about is whether something is evil or a criminal.
That's more or less what my code above does.
I definitely do not think it should be based on AI type! I believe some AIs might potentially require different statuses (such as a thief that has not been exposed yet), or an animal that suddenly goes crazed, etc. – how an NPC thinks should not determine its status, it should be the actions of the NPC as a result of that thinking.
For this you'll need to make the following changes:
First we need to make a change so that kills will also be counted on NPCs, or they won't become a murderer. Open combat.cpp and find the "CHandleCombat::Kill( CChar *mChar, CChar *ourTarg )" function. There find:

Code: Select all

// Add murder counts
and change the if-statement below to this:

Code: Select all

	if( mChar->DidAttackFirst() && WillResultInCriminal( mChar, ourTarg ) )
	{
		mChar->SetKills( mChar->GetKills() + 1 );
		mChar->SetTimer( tCHAR_MURDERRATE, cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_MURDERDECAY ) );
		if( !mChar->IsNpc() )
		{
			CSocket *aSock = calcSocketObjFromChar( mChar );
			if( aSock != NULL )
			{
				aSock->sysmessage( 314, mChar->GetKills() );
				if( mChar->GetKills() == cwmWorldState->ServerData()->RepMaxKills() + 1 )
					aSock->sysmessage( 315 );
			}
		}
		setcharflag( mChar );
	}
	if( !mChar->IsNpc() && !ourTarg->IsNpc() )
		Console.Log( Dictionary->GetEntry( 1617 ).c_str(), "PvP.log", ourTarg->GetName().c_str(), mChar->GetName().c_str() );
Now kills will be counted for NPCs too.

Now we need to completely change the "setcharflag( CChar *c )" function in uox3.cpp, so that it looks like this:

Code: Select all

void setcharflag( CChar *c )
{
	if( !ValidateObject( c ) )
		return;

	UI08 oldFlag = c->GetFlag();
	if( ValidateObject( c->GetOwnerObj() ) && c->IsTamed() )
	{
		CChar *i = c->GetOwnerObj();
		if( ValidateObject( i ) )
			c->SetFlag( i->GetFlag() );
		else
			c->SetFlagBlue();
		if( c->IsInnocent() && !cwmWorldState->ServerData()->CombatAnimalsGuarded() )
			c->SetFlagBlue();
	}
	else if ( cwmWorldState->creatures[c->GetID()].IsAnimal() )
	{
		if( c->GetKills() > cwmWorldState->ServerData()->RepMaxKills() )
			c->SetFlagRed();
		else if( c->GetTimer( tCHAR_CRIMFLAG ) != 0 )
			c->SetFlagGray();
		else if( cwmWorldState->ServerData()->CombatAnimalsGuarded() && c->GetRegion()->IsGuarded() )
			c->SetFlagBlue();
		else
			c->SetFlagNeutral();
	}
	else
	{
		if( c->GetKills() > cwmWorldState->ServerData()->RepMaxKills() )
			c->SetFlagRed();
		else if( c->GetTimer( tCHAR_CRIMFLAG ) != 0 )
			c->SetFlagGray();
		else if( !c->IsNeutral() )
			c->SetFlagBlue();
		else
			c->SetFlagNeutral();
	}
	UI08 newFlag = c->GetFlag();
	if( oldFlag != newFlag )
	{
		UI16 targTrig = c->GetScriptTrigger();
		cScript *toExecute = JSMapping->GetScript( targTrig );
		if( toExecute != NULL )
			toExecute->OnFlagChange( c, newFlag, oldFlag );
	}
}
But beware this is completely untested as I have just come up with it and don't have time for testing right now. So it might not work at all.
Last edited by Grimson on Thu Jul 21, 2005 5:01 pm, edited 1 time in total.
Post Reply