convert line ends
[canaan.git] / prj / cam / src / sim / ghostsnd.cpp
blob29d205df40fe35bbb8ee1f325aec2bc5dbdd4cd9
1 /*
2 @Copyright Looking Glass Studios, Inc.
3 1996,1997,1998,1999,2000 Unpublished Work.
4 */
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
9 #include <appagg.h>
11 #include <ghost.h>
12 #include <ghostapi.h>
13 #include <ghostmsg.h>
14 #include <ghostphy.h>
15 #include <ghostlst.h>
16 #include <ghostsnd.h>
17 #include <ghostrcv.h>
18 #include <ghostmvr.h>
19 #include <ghosthst.h>
21 #include <simtime.h>
22 #include <config.h>
24 #include <physapi.h>
25 #include <phcore.h>
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
30 #include <objpos.h>
31 #include <plyrmode.h> // umm, player mode
32 #include <wrtype.h> // for Position, for ObjPos stuff
33 #include <aiman.h> // argh
34 #include <motmngr.h>
35 #include <motset.h>
37 #include <weapcb.h> // so we can track weapon state, to include it in packets
39 #include <dbmem.h>
41 //////////////////////////////
42 // globals
44 // really, if we punt impulse, we can just give up on this...
45 static IAINetServices *g_pAINetServ=NULL;
47 // from ghostapi.h
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)
56 if (eps==0)
57 return ((vec1->tx == vec2->tx) &&
58 (vec1->ty == vec2->ty) &&
59 (vec1->tz == vec2->tz));
60 else
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);
73 if (is_gloco)
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)));
81 else
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;
96 #endif
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;
112 BOOL allowed=FALSE;
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; }
122 if (!allowed)
124 if (_GhostIsType(pGL->cfg.flags,AI))
125 nogo_ai_hb++;
126 if (_GhostIsType(pGL->cfg.flags,IsObj))
127 nogo_obj_hb++;
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++;
132 if (!no_magdtz)
134 failed_mag_dtz++;
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++;
150 total_ng_hb++;
151 #endif
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? ???
161 if (same_flags&&
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?
165 sGhostRotMiniHB RMH;
166 RMH.angle_info.fac=pNew->pos.angle_info.fac;
167 g_pGhostRotHeartbeatNGMsg->Send(OBJ_NULL,pGL->obj,pGL->info.seq_id,&RMH);
168 return;
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)
178 sGhostAIMiniHB AHB;
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);
183 return;
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);
196 return;
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))
203 sGhostObjMiniHB OHB;
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);
207 return;
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
231 total_hb++;
232 #endif
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 ////////////////////////
261 // Frame Control
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
267 // type
268 // detail slider
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
279 // type
280 return _ghost_HF_Min[_GhostGetType(pGL->cfg.flags)];
284 ////////////////////////////////////////
285 // detail systems
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;
323 mxs_ang last_tz;
325 if (pDtz->s_cnt)
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;
331 else
333 last_tz=pDtz->samples[prev_samp].s_tz;
335 else
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;
357 if (cur_dtz==0)
358 if (pDtz->streak>0) pDtz->streak=-1;
359 else pDtz->streak--;
360 else
361 if (pDtz->streak<0) pDtz->streak= 1;
362 else pDtz->streak++;
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));
370 return real_dtz;
373 #ifdef GHOST_DEBUGGING
374 static int _ghost_new_packet_reason;
375 #define Return_NeedPacketReason(reason) { _ghost_new_packet_reason=reason; return TRUE; }
376 #else
377 #define _ghost_new_packet_reason 0
378 #define Return_NeedPacketReason(reason) return TRUE
379 #endif
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);
387 if (pModel==NULL)
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)));
390 return FALSE;
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));
399 return FALSE;
402 pNew->pos.flags = 0;
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));
422 else
423 pNew->pos.mode = 0;
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
444 sAIImpulse ourImp;
445 g_pAINetServ->GetTargetVel(pGL->obj,&ourImp);
446 #if 0
447 if (pGL->state&kGhostStSwinging) // dont look at impulse during combat???
448 mx_zero_vec(&pNew->pos.vel); // probably a bad idea...
449 else
450 #endif
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();
457 else
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;
476 else
477 pNew->pos.angle_info.p = 0;
479 else
480 pNew->pos.flags |= kGhostHBFullAngs;
482 else
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;
490 else
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);
523 #endif
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
533 ///////////////
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);
551 // check mode change
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;
591 else
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);
610 // check flag
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())
619 return FALSE;
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);
630 ////////////////
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;
646 _GhostClearDtz(pGL);
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);
654 ////////////////
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);
662 // check timeout
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);
721 else
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);
761 return FALSE;
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
778 sGhostPos new_pos;
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));
816 #ifdef DBG_ON
817 else if (IsRemoteGhost(culprit))
819 if (_ghost_track_weapons())
820 _ghost_mprintf(("GWC: seeing %x for %s\n",event,ObjWarnName(culprit)));
822 #endif
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!!");
838 #endif
841 void GhostSendTerm(void)
843 SafeRelease(g_pAINetServ);
844 DeregisterWeaponEventCallback(kStartEndEvents,_GhostWeaponCallback);
845 _ghost_histo_term();
846 #ifdef MINIPACKET_STATS
847 if (total_hb)
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));
854 #endif