Forum

send enemy entity to CSQC

Discuss CSQC related programming.

Moderator: InsideQC Admins

send enemy entity to CSQC

Postby toneddu2000 » Thu Jul 10, 2014 1:31 am

Hy guys, I'm trying to circumnavigate my csqc animation problem starting by adding a simple enemy entity to CSQC and then add a skeleton to it (now I'm good at that part :D ). This is the SSQC code
Code: Select all

float SendEnemyToCSQC()
{
   WriteByte(MSG_ENTITY, ENT_CSENEMY);
   WriteByte(MSG_ENTITY, enemyBase.frame);   //FIXME NEED COMPRESSION
   WriteByte(MSG_ENTITY, enemyBase.movetype);   //FIXME NEED COMPRESSION
   WriteByte(MSG_ENTITY, enemyBase.solid);   //FIXME NEED COMPRESSION
   WriteString(MSG_ENTITY,enemyBase.model);
   WriteByte(MSG_ENTITY, enemyBase.PersID);
   //WriteEntity(MSG_ENTITY, self.entityType);
   WriteByte(MSG_ENTITY, enemyBase.v_angle_x*(256/360));
   WriteByte(MSG_ENTITY, enemyBase.v_angle_y*(256/360));
   WriteCoord(MSG_ENTITY, enemyBase.origin_x);
   WriteCoord(MSG_ENTITY, enemyBase.origin_y);
   WriteCoord(MSG_ENTITY, enemyBase.origin_z);
   WriteShort(MSG_ENTITY, enemyBase.velocity_x);
   WriteShort(MSG_ENTITY, enemyBase.velocity_y);
   WriteShort(MSG_ENTITY, enemyBase.velocity_z);
   return TRUE;
}
void enemy_base()
{
   local entity tempent;
   tempent = spawn();
   enemyBase = self; //vital! Otherwise the model is not loaded.And order matters!
   enemyBase.solid = SOLID_SLIDEBOX;
   enemyBase.angles = tempent.angles; //vital otherwise enemy doesn't search player all the time!
   enemyBase.movetype = MOVETYPE_STEP;
   precache_model("models/enemies/erebus.iqm");
   setmodel (enemyBase, "models/enemies/erebus.iqm");
   enemyBase.health = 200;
   enemyBase.classname = "enemy_base";
   setsize (enemyBase, VEC_HULL_MIN, VEC_HULL_MAX);
   Enemy_SearchingPlayer(); //AI stuff for making player life less easy!
   enemyBase.flags = FL_MONSTER;
   enemyBase.SendEntity = SendEnemyToCSQC; //send enemy to CSQC...I'm not sure about it! :)
}


I tried to put SendEnemyToCSQC() even in PutClientInServer() but results are the same.
CSQC
Code: Select all
void UpdateCSEnemy(float newent)
{
   if (newent)
   {
      enemyBase.classname = "enemy_base";
      print(enemyBase.model,"- model name\n");
      precache_model(enemyBase.model);
      setmodel (self, enemyBase.model);
      enemyBase.drawmask = MASK_NORMAL; // makes the entity visible
      addentity(enemyBase); //does it need?
   }
}

void CSQC_Ent_Update (float isnew)
{
   self.enttype = ReadByte();
   switch(self.enttype)
   {
      case    ENT_CSPLAYER:
         //blah,blah, player stuff
      break;
            
      case    ENT_CSENEMY:
         enemyBase = self;      
         enemyBase.animation = ReadByte();
         enemyBase.movetype = ReadByte();
         enemyBase.solid = ReadByte();
         enemyBase.model = ReadString();
         enemyBase.PersID = ReadByte();
         //self.entityType = ReadEnt();
         enemyBase.old_wish_ang_x = ReadByte();
         enemyBase.old_wish_ang_y = ReadByte();
         enemyBase.origin_x = ReadCoord();
         enemyBase.origin_y = ReadCoord();
         enemyBase.origin_z = ReadCoord();
         enemyBase.velocity_x = ReadShort();
         enemyBase.velocity_y = ReadShort();
         enemyBase.velocity_z = ReadShort();

         UpdateCSEnemy(isnew); // IT WILL HAPPEN JUST ONCE      
      break;
      
   }

}

void UpdateEnemyEveryFrame()
{
   local entity ent;

   ent = nextent(world);
   while (ent)
   {
      if (ent.classname == "enemy_base")
      {
         centerprint(vtos(enemyBase.origin));
      }
      ent = nextent(ent);
   }
}

// CSQC_UpdateView - Called every rendered frame on the client.  Useful for HUD drawing operations.
void CSQC_UpdateView(float vwidth, float vheight)
{
        centerprint(vtos(enemyBase.origin)); //stamps only enemy first origin but not updating it
        UpdateEnemyEveryFrame(); //same results as above

   R_ClearScene();
   R_SetView(VF_DRAWWORLD, 1);
   R_SetView(VF_ORIGIN, player.origin);
   R_AddEntities(MASK_ENGINE | MASK_ENGINEVIEWMODELS | MASK_NORMAL );///not weapon
   R_RenderScene();
}

the model, instead of searching player, remains at middle air, and the centerprint prints only first coordinate system (where the model is hanged). But If I turn r_showbboxes to 1 I can see the enemyBase entity searching the player
So, my question is: is it possible to send another entity that is not the player to CSQC?
Thanks in advance
toneddu2000
 
Posts: 1301
Joined: Tue Feb 24, 2009 4:39 pm
Location: Italy

Re: send enemy entity to CSQC

Postby Spike » Thu Jul 10, 2014 2:28 am

your enemy_base function spawns a new entity, which it then ignores and leaks. it instead edits the self(player?) entity, which probably isn't desirable.
there's no think function set (in the given code). which implies that you won't ever set SendFlags, thus the engine will never see that it needs to resend the ent, so you get one send and then nothing (until it leaves pvs anyway). Remember, the entity will only be sent by the server if the server thinks there's a new version. So remember to set some SendFlags bit to tell the server that its changed so that it gets sent. It does not spam updates unless the mod marks that it has changed (ie: put self.SendFlags |= 1; at the bottom of your think function).
If SendEntity is set, the setmodel is optional (and thus so is the precache) in FTE. DP is more annoying than that though. Seeing as you're precaching the model in ssqc anyway, you might as well send the .modelindex and use setmodelindex in csqc to match it. this will reduce traffic a bit. you might need to use a short to send modelindexes (often depends on map), but this is usually noticably smaller than strings.


addentity/addentities should only be used between ClearScene and RenderScene. calling them elsewhere is an error (or just pointless). you should not call it within CSQC_Ent_Update (nor a child function).
I recommend you use .predraw to handle the interpolation/animation. this way the engine can do the nextent stuff for you, as well as drawmask filtering (remember, addentities calls the predraw function on every entity that matches the mask, and then calls addentity on it as appropriate).
use nextent+addentity if you really want, just be sure you do it between clearscene (which wipes+resets all scene state, forgetting all added entities) and renderscene (which renders the current scene state to the screen, including all added entities, without wiping any).


anything I missed? :s
Spike
 
Posts: 2878
Joined: Fri Nov 05, 2004 3:12 am
Location: UK

Re: send enemy entity to CSQC

Postby toneddu2000 » Fri Jul 11, 2014 12:53 am

Thanks a lot Spike, your hints turned out very precious! Now I can send an enemy from SSQC to CSQC! It was the self.SendFlags |= 1; that did the trick!
Here the code:
SSQC part
Code: Select all
//be sure to follow the sent elements order from SSQC TO CSQC!
float SendEnemyToCSQC()
{
   WriteByte(MSG_ENTITY, ENT_CSENEMY);
   WriteByte(MSG_ENTITY, enemyBase.frame);   //FIXME NEED COMPRESSION
   WriteByte(MSG_ENTITY, enemyBase.movetype);
   WriteByte(MSG_ENTITY, enemyBase.solid);
   WriteString(MSG_ENTITY,enemyBase.model);
   WriteByte(MSG_ENTITY, enemyBase.modelindex);
   WriteByte(MSG_ENTITY, enemyBase.skeletonindex);
   WriteByte(MSG_ENTITY, enemyBase.PersID);
   WriteShort(MSG_ENTITY, enemyBase.angles_x);
   WriteShort(MSG_ENTITY, enemyBase.angles_y);
   WriteShort(MSG_ENTITY, enemyBase.angles_z);
   WriteCoord(MSG_ENTITY, enemyBase.origin_x);
   WriteCoord(MSG_ENTITY, enemyBase.origin_y);
   WriteCoord(MSG_ENTITY, enemyBase.origin_z);
   WriteShort(MSG_ENTITY, enemyBase.velocity_x);
   WriteShort(MSG_ENTITY, enemyBase.velocity_y);
   WriteShort(MSG_ENTITY, enemyBase.velocity_z);
   return TRUE;
}

void Enemy_SearchingPlayer()
{
   self.SendFlags |= 1;   //vital!! updates the entity in csqc and tells the engine to refresh it!
   self.nextthink = time + 0.04;
}

void Enemy_Think()
{
   self.think = Enemy_SearchingPlayer;
   self.nextthink = time + 0.04;
}

//there's an entity in the map named "enemy_base"
void enemy_base()
{
   local entity tempent;
   tempent = spawn();
   enemyBase = self; //vital! Otherwise the model is not loaded.And order matters!
   if (!enemyBase.skeletonindex){
         enemyBase.skeletonindex = skel_create(enemyBase.modelindex);   
    }
    skel_build(enemyBase.skeletonindex, enemyBase, enemyBase.modelindex, 0, 0, 0, 10000);
   enemyBase.movetype = MOVETYPE_STEP;
   enemyBase.solid = SOLID_SLIDEBOX;
   enemyBase.angles = tempent.angles; //vital otherwise enemy doesn't search player all the time!
   precache_model("models/enemies/erebus.iqm");
   setmodel (enemyBase, "models/enemies/erebus.iqm");
   enemyBase.health = 200;
   enemyBase.classname = "enemy_base";
   setsize (enemyBase, VEC_HULL_MIN, VEC_HULL_MAX);
   Enemy_Think(); //Ai stuff, not important now - simply follow the player everywhere
   enemyBase.flags = FL_MONSTER;
   enemyBase.fixangle = TRUE;      // turn this way immediately
   enemyBase.SendEntity = SendEnemyToCSQC; //vital! It sends enemy to CSQC!!
}

now the CSQC part
Code: Select all
void enemyPredraw()
{
   local float fl_vel;
   local vector vc_vel;

   //velocity checking
   vc_vel = enemyBase.velocity;
   vc_vel_z = 0;
   fl_vel = vlen(vc_vel);
   //skeleton
   if (!enemyBase.skeletonindex){
         enemyBase.skeletonindex = skel_create(enemyBase.modelindex); 
    }
    skel_build(enemyBase.skeletonindex, enemyBase, enemyBase.modelindex, 0, 0, 0, 10000);
    //animation
    enemyBase.frame =  frameforname(enemyBase.modelindex,"run");
    enemyBase.frame1time =  time / (time-self.frame1time * fl_vel);
    //cleanup
    skel_delete(enemyBase.skeletonindex); //important! Otherwise the model appears all messed-up!
   self.skeletonindex=0; //vital!otherwise model can't store bones numbers
}

void UpdateCSEnemy(float newent)
{
   if (newent)
   {
      enemyBase.classname = "enemy_base";
      setmodelindex(self, enemyBase.modelindex);//thanks to Spike, I pass modelindex from SSQC!
      enemyBase.drawmask = MASK_NORMAL; // makes the entity visible
      enemyBase.frame1time = time;
      enemyBase.predraw = enemyPredraw;
   }
}

void CSQC_Ent_Update (float isnew)
{
   self.enttype = ReadByte();
   switch(self.enttype)
   {
      case    ENT_CSPLAYER:
                //player stuff here. Not important now   
      break;
            
      case    ENT_CSENEMY:
                //be sure to match SSQC sent elements order here!
         enemyBase = self;      
         enemyBase.animation = ReadByte();
         enemyBase.movetype = ReadByte();
         enemyBase.solid = ReadByte();
         enemyBase.model = ReadString();
         enemyBase.modelindex = ReadByte();
         enemyBase.skeletonindex = ReadByte();
         enemyBase.PersID = ReadByte();
         enemyBase.angles_x = ReadShort();
         enemyBase.angles_y = ReadShort();
         enemyBase.angles_z = ReadShort();
         enemyBase.origin_x = ReadCoord();
         enemyBase.origin_y = ReadCoord();
         enemyBase.origin_z = ReadCoord();
         enemyBase.velocity_x = ReadShort();
         enemyBase.velocity_y = ReadShort();
         enemyBase.velocity_z = ReadShort();

         UpdateCSEnemy(isnew); // IT WILL HAPPEN JUST ONCE      
      break;
      
   }

}

Now I can use the enemy entity in client side! The cool part is that I don't need to add the model in CSQC anymore, because it's passed from server!
I'm not sure if it needs all the skeleton stuff in SSQC too, but I'm too tired tonight, I'll check next morning!

The only thing that's bugging me is why the content of .predraw function executes at every frame (for example if I put a print, it will be printed every frame) but not for self.frame so it doesn't loop animations. Well, I'm at the starting point again! :lol:
Another thing I don't understand is velocity. I'm using a movetogoal function in SSQC which works fine but when I send velocity from SSQC to CSQC the field is '0 0 0' but the entity is moving!
Does movetogoal() not use velocity for its movements?

Thanks again Spike for your help, vital as always! :D

PS: For the ones interested in the whole code, don't worry, I'm gonna leave all the code soon with a (I hope) very detailed guide! If anyone wants to contribute is welcome, I would need a big help on how to loop skeletal animations in CSQC without framegroups! (hint !hint!) :wink:
toneddu2000
 
Posts: 1301
Joined: Tue Feb 24, 2009 4:39 pm
Location: Italy

Re: send enemy entity to CSQC

Postby Spike » Fri Jul 11, 2014 5:52 am

movetogoal/walkmove use stepping physics. they do NOT use velocity.
the client will need to know the expected animation rate, and will need to interpolate at that rate.

skel_build/skel_create are basically utterly pointless in ssqc. they affect MOVE_HITMODEL stuff, but this is typically laggy such that its pretty much pointless in the first place. in dp there's some way to replicate it to the client (not via csqc), but that's so spammy and jerky that its really only usable for debugging.
your existing csqc code does not need to use them. and in fact its use of them is a complete waste of time - it deletes it before it gets a chance to be used.
skeletal objects last a frame or so beyond when they were removed. this is to allow you to addentity+remove in that order. if addentity sees a skeletonindex of 0 then no skeletal object is used (remember that addentity is implicitly called after your predraw function by the addentities builtin).
and because you create+build+delete+clear all without adding the entity, the skeletal object is never used and it just falls back to the traditional pure-frame-based animation system instead. your code would work with an mdl just as well as it would an iqm. any editing of the skeletal object you make is simply thrown away with your code! but hey, at least you don't leak. the last two lines should generally not be in your predraw function. they should be in your CSQC_Ent_Remove handler instead of being called every single frame in a really wasteful way.

frame1time loops automatically if the model format specifies that the specified animation should loop. for smooth movement, you can try something like this:
self.origin = self.oldrigin + (self.neworigin - self.oldorigin)*((time - self.updatetime)/thinkinterval);
self.frame1time += vlen(self.origin - self.lastorigin) / animationdistancepersecond;
self.lastorigin = self.origin;

thinkinterval is 0.04, matching your nextthink=time+foo; from ssqc. self.updatetime needs to be set to time when you receive an entity update. at that time you should also store neworigin into oldorigin, and read a neworigin value. if its new then you'll need to cope with that too. obviously if its not moving then you should probably do something else with the frame1time value, because that code will fail if its meant to be idle (it won't animate an idle animation).

if you want to get fancy, you can figure out how far something moved in terms of the v_right/v_forward/v_up vectors by using a dotproduct. distancemovedright=distancemovedvector*v_right; for instance, will give you the distance moved along the rightwards vector, and will ignore any forwards/up movement. thanks to v_right being normalized, the result is somewhat similar to vlen. remember to makevectors first. this is useful when you want to blend multiple run animations together, but of course this requires that your models have suitable animations.
Spike
 
Posts: 2878
Joined: Fri Nov 05, 2004 3:12 am
Location: UK

Re: send enemy entity to CSQC

Postby toneddu2000 » Fri Jul 11, 2014 9:35 am

Thanks a lot Spike, I'll try imediately your hints!

But, I can't get these:

movetogoal/walkmove use stepping physics. they do NOT use velocity.
the client will need to know the expected animation rate, and will need to interpolate at that rate.
For animation rate, do you mean fps in iqm model(the frame rate it was produced in Blender with)?

skeletal objects last a frame or so beyond when they were removed. this is to allow you to addentity+remove in that order.
Yeah, infact I always followed your lead about that. When you say "this is to allow you to addentity+remove in that order" I thought that I should have added skel_delete and self.skeletonindex=0 stuff at the end of predraw. I try to put them in CSQC_Ent_Remove and let's see what happens.

frame1time loops automatically if the model format specifies that the specified animation should loop.
I'm sorry but I don't get this. Blender, when outputs an iqm (or whatever model format), doesn't let me choose if an animation strip (visualized in the DopeSheet editor) is loopable or not. Same logic for UDK/Unity and FBX model format. I export a model with all the animation strips I need (run,fire,idle,etc.) and, inside the code, I tell the engine which animation I want to loop and which not. I can't understand your last statement

Code: Select all
self.origin = self.oldrigin + (self.neworigin - self.oldorigin)*((time - self.updatetime)/thinkinterval);
self.frame1time += vlen(self.origin - self.lastorigin) / animationdistancepersecond;
self.lastorigin = self.origin;
In the next days I'll try to do this,thanks. There's a way to know which one is the lastorigin (after a certain amount of time, perhaps)?

Now, just a stupid question. You Spike, that you're a genius, can you please (PLIZ PLIZ) create a new method to play animations in FTE? Somethinh like UDK (which is the best imo)
Random code from my game in UDK
Code: Select all
//in PostInitAnimTree I create the skeleton
simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp)
{
   if (SkelComp == PlayerMesh) //Here it checks if this is the real mesh I need
   {
      PlayerFullBodySlot = AnimNodeSlot(PlayerMesh.FindAnimNode('FullBodySlot')); // this is the whole skeleton
      PlayerHalfBodySlot = AnimNodeSlot(PlayerMesh.FindAnimNode('TopHalfSlot')); // this is the upper part skeleton

      AimNode = AnimNodeAimOffset( PlayerMesh.FindAnimNode('AimNode') ); //this is the node for waist to make aim stuff
      AimNode.SetActiveProfileByName('PlayerBase'); //using a profile name to know it
   }
}

//here I destroy skeleton when is not needed anymore (this function is called when entity is deleted, I presume)
simulated event Destroyed()
{
  Super.Destroyed();
  PlayerFullBodySlot = None;
  PlayerHalfBodySlot = None;
}

//here a simple play animation by custom duration, look at the simplicity
simulated function PlayPlayerFistAnimation()
{
   if(PlayerFullBodySlot != None) //if there's a skeleton
   {
    //I'm playing an animation with just the upper part (PlayerHalfBodySlot skeleton "slot" filter)
      PlayerHalfBodySlot.PlayCustomAnimByDuration('Fist',playerFistAnimDuration,,,false,false);
    //the first boolean is the loop flag, turning that to true will loop animation
    //the second boolean is the override flag, this will override normal flow of animations
   }
}

Could it be feasible making this in FTE? I think all the community will be grateful to you for ever! :D
toneddu2000
 
Posts: 1301
Joined: Tue Feb 24, 2009 4:39 pm
Location: Italy

Re: send enemy entity to CSQC

Postby Spike » Fri Jul 11, 2014 10:32 am

the IQM format has an 'IQM_LOOP' flag on each animation. if its set, frame times beyond the duration of the animation loop back to the start. if its not set, the animation is clamped to the last frame. this is a file format thing, not a code thing. you can use the frameduration builtin and reset it when the duration is exceeded in order to loop it, but it isn't pretty.

by movetogoal animation rates, I mean the number of times it steps per second. an animation (ie: within a framegroup) rate that was specified by a model editor is an automatic thing that you don't normally have to care about, as it just animates automatically anyway. however, you will generally need to smooth out origins so that it appears to move smoothly despite only moving 10 times a second or whatever.

skel_delete inside predraw means you're always allocating new skeletons every single frame. its just inefficient to constantly make reallocations like that. just keep reusing the same skeletal object until you delete it. there's less set-up then (and is required if you're going to try using the ragdoll extension). despite the inefficiency of extra allocations, the problems with your code are that you clear the skeleton to 0 so addentity can never actually use it, and if you don't clear it then you don't create a new skeleton above. you could make the skel_create unconditional and remove the skeletonindex=0 line, that would work, but it would still be more efficient to not constantly reallocate.

Regarding your UT code: no. QuakeC is not object oriented. Quake's model formats do not have any support for bone sets (which is why skel_build takes a bone range).
Assuming you can write your code to use bone ranges, there's really no reason why you can't write your own state management to call skel_build on each bone range as desired.
Spike
 
Posts: 2878
Joined: Fri Nov 05, 2004 3:12 am
Location: UK

Re: send enemy entity to CSQC

Postby toneddu2000 » Fri Jul 11, 2014 11:21 am

yeah, you were right. :D
Extracted from here
Code: Select all
The "Animations" field contains a comma (",") separated list of action names to export. The names can also have parameters of the form "name:X:Y:Z:L", where X is the start frame number, Y is the end frame number, Z is the frames per second (floating-point), and L is 0 or 1 to indicate looping. Earlier parameters can be left empty as in "idle::::1" to specify only a later parameter, and later parameters can be omitted if unnecessary as in "run:1:25".
Sorry to have doubted of your word! :D
I think that Z parameter is what you were describing earlier.
skel_delete inside predraw means you're always allocating new skeletons every single frame. its just inefficient to constantly make reallocations like that. just keep reusing the same skeletal object until you delete it. there's less set-up then (and is required if you're going to try using the ragdoll extension). despite the inefficiency of extra allocations, the problems with your code are that you clear the skeleton to 0 so addentity can never actually use it, and if you don't clear it then you don't create a new skeleton above. you could make the skel_create unconditional and remove the skeletonindex=0 line, that would work, but it would still be more efficient to not constantly reallocate.
The wierd thing is that I used that approach to generate ragdolls some time ago and it worked! Well, now I'll use this new approach and I'll let you know

thanks again Spike
toneddu2000
 
Posts: 1301
Joined: Tue Feb 24, 2009 4:39 pm
Location: Italy

Re: send enemy entity to CSQC

Postby toneddu2000 » Fri Jul 11, 2014 4:35 pm

If I were a chick I'd kiss you Spike! :lol:
For today I'm satisfied. Next days I'll try to add physics to skeleton.

Anyway, if I delete enemyBase.skeletonindex=0; from predraw and move to CSQC_Ent_Remove, and I query a certain bone via print command the engine outputs something like that
Code: Select all
Bone spine num: 0
Bone spine num: 0
Bone spine num: 0
Bone spine num: 0
Bone spine num: 0
Bone spine num: 16
Bone spine num: 0
Bone spine num: 0
Bone spine num: 0
Bone spine num: 0
Bone spine num: 0
Bone spine num: 16

and so on. So, it's like it can understand what's the real number of a bone (16, in this case) for a moment and then the value is erased at the next frame
If I leave it in the predraw function and I issue the print command, it will be always "Bone spine num: 16"

Another thing:if I remove skel_delete() from predraw and I put in CSQC_Ent_Remove() it crashes my model, so I can't understand where to put that damn function! :D

Anyone looks at the shadow bug?! Weird, huh? The shadow projects a model in T-pose, while the model uses correct animation data!

Thanks again Spike, it was 3 years I was striving to accomplish this task!! Tonight I'll post all the code
toneddu2000
 
Posts: 1301
Joined: Tue Feb 24, 2009 4:39 pm
Location: Italy

Re: send enemy entity to CSQC

Postby Nahuel » Sun Jul 13, 2014 10:22 am

awesome work !! i really will apreciate the qc source! I do not understand anything about skeletal animations :(
hi, I am nahuel, I love quake and qc.
User avatar
Nahuel
 
Posts: 484
Joined: Wed Jan 12, 2011 8:42 pm
Location: mar del plata

Re: send enemy entity to CSQC

Postby toneddu2000 » Sun Jul 13, 2014 11:14 am

Thanks Nahuel! the REAL source is coming! :lol:
I'm just updating it a little to manage multiple animation states. I'll post it in the other thread as soon as I can (I hope this evening)!
toneddu2000
 
Posts: 1301
Joined: Tue Feb 24, 2009 4:39 pm
Location: Italy


Return to CSQC Programming

Who is online

Users browsing this forum: No registered users and 1 guest