Game Development Community

Attacking from Code

by Robert Stewart · in RTS Starter Kit · 03/30/2005 (9:58 am) · 17 replies

Ok im trying to use sendattackevent but it crashes.

SimObjectId unitID = unit->getId();
		 SimObjectId targetID = target->getId();
                      
           Con::printf("Target %i", targetID); // Returns the proper ID 
		     Con::printf("Unit %i", unitID); // Returns the proper ID 

   client->sendAttackEvent((SimSet*)unitID, (GameBase*)targetID);

#1
04/03/2005 (6:50 pm)
Ok so it crashes with that, but when I try to use it from script instead of code it says it cant find client source or victim. here is my script code
%target = 1463.resolveObjectFromGhostIndex(2010);
     1463.sendAttackEvent(1463.selection, %target);
Probrally a different problem all together.
Also when I send a fake ID through sendattackevent in code not script, lt dosent accually attack but it at least dosent crash on me.
#2
04/04/2005 (6:32 am)
The sendAttackEvent code utilizes selections, not unit id's. There is no coded functionality for sending an attack based on a unit's id itself. Take a look at function serverCmdIssueAttack(%client, %targetID); for how it is called. In your code above, you are trying to cast a unit's id as a SimSet, and you cannot do that--it's not a set!

One note: sendAttackEvent is not completely robust--we've found situations where it would cause a segmentation fault of the unit got killed between the time the sendAttackEvent was posted, and when it was actually ready to be sent to the clients. Interestingly, I wound up disabling it completely, and while it's obviously not a great fix, it works "ok".

One suggestion: This isn't a great solution (for what you want, you really need to re-implement the entire attackEvent concept so that a single unit really can attack), but you can use a "selection group wrapper" concept prior to calling the sendAttackEvent from script:

In RTSUnit::createPlayer(), add this near the bottom:
// we have an issue with how the server processes an attack Event--the code basically
// requires a selection SimSet to be sent to the network code for the attack
// to be transmitted to all clients. 
// for Tier 1 server directed attacks, we are going to hack in a selection SimSet for
// each and every unit, containing just itself. This will allow us to use existing net code
// to handle a server directed attack.
   %player.selectionWrapper = new SimSet();
   %player.selectionWrapper.add(%player);


and then for the times when you want just a single unit to attack, under control of the server directly:
for (%i = 0; %i < ClientGroup.getCount(); %i++)
   {
      %cl = ClientGroup.getObject(%i);
      %cl.sendAttackEvent(%attacker.selectionWrapper, %target);
   }
#3
04/04/2005 (9:17 am)
I thought selections where just the ids of units,echo 1463.selection = 2014 = my unit id. Thanks for the help.
#4
04/04/2005 (9:29 am)
No, not at all. Selections are a set that is added to and subtracted from that includes all objects that the player currently has "selected" (via mouse clicks and click/drag selection techniques).

The echo that you get is the object ID for the SimSet that contains the current selected units of the client. If you do a .dump() on that object, you'll see it's contents (if any).
#5
04/04/2005 (9:55 am)
Ok well Im pretty sure I understand now, but what about the code been called in the engine. when I use the default rightclick to attack script it echos out Victim 1512 and Unit 1513 my units. Because of this printf i put in
void RTSConnection::sendAttackEvent(SimSet* attackSet, GameBase* victim)
{
	   Con::printf("Unit %i", attackSet);
	   Con::printf("Victim %i", victim);

This must be after it gets the units individually from the selection. but what I would like to know is why my code up top for the engine wont work.. any ideas? am I missing something .
#6
04/04/2005 (10:12 am)
As I said, what is crashing is this line (most likely):

client->sendAttackEvent((SimSet*)unitID, (GameBase*)targetID);

you cannot cast a unitID to a SimSet like that...the code farther down in the execution chain (down in sendAttackEvent() ) expects a REAL SimSet, that actually contains objects, not a cast of a single object pretending it's a SimSet.
#7
04/04/2005 (4:38 pm)
I was wondering how I would get the Selection SimSet into the engine, I tried this
SimSet * selection = client->getDataField(selection,0);
but it keeps giving me this error, I realize this is just because Im new to C++ but still help appreciated
SimObject::getDataField' : cannot convert parameter 1 from 'SimSet *' to 'StringTableEntry'
#8
04/05/2005 (5:40 am)
In script, it's %client.selection. In code, you'll have it passed to you as a parameter, normally called set, sometimes called attackSet in the various methods that use it.

Now, back to your original question--are you still trying to have the server script/code create an automatic attack for a single unit? If so, as I mentioned several posts up, you'll need to create a selectionWrapper for your units (each and every one of them) when the unit itself is created, and place the unit inside it's selectionWrapper.

Now, you can use this selectionWrapper (which is a set) just the same as you'd use %client.selection--pass it along the chain from script to the code, just like serverCmdIssueAttack (script function, commands.cs on the server side) does.
#9
04/05/2005 (5:15 pm)
Can you explain to me what you mean when you say pass the variable. I cant really call this code from script, because it will be called every second. That is why it is done in the engine, So I cant pass from script to code. Thanks for any help.
#10
04/05/2005 (8:45 pm)
Script calls for dozens of different object classes happen every single tick (33 milliseconds or so)...script is not slow, it's just slightly slower than source code.

When you supply arguments to a function call, you are "passing variables"...in other words, populating information for the method to use.

RTSConnection::sendAttackEvent(SimSet* attackSet, GameBase* victim)

--attackSet and victim are variables that are passed to the method sendAttackEvent from whatever calls that method. In this case, it's almost always script in stock RTS, but that isn't -required-.
#11
04/06/2005 (4:31 pm)
Ok fine what im trying to do here is implement a Attack on Sight feature. So im trying to put a sendattackevent in the vismanager code. So I now have a new sendattackevent thing for single units. I create a SimSet when the unit gets created, Then when the guy is in range he fires on target->getDataBlock()))->mGroup. Is this a good setup? Also im having trouble creating and using a simset. With something like mVision its done by adding stuff to the RTSUnit, like Addfield and pack/unpack stuff like that. How is this done with SimSets?
#12
04/06/2005 (6:02 pm)
What is target->getDataBlock()))->mGroup?

He should just fire on target (assuming that target is the npc closest to him in range that is an enemy).

I personally did the simset creation and the attack call in script, with the underlying detection code and call in processTick, and all I had to do was to set (again, in script):

function RTSUnit::issueSingleAttack(%this, %target)
{
//   for (%i = 0; %i < ClientGroup.getCount(); %i++)
//   {
//      %cl = ClientGroup.getObject(%i);
//      %cl.sendAttackEvent(%this.selectionWrapper, %target);
//   }

  
// echo("RTSUnit::issueSingleAttack--setting (" @ %attacker @ ") to attack (" @ %target @ ")");
   
   %this.setAimObject(%target);
}

Note carefully that I originally used what you first asked about: sendAttackEvent(), but it turns out that there is a nasty bug deep in the code where if a unit dies between a sendAttackEvent being posted as a NetEvent, but before the NetEvent is distributed to the clients, your server will crash. Since I haven't had time to track this bug down exactly yet (I did try!), I simply set the attacker's (%this, in this example) aimObject to the %target and let the rest of the code do it's job.
#13
04/06/2005 (6:05 pm)
Follow-up on your question about SimSets in code:

It's extremely simple to create a simset code side (just do a find in files for simset and you'll see dozens to hundreds of examples).

It's extremely important however to know when you should be passing information to the clients via pack/unpack, and when you shouldn't. This is server side only code, and the information isn't ever anything the client needs to "know about", so you shouldn't be passing it down to them at all.
#14
04/07/2005 (4:21 pm)
I added your code for the single attack script, sorry for not understanding your posts aswell as I should. So this should work? or is this not right. I just put this Con::executef(unitID, 1, "issueSingleAttack", targetID); in VisManager::processServer() and it crashes. unitID is my units id..Thanks for the help.
#15
04/08/2005 (6:36 am)
I can guarantee it shouldn't be in that loop (vismanager, processServer() ). That area of code is designed for a specific purpose: determining what units are visible to others, and marking them as deliverable to each client or not, as appropriate. It needs to be done when the unit is being processed for various actions, in RTSUnit.cc processTick().

I'm going to make an observation here, and please understand that I'm not trying to be rude or mean, just stating an opinion based on some observations: I would suggest that you might want to work on learning general object oriented development, and code development/debugging. You've got a pretty good start in figuring out what you want to do, and a broad overview of how to do it, but it sounds like you might be missing some of the fundamentals that would really help you in your implementations.

It's important when modifying source code to know how to run your code under a debugger--debuggers tell you where exactly the code crashed, and what the values for various variables are at the time of the crash, as well as leading up to it. They also give an error message that most of the time will point out what the problem is, or what it is related to.

It's also incredibly important to understand pretty exactly what code does, and most importantly why before you start changing things at a low level like this--for example, knowing the difference between the vis manager's processServer()/processClient(), and the RTSUnit's processTick() are really important things to help you plan out a modification like this. Also, the difference between a selection group, and the unit id's themselves would hopefully have helped you early on.

My suggestion is that you pick a system that the RTS-SK uses--say, how a player selects units, and follow through the code -all- the way down to the smallest level and understand how the code does what it does. That research will allow you to recognize where, and how, to change things to accomplish what you may want to do in the future, and is an important part of modifying an existing code base!
#16
04/11/2005 (10:30 am)
I added a way for the units to attack other units on sight
InitContainerRadiusSearch(%player.getPosition(), %maxRange, $TypeMasks::PlayerObjectType);
and a bunch of other stuff, so it is all in script. They all attack each other but no damage is ever dealt. Is this the problem you were having stephan?
This is my attackevent.
%player.client.sendAttackEvent(%player.selectionWrapper, %targetObject);
#17
04/11/2005 (11:01 am)
Nm, it started working when I added aimobject.