2 @Copyright Looking Glass Studios, Inc.
3 1996,1997,1998,1999,2000 Unpublished Work.
6 // $Header: r:/t2repos/thief2/src/sim/ghostsnd.cpp,v 1.12 1999/08/26 12:52:58 dc Exp $
7 // send packets/local control systems for ghosts
26 #include <phmods.h> // phys models (all of them) methods (i.e. for the list)
27 #include <phmod.h> // phys model methods (for an individual model)
28 #include <phmoapi.h> // leaning
31 #include <plyrmode.h> // umm, player mode
32 #include <wrtype.h> // for Position, for ObjPos stuff
33 #include <aiman.h> // argh
37 #include <weapcb.h> // so we can track weapon state, to include it in packets
41 //////////////////////////////
44 // really, if we punt impulse, we can just give up on this...
45 static IAINetServices
*g_pAINetServ
=NULL
;
48 int (*GhostPlayerActionCallback
)(ObjID ghost
, int mode
, int state
)=NULL
;
50 //////////////////////////////
51 // misc dumb helpers, why isnt this in a library
53 static BOOL
AngvecMatch(mxs_angvec
*vec1
, mxs_angvec
*vec2
, int eps
)
55 #define eps_test(ang,eps) (abs((short)(vec1->ang-vec2->ang))<eps)
57 return ((vec1
->tx
== vec2
->tx
) &&
58 (vec1
->ty
== vec2
->ty
) &&
59 (vec1
->tz
== vec2
->tz
));
61 return (eps_test(tx
,eps
) && eps_test(ty
,eps
) && eps_test(tz
,eps
));
64 ////////////////////////
65 // core packet generation
67 // actually store into the local ghost state for send during next packet....
68 // for now called directly from motion code (mvr*), prototyped in ghostmvr
69 void GhostSendMoCap(ObjID ghost
, int schema
, int motion
, BOOL is_gloco
)
71 sGhostLocal
*pGL
=GhostGetLocal(ghost
);
72 _GhostDebugSetupLocal(pGL
);
75 // notify about gloco (for interruption purposes) by setting the
76 // schema index to kGhostMotSchemaGLoco
77 pGL
->playing
.schema_idx
=kGhostMotSchemaGLoco
;
78 if (_ghost_track_idx_mocap()||_ghost_track_mocap())
79 _ghost_mprintf(("%s setting send gloco\n", ObjWarnName(ghost
)));
83 pGL
->playing
.schema_idx
=schema
;
84 pGL
->playing
.motion_num
=motion
;
85 if (_ghost_track_idx_mocap()||_ghost_track_mocap())
86 _ghost_mprintf(("%s setting send %d %d (%s) idx based\n",
87 ObjWarnName(ghost
),schema
,motion
,
88 motion
>0?(char *)g_pMotionSet
->GetName(motion
):"???"));
92 //#define MINIPACKET_STATS
93 #ifdef MINIPACKET_STATS
94 static int failed_dpZ
, failed_dvZ
, failed_mag_vZ
, failed_dFac
, failed_mag_dtz
, failed_dp
, failed_player_mag_dtz
, failed_flags
, failed_dvel
, failed_dpos
;
95 static int allow_mini_hb
, allow_rot_hb
, allow_ai_hb
, nogo_ai_hb
, allow_obj_hb
, nogo_obj_hb
, total_hb
, total_ng_hb
;
98 static void _GhostAnalyzeAndSendMiniPacket(sGhostLocal
*pGL
, sGhostPos
*pNew
)
100 #ifdef MINIPACKET_STATS
101 BOOL no_dp
=abs((((int)pNew
->pos
.angle_info
.p
)+100000)-(((int)pGL
->info
.last
.pos
.angle_info
.p
)+100000))<0x80;
102 BOOL no_dpz
=fabs(pNew
->pos
.pos
.z
-pGL
->info
.last
.pos
.pos
.z
)<0.2;
103 BOOL no_dvz
=fabs(pNew
->pos
.vel
.z
-pGL
->info
.last
.pos
.vel
.z
)<0.2;
104 BOOL no_dvel
=mx_is_identical(&pNew
->pos
.vel
,&pGL
->info
.last
.pos
.vel
,0.02);
105 BOOL no_dpos
=mx_is_identical(&pNew
->pos
.pos
,&pGL
->info
.last
.pos
.pos
,0.02);
106 BOOL no_magvz
=fabs(pNew
->pos
.vel
.z
)<=0.2;
107 BOOL no_magdtz
=(abs(pNew
->pos
.angle_info
.dtz
)<0x080);
108 BOOL no_dfac
=AngvecMatch(&pNew
->pos
.angle_info
.fac
,&pGL
->info
.last
.pos
.angle_info
.fac
,0x80);
109 int der_diff_flags
=pNew
->pos
.flags
^pGL
->info
.last
.pos
.flags
;
110 der_diff_flags
&=~(kGhostHBHaveCap
|kGhostHBUseG
); // what others dont we care about
111 BOOL der_same_flags
=der_diff_flags
==0;
113 // do some minipacket analysis, eh?
114 if (no_dvel
&& no_dpos
&& der_same_flags
)
115 { allow_rot_hb
++; allowed
=TRUE
; }
116 else if (_GhostIsType(pGL
->cfg
.flags
,AI
) && no_dp
&& no_dpz
&& no_dvz
&& der_same_flags
)
117 { allow_ai_hb
++; allowed
=TRUE
; }
118 else if (no_dp
&& no_magvz
&& no_magdtz
)
119 { allow_mini_hb
++; allowed
=TRUE
; }
120 else if (_GhostIsType(pGL
->cfg
.flags
,IsObj
) && no_dfac
&& der_same_flags
)
121 { allow_obj_hb
++; allowed
=TRUE
; }
124 if (_GhostIsType(pGL
->cfg
.flags
,AI
))
126 if (_GhostIsType(pGL
->cfg
.flags
,IsObj
))
128 if (!no_dp
) failed_dp
++;
129 if (!no_magvz
) failed_mag_vZ
++;
130 if (!no_dvel
) failed_dvel
++;
131 if (!no_dpos
) failed_dpos
++;
135 if (_GhostIsType(pGL
->cfg
.flags
,Player
))
136 failed_player_mag_dtz
++;
138 if (_GhostIsType(pGL
->cfg
.flags
,AI
)) // specials for AI/Obj only sitchiations...
140 if (!no_dpz
) failed_dpZ
++;
141 if (!no_dvz
) failed_dvZ
++;
142 if (!der_same_flags
) failed_flags
++;
144 if (_GhostIsType(pGL
->cfg
.flags
,IsObj
))
146 if (!no_dfac
) failed_dFac
++;
147 if (!der_same_flags
) failed_flags
++;
152 // actual minipacket send logic...
153 #define ALLOW_MINIPACKETS
154 #ifdef ALLOW_MINIPACKETS
155 short delta_p
= (short)((ushort
)pNew
->pos
.angle_info
.p
- (ushort
)pGL
->info
.last
.pos
.angle_info
.p
);
156 int diff_flags
=pNew
->pos
.flags
^pGL
->info
.last
.pos
.flags
;
157 diff_flags
&=~(kGhostHBHaveCap
|kGhostHBUseG
); // what others dont we care about
158 BOOL same_flags
=diff_flags
==0;
160 // would this be faster as subtract and zero vec test? ???
162 mx_is_identical(&pNew
->pos
.vel
,&pGL
->info
.last
.pos
.vel
,0.02)&&
163 mx_is_identical(&pNew
->pos
.pos
,&pGL
->info
.last
.pos
.pos
,0.02))
164 { // wow, we can use a rot packet, i guess?
166 RMH
.angle_info
.fac
=pNew
->pos
.angle_info
.fac
;
167 g_pGhostRotHeartbeatNGMsg
->Send(OBJ_NULL
,pGL
->obj
,pGL
->info
.seq_id
,&RMH
);
170 if (abs(delta_p
)<0x200)
172 if (_GhostIsType(pGL
->cfg
.flags
,AI
))
174 BOOL nodpz
=fabs(pNew
->pos
.pos
.z
-pGL
->info
.last
.pos
.pos
.z
)<0.2;
175 BOOL nodvz
=fabs(pNew
->pos
.vel
.z
-pGL
->info
.last
.pos
.vel
.z
)<0.2;
176 if (nodpz
&& nodvz
&& same_flags
)
179 AHB
.posx
=pNew
->pos
.pos
.x
; AHB
.posy
=pNew
->pos
.pos
.y
;
180 AHB
.velx
=pNew
->pos
.vel
.x
; AHB
.vely
=pNew
->pos
.vel
.y
;
181 AHB
.tz
=pNew
->pos
.angle_info
.tz
; AHB
.dtz
=pNew
->pos
.angle_info
.dtz
;
182 g_pGhostAIHeartbeatNGMsg
->Send(OBJ_NULL
,pGL
->obj
,pGL
->info
.seq_id
,&AHB
);
186 BOOL nomagvz
=fabs(pNew
->pos
.vel
.z
)<=0.2;
187 BOOL nomagdtz
=(abs(pNew
->pos
.angle_info
.dtz
)<0x080);
188 // somehow, have to make sure this isnt a go to dtz 0 frame too!
189 if (nomagvz
&& nomagdtz
)
191 sGhostMiniHeartbeat MHB
;
192 MHB
.pos
=pNew
->pos
.pos
; MHB
.flags
=pNew
->pos
.flags
;
193 MHB
.velx
=pNew
->pos
.vel
.x
; MHB
.vely
=pNew
->pos
.vel
.y
;
194 MHB
.tz
=pNew
->pos
.angle_info
.tz
;
195 g_pGhostMiniHeartbeatNGMsg
->Send(OBJ_NULL
,pGL
->obj
,pGL
->info
.seq_id
,&MHB
);
199 if (_GhostIsType(pGL
->cfg
.flags
,IsObj
))
201 if (same_flags
&& AngvecMatch(&pNew
->pos
.angle_info
.fac
,&pGL
->info
.last
.pos
.angle_info
.fac
,0x80))
204 OHB
.pos
=pNew
->pos
.pos
;
205 OHB
.vel
=pNew
->pos
.vel
;
206 g_pGhostObjHeartbeatNGMsg
->Send(OBJ_NULL
,pGL
->obj
,pGL
->info
.seq_id
,&OHB
);
210 #endif // ALLOW_MINIPACKETS
211 g_pGhostHeartbeatTerseNGMsg
->Send(OBJ_NULL
,pGL
->obj
,pGL
->info
.seq_id
,&pNew
->pos
);
214 // actually send a ghost update packet
215 void _GhostSendHeartbeat(sGhostLocal
*pGL
, sGhostPos
*pNew
)
217 pGL
->info
.seq_id
++; // if it wraps, so what, clients problem
218 if (pGL
->playing
.schema_idx
>0) // this isnt quite right, but hey... ignore gLoco too...
219 pNew
->pos
.flags
|=kGhostHBHaveCap
; // for now, this is safest place to do this
220 if (mx_mag2_vec(&pGL
->info
.last
.pos
.vel
)>mx_mag2_vec(&pNew
->pos
.vel
)+5.0) // some slack
221 pNew
->pos
.flags
|=kGhostHBSlowDown
; // im going to slow down, watch me... see
223 if (!_ghost_no_sends()) // go ahead and send it
224 if (pNew
->pos
.flags
&(kGhostHBObjRel
|kGhostHBHaveCap
))
225 g_pGhostHeartbeatFullMsg
->Send(OBJ_NULL
,pGL
->obj
,pGL
->info
.seq_id
,pNew
->rel_obj
,&pNew
->pos
,&pGL
->playing
);
226 else if (pNew
->pos
.flags
&kGhostHBUseG
) // send pNew->rel_obj always, or only if bit is set???
227 g_pGhostHeartbeatNormMsg
->Send(OBJ_NULL
,pGL
->obj
,pGL
->info
.seq_id
,&pNew
->pos
);
228 else // @TBD: minipackets here as well?
229 _GhostAnalyzeAndSendMiniPacket(pGL
,pNew
);
230 #ifdef MINIPACKET_STATS
234 // fill last, update times, etc...
235 pGL
->info
.last
= *pNew
;
236 pGL
->info
.last
.time
= GetSimTime();
237 pGL
->cfg
.flags
&= ~kGhostCfForce
; // we sent, force no longer needed
239 // do same horrible thing we do on receiver... i hate this
240 mxs_vector old_vel
=pGL
->info
.pred
.pos
.vel
;
241 pGL
->info
.pred
= pGL
->info
.last
; // start predicting from the packet we just sent
242 if ((pGL
->info
.pred
.pos
.flags
&kGhostHBAbsVel
)==0)
243 pGL
->info
.pred
.pos
.vel
=old_vel
;
245 // set/clear edge triggers
246 pGL
->info
.rel_obj
= pNew
->rel_obj
;
247 pGL
->playing
.schema_idx
= kGhostMotSchemaNoCustom
;
249 // did we state change?
250 if (pNew
->pos
.mode
==kGhostModeSleep
) // we are gonna go to sleep too, since we sent it
251 pGL
->cfg
.flags
|=kGhostCfDontRun
;
253 // misc debugging fun
254 if (_ghost_show_sends())
255 _ghost_mprintf(("Sent message %d from %s at time %d\n",pGL
->info
.seq_id
,ObjWarnName(pGL
->obj
),GetSimTime()));
256 if (_ghost_show_full_sends())
257 _GhostPrintGhostPos(pGL
->obj
,&pNew
->pos
,pGL
->cfg
.flags
,"snd",pGL
->info
.seq_id
);
260 ////////////////////////
263 // @TBD: as you might imagine, this is not a particularly good implementation at this point
264 static int _ComputeHeartFrequencyMax(sGhostLocal
*pGL
, sGhostPos
*pNew
)
266 // fast moving things want more
269 return 30000; // 30s, sure, why not...
272 static int _ghost_HF_Min
[]={0,500,250,100};
274 static int _ComputeHeartFrequencyMin(sGhostLocal
*pGL
, sGhostPos
*pNew
)
276 // fast moving things want more, near player as well
277 // and should listen to the detail slider too
280 return _ghost_HF_Min
[_GhostGetType(pGL
->cfg
.flags
)];
284 ////////////////////////////////////////
287 static int _ghost_detail_mul
[]={1.0,1.5,1.0,0.5};
289 static float _ComputeGhostDetail(sGhostLocal
*pGL
)
291 return pGL
->cfg
.detail
;
294 // these are all totally bogus, need a core of estimate/predition code
295 #define kGhostEpsPos (20.0) // w30 // distance from the last
296 #define kGhostEpsApproxPos (4.0) // w8
297 #define kGhostEpsAbsVel (4.0)
298 #define kGhostEpsControlVel (4.0)
299 #define kGhostEpsFromZeroVel (1.0)
300 #define kGhostEpsHeading (0x2000)
301 #define kGhostEpsPitch (0x1000)
302 #define kGhostEpsHeadApprox (0x1000)
303 #define kGhostEpsHeadVel (0x2800) // really, i almost never want this to fire
304 #define kGhostDtzMinCap (0x800)
306 // use detail implicitly
307 #define _fltEps(type) ((float)((kGhostEps##type##))*_ComputeGhostDetail(pGL))
308 #define _intEps(type) ((int)((kGhostEps##type##))*_ComputeGhostDetail(pGL))
310 ////////////////////////////////////////
311 // do the wierd stuff we do for dtz computation
312 static void _GhostClearDtz(sGhostLocal
*pGL
)
314 pGL
->info
.dtz_dat
.s_ptr
=pGL
->info
.dtz_dat
.s_cnt
=pGL
->info
.dtz_dat
.dtz_sum
=0;
317 // actually go compute a current windowed average dtz and update streak counts
318 static short _GhostFindDtz(sGhostLocal
*pGL
, mxs_ang cur_tz
)
320 int base_time
, cur_time
=GetSimTime();
321 sGhostDtzInfo
*pDtz
=&pGL
->info
.dtz_dat
;
322 int prev_samp
=(pDtz
->s_ptr
+NUM_DTZ_SAMPLES
-1)&MASK_DTZ_SAMPLES
;
326 if (pGL
->last_fr
!=pDtz
->samples
[prev_samp
].s_time
)
327 { // invalidating all old data
328 pDtz
->s_ptr
=pDtz
->s_cnt
=pDtz
->dtz_sum
=0; // pDtz->streak=0;
329 last_tz
=pGL
->info
.last
.pos
.angle_info
.tz
;
333 last_tz
=pDtz
->samples
[prev_samp
].s_tz
;
336 last_tz
=pGL
->info
.last
.pos
.angle_info
.tz
;
337 short cur_dtz
=cur_tz
-last_tz
;
339 if (pDtz
->s_cnt
==NUM_DTZ_SAMPLES
)
341 pDtz
->dtz_sum
-=pDtz
->samples
[pDtz
->s_ptr
].s_dtz
; // sub out old value
342 base_time
=pDtz
->samples
[pDtz
->s_ptr
].s_time
;
344 else // still working up to full set of samples
346 base_time
=pGL
->info
.last
.time
;
347 pDtz
->s_cnt
=pDtz
->s_ptr
+1;
350 pDtz
->samples
[pDtz
->s_ptr
].s_tz
= cur_tz
;
351 pDtz
->samples
[pDtz
->s_ptr
].s_time
= cur_time
;
352 pDtz
->samples
[pDtz
->s_ptr
].s_dtz
= cur_dtz
;
354 pDtz
->dtz_sum
+= cur_dtz
;
355 pDtz
->s_ptr
=(pDtz
->s_ptr
+NUM_DTZ_SAMPLES
+1)&MASK_DTZ_SAMPLES
;
358 if (pDtz
->streak
>0) pDtz
->streak
=-1;
361 if (pDtz
->streak
<0) pDtz
->streak
= 1;
364 short real_dtz
=DTANG_TO_FIXVEL(pDtz
->dtz_sum
,cur_time
-base_time
);
366 if (_ghost_track_heading())
367 if (_GhostIsType(pGL
->cfg
.flags
,Player
))
368 _ghost_mprintf(("%s real %d from sum %d times %d %d streak %d (cnt %d ptr %d)\n",
369 ObjWarnName(pGL
->obj
),real_dtz
,pDtz
->dtz_sum
,cur_time
,base_time
,pDtz
->streak
,pDtz
->s_cnt
,pDtz
->s_ptr
));
373 #ifdef GHOST_DEBUGGING
374 static int _ghost_new_packet_reason
;
375 #define Return_NeedPacketReason(reason) { _ghost_new_packet_reason=reason; return TRUE; }
377 #define _ghost_new_packet_reason 0
378 #define Return_NeedPacketReason(reason) return TRUE
381 // local host, do we need to send packet updates
382 // first, go through and build pNew, since we need to know our state
383 // then, look at pNew verse last sent packet to determine what to do
384 static BOOL
_IsNewPacketNeeded(sGhostLocal
*pGL
, sGhostPos
*pNew
)
386 cPhysModel
*pModel
= g_PhysModels
.Get(pGL
->obj
);
388 { // since we check for phys outside, shouldnt happen, but ....
389 Warning(("Hey! GhostIsNewPacketNeeded called on %s, which has no phys models\n",ObjWarnName(pGL
->obj
)));
393 cPhysCtrlData
*pCtrl
= pModel
->GetControls();
394 cPhysDynData
*pDyn
= pModel
->GetDynamics();
396 if (pCtrl
==NULL
|| pDyn
==NULL
)
397 { // this really should never fire, but hey, lets be safety pup approved
398 Warning(("Hey! GhostIsNewPacketNeeded called on %s, which has pCtrl %x pDyn %x\n",ObjWarnName(pGL
->obj
),pCtrl
,pDyn
));
404 // do weapon and mode, and any other player specific things
405 if (_GhostIsType(pGL
->cfg
.flags
,Player
))
407 switch (PlayerMotionGetActive())
409 case kMoLeanLeft
: pNew
->pos
.mode
= kGhostModeLeanLeft
; break;
410 case kMoLeanRight
: pNew
->pos
.mode
= kGhostModeLeanRight
; break;
411 default: pNew
->pos
.mode
= GetPlayerMode(); break;
413 pNew
->pos
.flags
|= kGhostHBHaveMode
;
414 pNew
->pos
.weap
= pGL
->nWeapon
;
415 if (pNew
->pos
.weap
!=pGL
->info
.last
.pos
.weap
)
417 pNew
->pos
.flags
|= kGhostHBWeap
; // counts on any change going G
418 if (_ghost_track_weapons())
419 _ghost_mprintf(("Send %s setting pNew weap to %d\n",ObjWarnName(pGL
->obj
),pNew
->pos
.weap
));
425 // lets assume we dont need to force an update w/these, since a mocap will be soon
426 if (pGL
->state
&kGhostStSwinging
)
427 pNew
->pos
.flags
|= kGhostHBStartSw
;
428 if (pGL
->state
&(kGhostStDead
|kGhostStDying
))
429 pNew
->pos
.flags
|= kGhostHBDead
;
430 if (_GhostGravLocal(pGL
->obj
, pNew
, pGL
->cfg
.flags
))
431 pNew
->pos
.flags
|= kGhostHBGravity
;
433 pNew
->pos
.pos
= pModel
->GetLocationVec();
435 // do the world-to-rel here, so we're always comparing relative location
436 pNew
->rel_obj
= OBJ_NULL
;
437 _ghost_pos_worldtorel(pGL
->obj
,&pNew
->rel_obj
,&pNew
->pos
.pos
,pGL
->cfg
.flags
);
438 if (pNew
->rel_obj
!=OBJ_NULL
)
439 pNew
->pos
.flags
|= kGhostHBOnObj
;
441 // get velocity data...
442 if (_GhostIsType(pGL
->cfg
.flags
,AI
))
443 { // as AI's seem to not set vels at all
445 g_pAINetServ
->GetTargetVel(pGL
->obj
,&ourImp
);
447 if (pGL
->state
&kGhostStSwinging
) // dont look at impulse during combat???
448 mx_zero_vec(&pNew
->pos
.vel
); // probably a bad idea...
451 pNew
->pos
.vel
= ourImp
.vec
;
452 // mprintf("Impulse %g %g %g\n",ourImp.vec.el[0],ourImp.vec.el[1],ourImp.vec.el[2]);
453 pNew
->pos
.flags
|= kGhostHBAbsVel
; // should this be treated as control or abs???
455 else if (pModel
->IsVelocityControlled()&&pNew
->pos
.mode
!=kPM_Jump
)
456 pNew
->pos
.vel
= pCtrl
->GetControlVelocity();
459 pNew
->pos
.vel
= pDyn
->GetVelocity();
460 _ghost_vel_worldtorel(pNew
->rel_obj
, &pNew
->pos
.vel
);
461 pNew
->pos
.flags
|= kGhostHBAbsVel
;
463 // mprintf("Got %g %g %g\n",pNew->pos.vel.x,pNew->pos.vel.y,pNew->pos.vel.z);
465 // now decide which Angular model to use
466 if (pGL
->cfg
.flags
&kGhostCfObjPos
|| _GhostIsType(pGL
->cfg
.flags
,Player
))
468 pNew
->pos
.angle_info
.fac
=ObjPosGet(pGL
->obj
)->fac
;
469 if ((pNew
->pos
.angle_info
.fac
.ty
|pNew
->pos
.angle_info
.fac
.tx
)==0)
470 { // as angle_info.fac.tz overlaps the other, this gets easier
471 pNew
->pos
.angle_info
.dtz
= _GhostFindDtz(pGL
,pNew
->pos
.angle_info
.tz
);
473 // don't forget about p: gets real val for player, zeroed for everyone else
474 if (_GhostIsType(pGL
->cfg
.flags
,Player
))
475 pNew
->pos
.angle_info
.p
= pModel
->GetRotation(PLAYER_HEAD
).ty
;
477 pNew
->pos
.angle_info
.p
= 0;
480 pNew
->pos
.flags
|= kGhostHBFullAngs
;
484 mxs_vector rvel
= pDyn
->GetRotationalVelocity();
485 if (mx_mag2_vec(&rvel
)<EPS_ZERO_VEC
)
487 pNew
->pos
.angle_info
.fac
=pModel
->GetRotation();
488 pNew
->pos
.flags
|= kGhostHBFullAngs
;
492 if (fabs(rvel
.x
)+fabs(rvel
.y
)>0.05)
494 // _ghost_mprintf(("%s losing rotational data %g %g %g\n",ObjWarnName(pGL->obj),rvel.x,rvel.y,rvel.z));
495 pNew
->pos
.flags
|= kGhostHBAxisRot
;
497 #define RADVEL_TO_FIXVEL(x) (short)(((x) / MX_REAL_PI) * 0x8000)
498 pNew
->pos
.angle_info
.tz
= pModel
->GetRotation().tz
;
499 pNew
->pos
.angle_info
.dtz
= RADVEL_TO_FIXVEL(rvel
.z
);
500 pNew
->pos
.angle_info
.p
= pModel
->GetRotation().ty
;
504 #ifdef GHOST_DEBUGGING
505 if ((pGL
->state
&(kGhostStSleep
|kGhostStRevive
))==(kGhostStSleep
|kGhostStRevive
))
506 _ghost_mprintf(("GHOST SND CONFUSED, %s is %x, Sleeping and Reviving at once\n",ObjWarnName(pGL
->obj
),pGL
->state
));
508 // so we can show the packet each frame
509 if (_ghost_show_pNew())
511 _GhostPrintGhostPos(pGL
->obj
,&pNew
->pos
,pGL
->cfg
.flags
,"pnew",-1);
512 if ((pNew
->pos
.flags
&kGhostHBAbsVel
)==0)
514 mxs_vector tmp
= pDyn
->GetVelocity();
515 if (__ghost_dbg_rel())
516 _ghost_mprintf((" ps. current vel %g %g %g\n",tmp
.x
,tmp
.y
,tmp
.z
));
519 if (_ghost_show_send_pred())
520 _GhostPrintGhostPos(pGL
->obj
,&pGL
->info
.pred
.pos
,pGL
->cfg
.flags
,"sprd",-1);
521 if (_ghost_show_send_last())
522 _GhostPrintGhostPos(pGL
->obj
,&pGL
->info
.last
.pos
,pGL
->cfg
.flags
,"slst",-1);
525 if (pNew
->rel_obj
!= pGL
->info
.rel_obj
)
527 if (_ghost_track_relobj())
528 _ghost_mprintf(("%s sending relobj %s (old %s)\n",
529 ObjWarnName(pGL
->obj
),ObjWarnName(pNew
->rel_obj
),ObjWarnName(pGL
->info
.rel_obj
)));
530 pNew
->pos
.flags
|=kGhostHBObjRel
; // relobj edge trigger - so it sends
534 // reasons which require guaranteed packets first
535 // do we want to set guarantee, or anything?
536 if (pGL
->state
&kGhostStSleep
) // next packets will be sleep as well
538 pNew
->pos
.mode
=kGhostModeSleep
; // have to do this before mode !=, and before needreason
539 pGL
->state
&=~kGhostStSleep
;
540 pGL
->state
|= kGhostSendStSleeping
; // umm, not sure this is right, but hey
541 pNew
->pos
.flags
|=kGhostHBHaveMode
; // hey, havemode here, so we trigger the last thing..
544 if (pGL
->cfg
.flags
&kGhostCfNew
)
546 pNew
->pos
.flags
|=kGhostHBUseG
; // always use G packet for first from a New Ghost
547 pGL
->cfg
.flags
&=~kGhostCfNew
; // and clear the New field
548 Return_NeedPacketReason(1);
552 if (pNew
->pos
.flags
&kGhostHBHaveMode
)
553 if (pNew
->pos
.mode
!=pGL
->info
.last
.pos
.mode
)
555 pNew
->pos
.flags
|=kGhostHBUseG
; // always use G packets for mode changes?
556 Return_NeedPacketReason(2);
559 // do the revive thing
560 if (pGL
->state
&kGhostStRevive
)
562 pNew
->pos
.mode
=kGhostModeRevive
;
563 pNew
->pos
.flags
|=kGhostHBHaveMode
|kGhostHBUseG
;
564 pGL
->state
=0; // clear all state, why not, live it up...
565 Return_NeedPacketReason(4);
567 if (pGL
->state
&kGhostStDying
) // set internal im dead state
568 pGL
->state
|=kGhostStDead
; // wont be cleared till revive
570 // player mocap hell - for now player only
571 if (_GhostIsType(pGL
->cfg
.flags
,Player
)&&(pGL
->state
&kGhostStIrqChecks
))
572 { // add "do" to pNew flags, if any left... - actually mode or pNew, woo woo
573 int new_schema
=kGhostMotSchemaNoCustom
, state_bit
=1, i
;
574 if (_ghost_watch_events())
575 _ghost_mprintf(("Handle state IRQ init state %x...",pGL
->state
));
576 if (GhostPlayerActionCallback
) // if no cback, cant play motions, what is our point
577 for (i
=0; state_bit
<=kGhostStLastPerFrame
; i
++, state_bit
<<=1)
578 if ((new_schema
==kGhostMotSchemaNoCustom
)&&(pGL
->state
&state_bit
))
580 new_schema
=(*GhostPlayerActionCallback
)(pGL
->obj
,pNew
->pos
.mode
,state_bit
);
581 pGL
->state
&=~state_bit
;
583 else if (new_schema
!=kGhostMotSchemaNoCustom
) // if we found something
584 pGL
->state
&=~state_bit
; // clear all less important state
585 if (new_schema
!=kGhostMotSchemaNoCustom
) // setting new schema will trigger a send due to mocap
587 if (_ghost_watch_events())
588 _ghost_mprintf(("final state %x\n",pGL
->state
));
589 pGL
->playing
.schema_idx
=new_schema
;
592 Warning(("MoCap Int Flag (%x) on player, but no schema\n",pGL
->state
));
595 if (pGL
->state
&kGhostStDying
) // if dying - clear it out
596 pGL
->state
&=~kGhostStDying
;
598 if (pGL
->playing
.schema_idx
>0)
599 { // always use G packets for custom captures)
600 pNew
->pos
.flags
|=kGhostHBUseG
;
601 Return_NeedPacketReason(5);
604 if (pNew
->pos
.flags
&kGhostHBWeap
)
606 pNew
->pos
.flags
|=kGhostHBUseG
; // always use G packets for weap
607 Return_NeedPacketReason(6);
611 if (pGL
->cfg
.flags
&kGhostCfForce
)
613 pNew
->pos
.flags
|=kGhostHBUseG
; // always use G packets for forces
614 Return_NeedPacketReason(7);
617 // early out if we are sending too often - ???
618 if (pGL
->info
.last
.time
+_ComputeHeartFrequencyMin(pGL
,pNew
)>GetSimTime())
621 // check transition from relative to non-relative
622 if (pNew
->pos
.flags
&kGhostHBObjRel
)
624 if (_ghost_watch_events())
625 _ghost_mprintf(("Ghost %s relobj edge %d -> %d\n",ObjWarnName(pGL
->obj
),pGL
->info
.rel_obj
,pNew
->rel_obj
));
626 pNew
->pos
.flags
|=kGhostHBUseG
; // always use G packet for rel-world transition
627 Return_NeedPacketReason(3);
631 // transition packets which are only G to make sure we hear them
633 // going to Zero velocity
634 if (_is_zero_vec(&pNew
->pos
.vel
))
635 if (!_is_zero_vec(&pGL
->info
.last
.pos
.vel
))
637 pNew
->pos
.flags
|=kGhostHBUseG
; // dont want to miss hearing about go to zero
638 Return_NeedPacketReason(11);
641 if ((pNew
->pos
.flags
&kGhostHBFullAngs
)==0)
642 if (pGL
->info
.last
.pos
.angle_info
.dtz
!= 0) // last send non-zero
643 if (pGL
->info
.dtz_dat
.streak
<= -2) // 2 zero frames
645 pNew
->pos
.angle_info
.dtz
=0;
647 if (_ghost_math_sends())
648 _ghost_mprintf(("%d dtz 2 Frames at %x pred %x\n",pGL
->obj
,
649 pNew
->pos
.angle_info
.dtz
,pGL
->info
.pred
.pos
.angle_info
.dtz
));
650 pNew
->pos
.flags
|=kGhostHBUseG
; // dont want to miss hearing about go to zero
651 Return_NeedPacketReason(18);
655 // now "normal" reasons to require packets
657 #define constantFlags (kGhostHBFullAngs|kGhostHBAbsVel)
658 // if we switched how we are reporting angles or velocity, send an update
659 if ((pNew
->pos
.flags
&(constantFlags
))!=(pGL
->info
.last
.pos
.flags
&(constantFlags
)))
660 Return_NeedPacketReason(8);
663 if (pGL
->info
.last
.time
+_ComputeHeartFrequencyMax(pGL
,pNew
)<GetSimTime())
665 // no timeout if we are in the EXACT SAME PLACE - should check rotation too...
666 if (!mx_is_identical(&pNew
->pos
.pos
,&pGL
->info
.last
.pos
.pos
,0.02))
667 if (!AngvecMatch(&pNew
->pos
.angle_info
.fac
,&pGL
->info
.last
.pos
.angle_info
.fac
,0x100))
668 Return_NeedPacketReason(9);
671 float vel_eps
=(pNew
->pos
.flags
&kGhostHBAbsVel
)?_fltEps(AbsVel
):_fltEps(ControlVel
);
673 // check new velocity
674 if (!mx_is_identical(&pNew
->pos
.vel
,&pGL
->info
.last
.pos
.vel
,vel_eps
))
676 if (_ghost_math_sends())
677 _ghost_mprintf(("%d n:%g %g %g o:%g %g %g e:%g f:%x\n",
678 pGL
->obj
,pNew
->pos
.vel
.el
[0],pNew
->pos
.vel
.el
[1],pNew
->pos
.vel
.el
[2],
679 pGL
->info
.last
.pos
.vel
.el
[0],pGL
->info
.last
.pos
.vel
.el
[1],pGL
->info
.last
.pos
.vel
.el
[2],
680 vel_eps
,pNew
->pos
.flags
));
681 Return_NeedPacketReason(10);
684 if (_is_zero_vec(&pGL
->info
.last
.pos
.vel
))
685 if (mx_mag2_vec(&pNew
->pos
.vel
)>_fltEps(FromZeroVel
)*_fltEps(FromZeroVel
))
687 if (_ghost_math_sends())
688 _ghost_mprintf(("%d from zero vel n:%g %g %g e:%g f:%x\n",
689 pGL
->obj
,pNew
->pos
.vel
.el
[0],pNew
->pos
.vel
.el
[1],pNew
->pos
.vel
.el
[2],
690 vel_eps
,pNew
->pos
.flags
));
691 Return_NeedPacketReason(12);
694 // check position delta
695 if (!mx_is_identical(&pNew
->pos
.pos
,&pGL
->info
.last
.pos
.pos
,_fltEps(Pos
)))
696 Return_NeedPacketReason(13);
697 if (!mx_is_identical(&pNew
->pos
.pos
,&pGL
->info
.pred
.pos
.pos
,_fltEps(ApproxPos
)))
699 if (_ghost_math_sends())
700 _ghost_mprintf(("%d cur %g %g %g pred %g %g %g\n",
701 pGL
->obj
,pNew
->pos
.pos
.el
[0],pNew
->pos
.pos
.el
[1],pNew
->pos
.pos
.el
[2],
702 pGL
->info
.pred
.pos
.pos
.el
[0],pGL
->info
.pred
.pos
.pos
.el
[1],pGL
->info
.pred
.pos
.pos
.el
[2]));
703 Return_NeedPacketReason(14);
706 // check angular delta
707 if (pNew
->pos
.flags
&kGhostHBFullAngs
)
709 if (!AngvecMatch(&pNew
->pos
.angle_info
.fac
,&pGL
->info
.last
.pos
.angle_info
.fac
,_intEps(Heading
)))
711 if (_ghost_math_sends())
712 _ghost_mprintf(("%d cur %x %x %x pred %x %x %x\n",
713 pGL
->obj
, pNew
->pos
.angle_info
.fac
.el
[0],
714 pNew
->pos
.angle_info
.fac
.el
[1], pNew
->pos
.angle_info
.fac
.el
[2],
715 pGL
->info
.last
.pos
.angle_info
.fac
.el
[0], // ack
716 pGL
->info
.last
.pos
.angle_info
.fac
.el
[1],
717 pGL
->info
.last
.pos
.angle_info
.fac
.el
[2]));
718 Return_NeedPacketReason(15);
723 if (abs((short)(pNew
->pos
.angle_info
.tz
- pGL
->info
.pred
.pos
.angle_info
.tz
)) > _intEps(HeadApprox
))
725 if (_ghost_math_sends())
726 _ghost_mprintf(("%d tz at %x pred %x\n",pGL
->obj
,
727 pNew
->pos
.angle_info
.tz
,pGL
->info
.pred
.pos
.angle_info
.tz
));
728 Return_NeedPacketReason(16);
731 if (pGL
->info
.last
.pos
.angle_info
.dtz
== 0) // last sent at zero, only care about up
733 if (pGL
->info
.dtz_dat
.streak
> 2) // 2 positive frames
734 if (pNew
->pos
.angle_info
.dtz
>kGhostDtzMinCap
)
736 if (_ghost_math_sends())
737 _ghost_mprintf(("%d Dtz non-zero at %x pred %x\n",pGL
->obj
,
738 pNew
->pos
.angle_info
.dtz
,pGL
->info
.pred
.pos
.angle_info
.dtz
));
739 Return_NeedPacketReason(17);
743 // and dtz delta itself, as well
744 if (abs((short)(pNew
->pos
.angle_info
.dtz
- pGL
->info
.last
.pos
.angle_info
.dtz
)) > _intEps(HeadVel
))
746 if (_ghost_math_sends())
747 _ghost_mprintf(("%d dtz at %x pred %x\n",pGL
->obj
,
748 pNew
->pos
.angle_info
.dtz
,pGL
->info
.pred
.pos
.angle_info
.dtz
));
749 Return_NeedPacketReason(19);
752 if (abs((short)(pNew
->pos
.angle_info
.p
- pGL
->info
.last
.pos
.angle_info
.p
)) > _intEps(Pitch
))
754 if (_ghost_math_sends())
755 _ghost_mprintf(("%d p at %x pred %x\n",pGL
->obj
,
756 pNew
->pos
.angle_info
.p
,pGL
->info
.pred
.pos
.angle_info
.p
));
757 Return_NeedPacketReason(20);
764 #define NUM_SEND_REASONS 21 // ???? Keep this in Synch, this is hateful, so shoot me
766 int ghost_local_frame_rate
=0; // ms between frames
768 // go through our local ghosts and see what we care about updating
769 void _GhostFrameProcessLocal(sGhostLocal
*pGL
, float dt
)
771 if ((ghost_local_frame_rate
)&&(pGL
->last_fr
+ghost_local_frame_rate
>GetSimTime()))
772 return; // this ghost was last run at a time under our frame rate, so skip
773 _GhostDebugSetupLocal(pGL
);
774 if (pGL
->state
&kGhostStRevive
) // if user revives us
775 pGL
->cfg
.flags
&=~kGhostCfDontRun
; // allow ourselves to run again
776 if (!PhysObjHasPhysics(pGL
->obj
)||(pGL
->cfg
.flags
&(kGhostCfDontRun
|kGhostCfDisable
)))
777 return; // dont run anything if we are non-physical, or dontrun|disabled
780 BOOL gravity
= pGL
->info
.last
.pos
.flags
& kGhostHBGravity
;
782 _GhostApproxPhys(pGL
->obj
,&pGL
->info
.last
,&pGL
->info
.pred
,dt
,gravity
);
783 if (!_is_zero_vec(&pGL
->info
.pred
.pos
.vel
)) // hmmm, perhaps the right thing
784 _GhostBleedVelocity(pGL
->obj
, &pGL
->info
.pred
, pGL
->cfg
.flags
, pGL
->info
.last
.time
, dt
);
786 if (_IsNewPacketNeeded(pGL
,&new_pos
))
788 if (_ghost_explain_sends()) // show reasons for the new packet
789 _ghost_mprintf(("ghost %s sending reason %d time %d\n", ObjWarnName(pGL
->obj
), _ghost_new_packet_reason
, GetSimTime()));
790 _ghost_histo_add(pGL
->obj
,_ghost_new_packet_reason
,GetSimTime());
791 _GhostSendHeartbeat(pGL
,&new_pos
);
793 pGL
->last_fr
=GetSimTime();
796 static void _GhostWeaponCallback(eWeaponEvent event
, ObjID victim
, ObjID culprit
, void *data
)
798 _GhostDebugSetupObj(culprit
);
799 if (IsLocalGhost(culprit
))
801 if (event
==kStartAttack
)
803 sGhostLocal
*pGL
=GhostGetLocal(culprit
);
804 pGL
->state
|=kGhostStSwinging
;
805 if (_ghost_track_weapons())
806 _ghost_mprintf(("ghost setting StartAttack for %s (%x)\n",ObjWarnName(culprit
),pGL
->state
));
808 else if (event
==kEndAttack
)
810 sGhostLocal
*pGL
=GhostGetLocal(culprit
);
811 pGL
->state
&=~kGhostStSwinging
;
812 if (_ghost_track_weapons())
813 _ghost_mprintf(("ghost clearing StartAttack for %s (%x)\n",ObjWarnName(culprit
),pGL
->state
));
817 else if (IsRemoteGhost(culprit
))
819 if (_ghost_track_weapons())
820 _ghost_mprintf(("GWC: seeing %x for %s\n",event
,ObjWarnName(culprit
)));
825 // get the appagg to get the aimanager
826 void GhostSendInit(void)
828 g_pAINetServ
= AppGetObj(IAINetServices
);
829 RegisterWeaponEventCallback(kStartEndEvents
,_GhostWeaponCallback
,NULL
);
830 _ghost_histo_init(NUM_SEND_REASONS
,4000); // 4s is "long"
832 config_get_int("ghost_local_frame_rate",&ghost_local_frame_rate
);
834 #ifdef GHOST_DEBUGGING
835 sGhostHeartbeat HBTest
; // horrible hack for compiler ordering?
836 HBTest
.angle_info
.fac
.tz
=0xdead;
837 AssertMsg(HBTest
.angle_info
.tz
==0xdead,"Ghost TZ Union alignment failure!!");
841 void GhostSendTerm(void)
843 SafeRelease(g_pAINetServ
);
844 DeregisterWeaponEventCallback(kStartEndEvents
,_GhostWeaponCallback
);
846 #ifdef MINIPACKET_STATS
849 _ghost_mprintf(("\nGhostAllow mini %d, rot %d, ai %d/%d, obj %d/%d out of %d ng (%d total)\n",
850 allow_mini_hb
, allow_rot_hb
, allow_ai_hb
, nogo_ai_hb
, allow_obj_hb
, nogo_obj_hb
, total_ng_hb
, total_hb
));
851 _ghost_mprintf(("Failures dp %d dpZ %d dvZ %d magvZ %d dFac %d magdtZ %d flags %d dv %d dpos %d (play mdtz %d)\n\n",
852 failed_dp
, failed_dpZ
, failed_dvZ
, failed_mag_vZ
, failed_dFac
, failed_mag_dtz
, failed_flags
, failed_dvel
, failed_dpos
, failed_player_mag_dtz
));