2 @Copyright Looking Glass Studios, Inc.
3 1996,1997,1998,1999,2000 Unpublished Work.
6 ///////////////////////////////////////////////////////////////////////////////
7 // $Header: r:/t2repos/thief2/src/ai/aimovmot.cpp,v 1.60 2000/02/11 18:27:52 bfarquha Exp $
9 // Motion components of cAIMoveEnactor
12 // #define PROFILE_ON 1
27 #ifdef INCORRECT_AI_NETWORKING
46 // Must be last header
49 ///////////////////////////////////////////////////////////////////////////////
51 // CLASS: cAIMoveEnactor
54 void cAIMoveEnactor::MotCleanup()
58 delete m_pMotionCoord
;
59 m_pMotionCoord
= NULL
;
63 ///////////////////////////////////////
65 STDMETHODIMP_(void) cAIMoveEnactor::OnSimStart()
67 #ifdef INCORRECT_AI_NETWORKING
68 if (!AccessOuterAI()->IsNetworkProxy())
69 g_pPhysAICollideProp
->Set(GetID(), FALSE
);
73 ///////////////////////////////////////
75 void cAIMoveEnactor::MotOnModeChange(eAIMode previous
, eAIMode mode
)
77 IMotor
* pMotor
= CreatureGetMotorInterface(GetID());
79 // If we're entering combat, turn on collidability on,
80 // otherwise if it was on, turn it off
81 // (For the sake of network message bandwidth, we don't always turn it off).
82 if (mode
== kAIM_Combat
)
83 g_pPhysAICollideProp
->Set(GetID(), TRUE
);
84 else if (previous
== kAIM_Combat
)
85 g_pPhysAICollideProp
->Set(GetID(), FALSE
);
87 if (previous
== kAIM_Efficient
)
89 cCreature
* pCreature
= CreatureFromObj(GetID());
90 Assert_(pCreature
!=NULL
);
91 CreatureMakeNonBallistic(GetID());
92 // @HACK: yep, this is a total hack. We don't want our ballistic creatures falling
93 // to the floor when we go out of efficiency mode
94 if ((!pCreature
->GetCreatureDesc()->alwaysBallistic
) && pCreature
->IsPhysical())
96 // @TBD (toml 08-27-98): get gravity from room
97 PhysSetGravity(GetID(), 1.0);
98 PhysSetBaseFriction(GetID(), 0.0);
100 //mprint("Non ballistic!\n");
105 #ifndef NO_NON_PHYSICAL_SLEEP
106 if (mode
<= kAIM_SuperEfficient
)
108 pMotor
->MakeNonPhysical();
109 //mprint("Non phys!\n");
111 else if (mode
!= kAIM_Dead
)
113 pMotor
->MakePhysical();
117 // if (mode == kAIM_Dead)
118 // pMotor->MakeNonPhysical();
122 if (mode
== kAIM_Efficient
)
124 CreatureMakeBallistic(GetID(), kCMB_Efficient
);
125 cCreature
* pCreature
= CreatureFromObj(GetID());
126 if (pCreature
->IsPhysical())
128 SetObjImpulse(GetID(), 0, 0, 0, 0);
129 if (!pCreature
->GetCreatureDesc()->alwaysBallistic
)
131 PhysSetGravity(GetID(), 0);
132 PhysSetBaseFriction(GetID(), 1.0);
135 //mprint("Ballistic!\n");
139 ///////////////////////////////////////
141 void cAIMoveEnactor::MotorStateChangeCallback(void * p
)
145 ///////////////////////////////////////
147 BOOL
cAIMoveEnactor::MotSave(ITagFile
*pTagFile
)
150 m_pMotionCoord
->Save(pTagFile
);
154 ///////////////////////////////////////
156 BOOL
cAIMoveEnactor::MotLoad(ITagFile
*pTagFile
)
159 m_pMotionCoord
->Load(pTagFile
);
163 ///////////////////////////////////////
165 STDMETHODIMP_(BOOL
) cAIMoveEnactor::SupportsMotionActions()
167 return !!m_pMotionCoord
;
170 ///////////////////////////////////////
172 HRESULT
cAIMoveEnactor::SetMotor(IMotor
* pMotor
)
174 // this assertion is here just to sanity-check that
175 // motion system enum and ai enums are in synch, since
176 // one gets cast to the other. Would have this in
177 // constructor, but constructor is inline, and this
178 // function doesn't get called very often. KJ 5/20/98
179 AssertMsg(kAIME_Error
==kMCoord_Error
,"mcoord and ai status enums out of synch");
185 m_pMotionCoord
->SetMotor(NULL
);
186 delete m_pMotionCoord
;
187 m_pMotionCoord
= NULL
;
194 m_pMotionCoord
= MotSysCreateMotionCoordinator();
195 m_pMotionCoord
->SetMotorStateChangeCallback(MotorStateChangeCallback
,(void *)this);
198 m_pMotionCoord
->SetMotor(pMotor
);
203 ///////////////////////////////////////
205 HRESULT
cAIMoveEnactor::ResetMotionTags()
210 sMotActorTagList
*actorTagList
;
213 if(!ObjGetActorTagList(m_pAIState
->GetID(), &actorTagList
))
215 m_pMotionCoord
->SetPersistentTags(NULL
);
219 tags
.FromString(actorTagList
->m_TagStrings
);
220 m_pMotionCoord
->SetPersistentTags(&tags
);
225 ///////////////////////////////////////
230 eAIMEStatus
cAIMoveEnactor::MotStatus()
232 eAIMEStatus status
= eAIMEStatus(m_pMotionCoord
->GetStatus());
233 if (!CreatureSelfPropelled(GetID()))
235 if (status
== kAIME_ActiveBusy
&& m_ImpulsePeriod
.Expired())
236 status
= kAIME_ActiveInterrupt
;
241 ///////////////////////////////////////
243 HRESULT
cAIMoveEnactor::MotNoAction(ulong deltaTime
)
246 sAIMoveGoal nullGoal
;
248 BOOL refreshBallistic
= FALSE
;
250 if (m_pAIState
->GetMode() <= kAIM_Efficient
)
253 status
= MotStatus();
254 // We only play "stand" motions if not efficient, and the coordinator
255 // is idle, and we either didn't play one as our last act or the
256 // AI is actually being rendered (toml 11-03-98)
257 newStandGoal
= (status
== kMCoord_Idle
) && (!(m_flags
&kLastWasNoAction
) || (m_StandTimer
.Expired() && rendobj_object_visible(GetID())));
258 // If we're not ready to "re-stand" then check if we need to update our ballistic creature
260 refreshBallistic
= !CreatureSelfPropelled(GetID()) && (status
== kAIME_ActiveInterrupt
);
261 if (newStandGoal
|| refreshBallistic
)
263 nullGoal
.dir
= m_pAIState
->GetFacingAng();
264 nullGoal
.dest
= *m_pAIState
->GetLocation();
268 m_flags
|= kLastWasNoAction
;
269 m_StandTimer
.Reset();
270 return MotEnactMoveGoal(nullGoal
, cTagSet(), deltaTime
);
272 else if (refreshBallistic
)
273 return NonMotEnactMoveGoal(nullGoal
, deltaTime
);
277 ///////////////////////////////////////
279 // Enact a locomotion
282 DECLARE_TIMER(cAIMoveEnactor_MotEnact
, Average
);
284 HRESULT
cAIMoveEnactor::MotEnact(cAIMoveAction
* pAction
, ulong deltaTime
)
286 AUTO_TIMER(cAIMoveEnactor_MotEnact
);
288 if (m_pAIState
->GetMode() <= kAIM_Efficient
)
292 if (m_pMotionCoord
->GetStatus() == kMCoord_ActiveBusy
)
295 m_flags
&= ~kLastWasNoAction
;
296 m_StandTimer
.Force();
297 return MotEnactMoveGoal(pAction
->GetMoveGoal(), pAction
->GetTags(), deltaTime
);
300 ///////////////////////////////////////
302 // Locomotive goal primitive
305 #define STATIONARY_ERROR (MX_ANG_PI / 4)
306 #define STATIONARY_POS_ERROR_SQ 4.0
308 DECLARE_TIMER(AI_AIME_MEMG
, Average
);
309 DECLARE_TIMER(AI_AIME_BP_LOC
, Average
);
311 HRESULT
cAIMoveEnactor::MotEnactMoveGoal(const sAIMoveGoal
& inputGoal
, const cTagSet
& tags
, ulong deltaTime
)
313 eAIMEStatus motionStatus
= (eAIMEStatus
)m_pMotionCoord
->GetStatus();
314 sAIMoveGoal goal
= inputGoal
;
316 if (motionStatus
== kAIME_Idle
|| motionStatus
== kAIME_ActiveInterrupt
)
318 AUTO_TIMER(AI_AIME_MEMG
);
320 for (int i
= 0; i
< m_MoveRegulators
.Size(); i
++)
321 m_MoveRegulators
[i
]->AssistGoal(inputGoal
, &goal
);
323 #ifdef INCORRECT_AI_NETWORKING
324 #ifdef NEW_NETWORK_ENABLED
325 AutoAppIPtr(NetManager
);
326 AutoAppIPtr_(ObjectNetworking
, pObjNet
);
327 if (pNetManager
->Networking() && pObjNet
->ObjHostedHere(m_pAIState
->GetID())) // @TODO && !m_pAIState->NetworkingComplexAction()
328 BroadcastMove(goal
, tags
, deltaTime
);
332 const cMxsVector
& loc
= *m_pAIState
->GetLocation();
333 sMcMoveParams params
;
335 ConfigSpew("AIEnactTrace",("MotEnactMoveGoal"));
338 params
.direction
.tz
= (mxs_ang
)((goal
.dir
.value
/TWO_PI
)*0xffff);
339 params
.direction
.tx
= params
.direction
.ty
=0;
340 params
.duration
= deltaTime
;
342 if (goal
.speed
> kAIS_Stopped
)
344 params
.tags
.Add(cTag("Locomote", kMTV_set
));
346 if (goal
.speed
>= kAIS_Fast
)
347 params
.tags
.Add(cTag("LocoUrgent", kMTV_set
));
350 mx_copy_vec(&tmp
,&goal
.dest
);
351 ConfigSpew("CerebllmStride",("%d dest: %g, %g\n",m_pAIState
->GetID(),tmp
.x
,tmp
.y
));
352 mx_subeq_vec(&tmp
,&loc
);
354 params
.distance
=mx_mag_vec(&tmp
);
355 params
.mask
|=kMotParmFlag_Distance
;
356 ConfigSpew("CerebllmStride",("%d curp: %g, %g\n%d delt: %g, %g\n",\
357 m_pAIState
->GetID(),loc
.x
,loc
.y
,m_pAIState
->GetID(),tmp
.x
,tmp
.y
));
361 cTagSet
standTags(AIStandTags(GetID()));
362 if (standTags
.Size())
363 params
.tags
.Append(standTags
);
365 params
.tags
.Append(tags
);
367 // only set distance if velocity non-zero
368 params
.mask
|=(kMotParmFlag_Direction
|kMotParmFlag_Duration
);
370 if (goal
.facing
.type
!= kAIF_Any
)
372 params
.mask
|=kMotParmFlag_Facing
;
373 params
.facing
.tx
=params
.facing
.ty
=0;
374 params
.facing
.tz
=(mxs_ang
)((ComputeFacing(goal
).value
/TWO_PI
)*0xffff);
377 const char * pszTagsProp
= AIGetMotionTags(GetID());
380 params
.tags
.Append(cTagSet(pszTagsProp
));
382 TIMER_Start(AI_AIME_BP_LOC
);
384 IMotionPlan
* pPlan
= m_pMotionCoord
->BuildPlan(m_pMotionCoord
->GetNextEndState(), ¶ms
);
388 IManeuver
*pManeuver
= pPlan
->PopFirstManeuver();
389 m_pMotionCoord
->SetCurrentManeuver(pManeuver
);
390 ConfigSpew("CerebellumTrace",("%d: setting maneuver\n",m_pAIState
->GetID()));
393 TIMER_MarkStop(AI_AIME_BP_LOC
);
401 if (params
.tags
.Size())
403 params
.tags
.ToString(&tagString
);
404 Warning(("Unable to build motion plan for obj %d with tags {%s}\n",GetID(),tagString
));
407 TIMER_MarkStop(AI_AIME_BP_LOC
);
413 // If the motion system wont move us, use wedge movement
414 if (m_ImpulsePeriod
.Expired() && !CreatureSelfPropelled(GetID()))
415 // @TBD (toml 05-21-99): this appears to cause a double assist goal?
416 return NonMotEnactMoveGoal(inputGoal
, deltaTime
);
419 AutoAppIPtr(AINetServices
);
420 if (pAINetServices
->Networking())
423 CalculateImpulse(goal
, deltaTime
, &impulse
);
424 CalculateTargetVel(goal
, deltaTime
, &impulse
);
425 memcpy(m_pTargetVel
, &impulse
, sizeof(impulse
));
432 //////////////////////////////////////////
434 #ifdef INCORRECT_AI_NETWORKING
435 // Non-guaranteed (NG) move message structure. Only used by the following two routines.
436 struct sAINetMsg_Move_NG
{
437 sAINetMsg_Header aihdr
;
440 char tagsetBlock
[1]; // Raw block for tagset
444 // Broadcast a move for the given goal and tags (not based on an action object).
445 void cAIMoveEnactor::BroadcastMove(const sAIMoveGoal
&goal
, const cTagSet
&tags
, ulong deltaTime
)
447 ConfigSpew("net_ai_spew", ("NET AI: Send move for %d\n", m_pAIState
->GetID()));
449 // Figure out the size of the tagset block, allocate an appropriate
450 // sized message, and fill in the tagset block.
451 void *pTagBlock
= NULL
;
452 int size
= TagSetToNetBlock(&tags
, &pTagBlock
);
453 AssertMsg(pTagBlock
, "Tag block is empty!");
454 // msgSize is the size of the message. We subtract 1 for the byte that
455 // we have to declare for tagsetBlock:
456 ulong msgSize
= sizeof(sAINetMsg_Move_NG
) + size
- 1;
457 sAINetMsg_Move_NG
*netmsg
= (sAINetMsg_Move_NG
*)malloc(msgSize
);
458 memcpy(netmsg
->tagsetBlock
, pTagBlock
, size
);
461 AutoAppIPtr(AINetManager
);
462 sAINetMsg_Header hdr
= {pAINetManager
->NetMsgHandlerID(),
467 netmsg
->deltaTime
= deltaTime
;
470 AutoAppIPtr(NetManager
);
471 pNetManager
->Broadcast(netmsg
, msgSize
, FALSE
);
475 // Enact a move received off the network. No action is created for it.
476 STDMETHODIMP_(void) cAIMoveEnactor::HandleMoveMessage(void *pMsg
,
479 ConfigSpew("net_ai_spew",
480 ("NET AI: Receive move for %d\n", m_pAIState
->GetID()));
482 sAINetMsg_Move_NG
*netmsg
= (sAINetMsg_Move_NG
*)pMsg
;
483 // Initialize with a string, because the stupid compiler chokes if
484 // we use the empty constructor:
486 NetBlockToTagSet(netmsg
->tagsetBlock
, fromPlayer
, &tags
);
488 // Get the goal, tags & delta Time out of netmsg.
489 MotEnactMoveGoal(netmsg
->goal
, tags
, netmsg
->deltaTime
);
493 ///////////////////////////////////////
497 // @Note (toml 05-19-98): Right now, all motions interrupt
499 DECLARE_TIMER(AI_AIME_ME_M
, Average
);
500 DECLARE_TIMER(AI_AIME_BP_MOT
, Average
);
502 HRESULT
cAIMoveEnactor::MotEnact(cAIMotionAction
* pAction
, ulong deltaTime
)
504 if ((m_pAIState
->GetMode() > kAIM_Efficient
) && !pAction
->Started())
506 if (!pAction
->GetName() && !pAction
->GetTags().Size())
507 return MotNoAction(deltaTime
);
509 AUTO_TIMER(AI_AIME_ME_M
);
511 m_flags
&= ~kLastWasNoAction
;
512 m_StandTimer
.Force();
514 sMcMoveParams params
;
516 params
.tags
= pAction
->GetTags();
519 if (pAction
->IsFacingSet())
521 params
.facing
.tz
= (mxs_ang
)((pAction
->GetFacing().value
/TWO_PI
)*0xffff);
522 params
.facing
.tx
= params
.facing
.ty
= 0;
523 params
.mask
|= kMotParmFlag_Facing
;
526 if(pAction
->GetFocus(¶ms
.focus
, ¶ms
.turnspeed
))
528 params
.mask
|= kMotParmFlag_FocusObj
;
531 if (pAction
->GetName())
533 params
.motionNum
= MotDescNameGetNum((char *)pAction
->GetName());
534 params
.mask
|= kMotParmFlag_MotNum
;
535 if (params
.motionNum
< 0)
537 params
.tags
.Append(cTag("PlaySpecMotion",kMTV_true
));
540 const char * pszTagsProp
= AIGetMotionTags(GetID());
543 params
.tags
.Append(cTagSet(pszTagsProp
));
545 #ifdef INCORRECT_AI_NETWORKING
546 #ifdef NEW_NETWORK_ENABLED
547 AutoAppIPtr(NetManager
);
548 AutoAppIPtr_(ObjectNetworking
, pObjNet
);
549 if (pNetManager
->Networking() && pObjNet
->ObjHostedHere(m_pAIState
->GetID())) // @TODO && !m_pAIState->NetworkingComplexAction()
550 BroadcastMotion(params
);
554 return MotEnactMotion(params
);
556 if (m_ImpulsePeriod
.Expired() && !CreatureSelfPropelled(GetID()))
558 sAIMoveGoal nullGoal
;
560 if (pAction
->IsFacingSet())
561 nullGoal
.dir
= pAction
->GetFacing();
563 nullGoal
.dir
= m_pAIState
->GetFacingAng();
564 nullGoal
.dest
= *m_pAIState
->GetLocation();
565 return NonMotEnactMoveGoal(nullGoal
, deltaTime
);
571 HRESULT
cAIMoveEnactor::MotEnactMotion(const sMcMoveParams
¶ms
)
573 TIMER_Start(AI_AIME_BP_MOT
);
575 const sMcMoveState
* pState
= m_pMotionCoord
->GetNextEndState();
576 IMotionPlan
*pPlan
= m_pMotionCoord
->BuildPlan(pState
, ¶ms
);
582 params
.tags
.ToString(&tagString
);
583 Warning(("Unable to build motion plan for obj %d with tags {%s}\n",m_pAIState
->GetID(),tagString
));
585 TIMER_MarkStop(AI_AIME_BP_MOT
);
590 IManeuver
*pManeuver
= pPlan
->PopFirstManeuver();
591 m_pMotionCoord
->SetCurrentManeuver(pManeuver
);
594 TIMER_MarkStop(AI_AIME_BP_MOT
);
599 ///////////////////////////////////////
601 #ifdef INCORRECT_AI_NETWORKING
602 // Non-guaranteed (NG) motion message structure. Only used by the following two routines.
603 struct sAINetMsg_Motion_NG
{
604 sAINetMsg_Header aihdr
;
607 sGlobalObjID focusGID
;
609 char tagsetBlock
[1]; // Raw block for tagset
612 // Broadcast a motion for the given params.
613 void cAIMoveEnactor::BroadcastMotion(const sMcMoveParams params
)
615 ConfigSpew("net_ai_spew", ("NET AI: Send motion for %d\n", m_pAIState
->GetID()));
617 // Figure out the size of the tagset block, allocate an appropriate
618 // sized message, and fill in the tagset block.
619 void *pTagBlock
= NULL
;
620 int size
= TagSetToNetBlock(&(params
.tags
), &pTagBlock
);
621 AssertMsg(pTagBlock
, "Tag block is empty!");
622 // msgSize is the size of the message. We subtract 1 for the bytes that
623 // we have to declare for tagsetBlock:
624 ulong msgSize
= sizeof(sAINetMsg_Motion_NG
) + size
- 1;
625 sAINetMsg_Motion_NG
*netmsg
= (sAINetMsg_Motion_NG
*)malloc(msgSize
);
626 memcpy(netmsg
->tagsetBlock
, pTagBlock
, size
);
629 AutoAppIPtr(AINetManager
);
630 AutoAppIPtr(NetManager
);
631 sAINetMsg_Header hdr
= {pAINetManager
->NetMsgHandlerID(), m_pAIState
->GetID(), FALSE
, kAIAT_Motion
};
633 netmsg
->mask
= params
.mask
;
634 if (params
.mask
& kMotParmFlag_Facing
)
635 netmsg
->facing_tz
= params
.facing
.tz
;
636 if (params
.mask
& kMotParmFlag_FocusObj
)
637 netmsg
->focusGID
= pNetManager
->ToGlobalObjID(params
.focus
);
638 if (params
.mask
& kMotParmFlag_MotNum
)
639 netmsg
->motionNum
= params
.motionNum
;
641 pNetManager
->Broadcast(netmsg
, msgSize
, FALSE
);
645 ///////////////////////////////////////
647 // Enact a motion based on the net message. No action is created.
648 STDMETHODIMP_(void) cAIMoveEnactor::HandleMotionMessage(void *pMsg
,
651 ConfigSpew("net_ai_spew", ("NET AI: Receive motion for %d\n", m_pAIState
->GetID()));
653 sAINetMsg_Motion_NG
*netmsg
= (sAINetMsg_Motion_NG
*)pMsg
;
655 // Get the motion parameters out of netmsg.
657 sMcMoveParams params
;
659 NetBlockToTagSet(netmsg
->tagsetBlock
, fromPlayer
, &tags
);
661 // Copy the mask and the optional fields.
662 params
.mask
= netmsg
->mask
;
663 if (params
.mask
& kMotParmFlag_Facing
)
665 params
.facing
.tz
= netmsg
->facing_tz
;
666 params
.facing
.tx
= params
.facing
.ty
= 0;
668 if (params
.mask
& kMotParmFlag_FocusObj
)
670 AutoAppIPtr(NetManager
);
671 params
.focus
= pNetManager
->FromGlobalObjID(&netmsg
->focusGID
);
673 if (params
.mask
& kMotParmFlag_MotNum
)
674 params
.motionNum
= netmsg
->motionNum
;
676 // Now do the motion on the proxy ai.
677 MotEnactMotion(params
);
681 ///////////////////////////////////////
686 HRESULT
cAIMoveEnactor::MotEnact(cAIOrientAction
* pAction
, ulong deltaTime
)
688 m_flags
&= ~kLastWasNoAction
;
689 m_StandTimer
.Force();
690 // @TBD (toml 04-10-98):