Obtaining a target

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!
Saint
UOX3 Apprentice
Posts: 162
Joined: Sun Feb 11, 2007 6:05 pm
Has thanked: 0
Been thanked: 0

Obtaining a target

Post by Saint »

I was wondering if there are any functions within the source that can be used to get the "next" valid target for someone to attack? I'd like to also know if there are functions for obtaining a target at a specific location.
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 »

It depends on what you mean by next valid target, I guess. We make no assumptions server side for that. Try AreaCharacterFunction. It'll end up iterating more than you want (there's no way to abort early), but it can do something for you.

Code: Select all

AreaCharacterFunction( funcName, srcChar, distRange, optSocket );
Function name is the call back function to call into, srcChar is the character at the centre, distRange is how close the characters are, and optSocket is an optional socket parameter. Your callback function needs a signature like:

Code: Select all

function myCallback( srcChar, trgChar, optionalSocket );
So to call that, you'd have:

Code: Select all

function myCallback( srcChar, trgChar, optionalSocket )
{
  trgChar.Emote( "I suck" );
}

function someSourceEvent()
{
    AreaCharacterFunction( "myCallback", mySourceChar, 15 );
}
That'll cause all characters within 15 paces of the srcChar to emote "I suck".
Saint
UOX3 Apprentice
Posts: 162
Joined: Sun Feb 11, 2007 6:05 pm
Has thanked: 0
Been thanked: 0

Post by Saint »

I was thinking more of something like the next target macro. Player presses the button and the next closes target to him gets chosen. Is there a way to ask the client to use the next target and then return that data to the server. Basically I'm designing a command where when used it immediately picks the next target if one hasn't been selected already.

In reality, what I'd want to eventually do though is target the character that is standing 1 tile in front of the character.

There are 5 different possible locations around the player for the server to search for a target. So I'm going to try to come up with 5 different formulas for getting the coordinates to those locations by simply plugging in a players direction at any given time.

Looks something like this, P being the player:

4P5
213
Saint
UOX3 Apprentice
Posts: 162
Joined: Sun Feb 11, 2007 6:05 pm
Has thanked: 0
Been thanked: 0

Post by Saint »

Alright well here's a better way to explain it:


The key, is speed.


I was having trouble coming up with a universal formula so I decide to create a sort of system that uses some arrays.


In theory, lets say we have two characters, A and B. A and B are on the same screen. The goal is to find the location of B by altering the coordinates of A. These alterations are called offsets. Example, we may decide B's location is located at (X-3), (Y+2) where X and Y represent A's location.

In reality, I'm only interested in obtaining targets very close to A. Maximum offsets of 1 or -1.

A picture of what I mean:
Image

What I am doing right nows is creating two arrays of size 9. 9 because there are 8 directions. Slot 0 represents no direction or an irrelevant direction (probably gonna be used for archery) and Slots 1 through 8 represent the corresponding directions a player can face. This array can be used to find the location directly infront of where the player is facing, and this is where we search for the first priority target

The arrays looking something like this:

X offsets {0, 1, 1 ,1 , 0, -1, -1 ,-1 , 0}
Y offsets {0, -1, 0, 1, 1, 1, 0, -1, -1 }

Clearly, we can see why. If a player were to be facing in say direction 2, we would go to array entry 2 (0 based) in each array, and return with an X offset of 1, and Y offset of 0. And sure enough, (X+1), (Y+0) is the location of the spot infront of a player facing direction 2 standing at coordinates (X,Y)

Now I have created a new multi dimensional array, which basically stores all the possible attack checks. it looks something like this

0 1 2 3 4 5 6 7 8
0 2 3 4 5 6 7 8 1
0 8 1 2 3 4 5 6 7
0 3 4 5 6 7 8 1 2
0 7 8 1 2 3 4 5 6

Basically, whenever a player initiates an attack, we will look at the array entry corresponding to his direction. So if the player was looking in direction 2 we would look at entry[2][index], where index starts as 0.

Then the number in that entry is used with those other offset arrays to determine the offset coordinates to search for a target at. If a target is not found at that location, index is incremented by 1. This means we now get the number at entry[2][1], and use the same procedure to find out if anyone is at those coordinates. This continues until index is no longer less than 5, at which point, a target was not found, and the player swings harmlessly into the air.


Now, my question is, with many players swinging their weapons all at the same time, will there be a lot of processing lag if I use this method? All these functions wanting to access all these arrays at the same time? Will this lag? Should I look for ANOTHER way to do what I want to DO?

Questions, comments?

But first things first, can characters even be retrieved from a location by coordinates?
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 »

OK, not really sure what you're wanting as an end result. Or what particular higher level problem you're trying to solve, I should say.

I can suggest two things.

1) We need to expose CheckDirection() from boats.cpp to the SE engine. This'll solve your location offsets, you'd just have to call it telling it source location, and the direction you'd want to travel.

2) There's no real simple way to get the characters at a particular location, except AreaCharacterFunction as I suggested earlier, with a range of 1. Unfortunately, that's not going to completely do it (it'll show all characters around you on all sides), but combined with 1), you could isolate the characters you really care about. And you don't have a way to abort easily either, which would be a pain.

As such, we don't have a GetCharacterAtXYZWorld() function, mostly because players can stack there. The question then becomes, which do you want? Just the first match? Do you want to be able to access all? And so on, as you can imagine. For a stack list, AreaCharacterFunction's probably your best bet (though I suspect we might need a similar function to deal with a source location, not just a character location).

Lots of processing I'd suggest is best avoided in JS if you can help it. It won't be as fast as native speed, and the stuff you're looking for is generally there anyway. So it might be more efficient to expose what you're looking for that way, not to mention making it more reusable elsewhere, with easier readability on the script side.

Would such a thing be of use?
Saint
UOX3 Apprentice
Posts: 162
Joined: Sun Feb 11, 2007 6:05 pm
Has thanked: 0
Been thanked: 0

Post by Saint »

Maarc wrote:OK, not really sure what you're wanting as an end result. Or what particular higher level problem you're trying to solve, I should say.

I can suggest two things.

1) We need to expose CheckDirection() from boats.cpp to the SE engine. This'll solve your location offsets, you'd just have to call it telling it source location, and the direction you'd want to travel.

2) There's no real simple way to get the characters at a particular location, except AreaCharacterFunction as I suggested earlier, with a range of 1. Unfortunately, that's not going to completely do it (it'll show all characters around you on all sides), but combined with 1), you could isolate the characters you really care about. And you don't have a way to abort easily either, which would be a pain.

As such, we don't have a GetCharacterAtXYZWorld() function, mostly because players can stack there. The question then becomes, which do you want? Just the first match? Do you want to be able to access all? And so on, as you can imagine. For a stack list, AreaCharacterFunction's probably your best bet (though I suspect we might need a similar function to deal with a source location, not just a character location).

Lots of processing I'd suggest is best avoided in JS if you can help it. It won't be as fast as native speed, and the stuff you're looking for is generally there anyway. So it might be more efficient to expose what you're looking for that way, not to mention making it more reusable elsewhere, with easier readability on the script side.

Would such a thing be of use?
The end result, should be real time combat. I've gone to line 219 of speech.cpp and created a new check for an [attack command, which basically takes priority over all commands. When this command is called, a new method will be activated in combat.cpp where a player will swing his weapon and damage another character (of course right now all it does is PlaySwingAnimations). If the target is stacked on top of another character only the first character in the stack gets hit. If no character is found at the tile infront of the player, then the next tiles are checked in order. If a target IS found, appropriate damage will be dealt to that target and the player will turn to face the target. If no target is ever found, the player simply swings into the air and does no damage to anyone.

The reason I want to avoid JS for this is because [attack will be called very very often, and possibly by multiple players simultaneously.

How do explosion potions work? If you toss one on the ground and it explodes doesn't it damage anyone standing on top of it? If I can't retrieve a character at a location the possibly couldn't I still just apply damage to anyone at a location? Essentially, it would be like creating a series of "traps" around the attacker everytime he swings.

Also, how about the Area character function, but used with a range of 0? If that is valid, couldn't we input the location to search at as the coordinates we want to search?
Saint
UOX3 Apprentice
Posts: 162
Joined: Sun Feb 11, 2007 6:05 pm
Has thanked: 0
Been thanked: 0

Post by Saint »

Can a SOCKLIST be modified to include Chars that are not players?
giwo
Developer
Posts: 1780
Joined: Fri Jun 18, 2004 4:17 pm
Location: California
Has thanked: 0
Been thanked: 0

Post by giwo »

Nope, only connected Players have sockets, a NULL value would cause a crash.

Having said that, generally the socklist was used because some part of the function required sending data to the socket (or pulling info from it). If a socket is not needed, CHARLIST should be used, which would allow any character object (socket or not).
Scott
Saint
UOX3 Apprentice
Posts: 162
Joined: Sun Feb 11, 2007 6:05 pm
Has thanked: 0
Been thanked: 0

Post by Saint »

giwo wrote:Nope, only connected Players have sockets, a NULL value would cause a crash.

Having said that, generally the socklist was used because some part of the function required sending data to the socket (or pulling info from it). If a socket is not needed, CHARLIST should be used, which would allow any character object (socket or not).

So then that means FindNearbyPlayers can be duplicated into a FindNearbyChars function that returns a CHARLIST?



Another question, does that function Push ALL the current connections to the server for evaluation? Or only the ones in the player's general area?

Code: Select all

SOCKLIST FindNearbyPlayers( CBaseObject *myObj, UI16 distance )
{
	SOCKLIST nearbyChars;
	Network->PushConn();
	for( CSocket *mSock = Network->FirstSocket(); !Network->FinishedSockets(); mSock = Network->NextSocket() )
	{
		if( objInRange( mSock->CurrcharObj(), myObj, distance ) )
			nearbyChars.push_back( mSock );
	}
	Network->PopConn();
	return nearbyChars;
}


And further more, what function in the source would allow for similar functionality as PushConn(), except pushing all the characters (including non-sockets)?
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 code you have posted here is a C++ function, not something that returns an array to the JS engine. There's a big difference in that regard. An NPC equivalent in C++ is essentially the same as AreaCharacterFunction, except we use the guts of the function, and don't return a list at all, but just do the actual search/recognition in situ. There's plenty of examples of that around, just look for the region stuff. eg

Code: Select all

	REGIONLIST nearbyRegions	= MapRegion->PopulateList( srcObject );
	for( REGIONLIST_CITERATOR rIter = nearbyRegions.begin(); rIter != nearbyRegions.end(); ++rIter )
	{
		CMapRegion *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() )
		{
		}
		regChars->Pop();
	}
But that function is C++, not JS, as is FindNearbyPlayers. You're wanting something that can return an array of characters or equivalent to JS, which isn't quite the same thing, nor is it simple.
Saint
UOX3 Apprentice
Posts: 162
Joined: Sun Feb 11, 2007 6:05 pm
Has thanked: 0
Been thanked: 0

Post by Saint »

I guess it's just an all around bad idea.

I'm thinking now of just making it so while in warmode ever click a player makes translates to an attack. If he clicked nothing the character just swings uselessly into the air. If he clicks a target the proper checks are made and then damage gets applied. Kind of like Diablo. I imagine players can later make their own macros anyway.

Is there a place somewhere in the source that handles player clicking? I'm gonna read through pcmanage
giwo
Developer
Posts: 1780
Joined: Fri Jun 18, 2004 4:17 pm
Location: California
Has thanked: 0
Been thanked: 0

Post by giwo »

cPlayerAction.cpp

The functions are named after what event the handle, basically.
Scott
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 »

Not necessarily a bad idea, more a case of coming up with a robust, reuseable solution that can be used in many ways, not just a one off for a specific purpose. You never did (that I can see) tell me whether the tweaks to AreaCharacterFunction would have been enough for you (the early outs, and update to work off a location, not just a character). That seems like a perfectly reasonable solution to me.
Saint
UOX3 Apprentice
Posts: 162
Joined: Sun Feb 11, 2007 6:05 pm
Has thanked: 0
Been thanked: 0

Post by Saint »

Maarc wrote:Not necessarily a bad idea, more a case of coming up with a robust, reuseable solution that can be used in many ways, not just a one off for a specific purpose. You never did (that I can see) tell me whether the tweaks to AreaCharacterFunction would have been enough for you (the early outs, and update to work off a location, not just a character). That seems like a perfectly reasonable solution to me.
I thought AreaCharacterFunction was a javascripted function?

But eitherway, if I could modify AreaCharacterFunction so that it would focus only on one specific location, obtain the first character it finds at that location, and return that character in order to be treated as a target by combat commands, then it would be of use. Of course, other concerns are being kept in mind. This code will be running a lot.

Speaking of which, is there a way to benchmark different functions? Running the function thousands of times and then finding out how long all those executions take?
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 »

ACF is definitely javascripted, but I thought you were looking for it in a fashion where it could be used from. If you're looking at it from code? Then use what I suggested earlier, with the nearbyRegions stuff. Just bung your logic in the for loop, and verify that it's the target location you want. Not much call for that sort of function to be created, because we rarely want to look at one specific tile. It's not that much work, really :) And hell, you don't have to populate the list based on a source object. Pass an x, y and worldNumber to it instead, and bob's your uncle.

As far as timing goes ... Store a call to time(), do your logic however many times you want to do it, then call time() again. Use difftime() to calculate the difference. That may not be the highest resolution timer, but it'll be a good first pass.

A testing framework for speed I haven't done yet, unfortunately. It's been on my list of things to evaluate for a while.
Saint
UOX3 Apprentice
Posts: 162
Joined: Sun Feb 11, 2007 6:05 pm
Has thanked: 0
Been thanked: 0

Post by Saint »

Maarc wrote:ACF is definitely javascripted, but I thought you were looking for it in a fashion where it could be used from. If you're looking at it from code? Then use what I suggested earlier, with the nearbyRegions stuff. Just bung your logic in the for loop, and verify that it's the target location you want. Not much call for that sort of function to be created, because we rarely want to look at one specific tile. It's not that much work, really :) And hell, you don't have to populate the list based on a source object. Pass an x, y and worldNumber to it instead, and bob's your uncle.

As far as timing goes ... Store a call to time(), do your logic however many times you want to do it, then call time() again. Use difftime() to calculate the difference. That may not be the highest resolution timer, but it'll be a good first pass.

A testing framework for speed I haven't done yet, unfortunately. It's been on my list of things to evaluate for a while.
I see, so MapArea->GetCharList() returns all characters in a certain region right?

All I would need to do then is define a 1x1 micro region infront of the character on the fly, where basically X1 and Y1 would both be equal to X2 and Y2. The question is, how to define it? I've looked at the CMapRegion data type, didn't find anything useful. I saw a GetMapRegion command, but I think it's only for finding already pre-existing regions.
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 »

No, that's not what I said. You can't just define micro regions like that. Do something like this:

Code: Select all

REGIONLIST nearbyRegions   = MapRegion->PopulateList( srcObject ); 
   for( REGIONLIST_CITERATOR rIter = nearbyRegions.begin(); rIter != nearbyRegions.end(); ++rIter ) 
   { 
      CMapRegion *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( ValidateObject( tempChar ) )
           {
                if( tempChar->GetX() == targX && tempChar->GetY() == targY && tempChar->WorldNumber() == targWorld )
                {
                  // This is one of the character's I'm looking for, do something here
                }
           }
      } 
      regChars->Pop(); 
   } 
Just put your stuff into the stubbed if area. Not hard.
Saint
UOX3 Apprentice
Posts: 162
Joined: Sun Feb 11, 2007 6:05 pm
Has thanked: 0
Been thanked: 0

Post by Saint »

Maarc wrote:No, that's not what I said. You can't just define micro regions like that. Do something like this:

Code: Select all

REGIONLIST nearbyRegions   = MapRegion->PopulateList( srcObject ); 
   for( REGIONLIST_CITERATOR rIter = nearbyRegions.begin(); rIter != nearbyRegions.end(); ++rIter ) 
   { 
      CMapRegion *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( ValidateObject( tempChar ) )
           {
                if( tempChar->GetX() == targX && tempChar->GetY() == targY && tempChar->WorldNumber() == targWorld )
                {
                  // This is one of the character's I'm looking for, do something here
                }
           }
      } 
      regChars->Pop(); 
   } 
Just put your stuff into the stubbed if area. Not hard.

My concern lies with GetCharList. I see what this code does, but wouldn't this essentially get a list of EVERY character in the region? Even ones that aren't remotely close? What if the region being looked at is "The World". It would have to make a lot of comparisons to a lot of characters before finding one that is at the targetX and targetY. In fact, in the worst case it compares every single character and doesn't even find one at the target x and y.
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 »

OK, now we're wandering into the realms of user terminology, vs developmental terminology :)

There are regions and there are regions and then there are regions. Users only care about the regions that they define. And I'm guessing you're thinking of regions like this.

But in this instance, a REGIONLIST is something different altogether. We're talking about internal, fixed size, regions, for breaking the world up. So we don't have to evaluate the entire list of regions in the world, we don't have to iterate over absolutely everything. We can deal with immediacy. This regionlist is a much smaller chunk of the world. By this code, you'll get a 3x3 chunk of regions. Each region is 32 tiles wide, 128 tiles high. In reality, we may want to balance that out over time. It DOES affect world saving (hence, the tiled filenames).

Now, that regionlist will give you a 96 x 384 tile slice of the world. Only characters in those regions will be in the for loop. And if you compare on fixed location, like I suggest, well, it'll only give those that are there. You're right, maybe no one's there! Always possible. GetCharList returns a constant time ordered access list. If you randomly pop over, then you'll run into problems, possibly. But the way it's ordered there, no way jose, you'll be fine and dandy.

For what it's worth, such code like this gets executed probably dozens of times per simulation, which is ... what? 0.25 seconds or something? :) REGIONLIST and comparisons like this are everywhere, for items and chars.

Now, an evolution may want to include a distance indicator in the call to PopulateList. So we know how many regions we really have to return, not just fixed ones. And possibly tweak the sizes down. 32 x 32, or 64 x 64, are reasonable chunks of the world, I think.
Saint
UOX3 Apprentice
Posts: 162
Joined: Sun Feb 11, 2007 6:05 pm
Has thanked: 0
Been thanked: 0

Post by Saint »

How can I return a NULL target? I've tried using NULL but it gives an error because it's converting from an Int to a CChar
Post Reply