Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/modules/objects/triggers/MultiTrigger.cc @ 11226

Last change on this file since 11226 was 11099, checked in by muemart, 9 years ago

Fix loads of doxygen warnings and other documentation issues

  • Property svn:eol-style set to native
File size: 22.8 KB
Line 
1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *      Damian 'Mozork' Frick
24 *   Co-authors:
25 *      ...
26 *
27*/
28
29/**
30    @file MultiTrigger.cc
31    @brief Implementation of the MultiTrigger class.
32    @ingroup MultiTrigger
33*/
34
35#include "MultiTrigger.h"
36
37#include "core/CoreIncludes.h"
38#include "core/XMLPort.h"
39
40#include "MultiTriggerContainer.h"
41
42namespace orxonox
43{
44
45    RegisterClass(MultiTrigger);
46
47    /**
48    @brief
49        Constructor. Registers the objects and initializes default values.
50    */
51    MultiTrigger::MultiTrigger(Context* context) : TriggerBase(context)
52    {
53        RegisterObject(MultiTrigger);
54
55        this->maxNumSimultaneousTriggerers_ = INF_s;
56
57        this->bBroadcast_ = false;
58
59        this->targetMask_.exclude(Class(BaseObject));
60
61        this->bMultiTrigger_ = true;
62
63        this->setSyncMode(ObjectDirection::None);
64    }
65
66    /**
67    @brief
68        Destructor. Cleans up the state queue.
69    */
70    MultiTrigger::~MultiTrigger()
71    {
72        orxout(verbose, context::triggers) << "Destroying MultiTrigger &" << this << ". " << this->stateQueue_.size() << " states still in queue. Deleting." << endl;
73        while(this->stateQueue_.size() > 0)
74        {
75            MultiTriggerState* state = this->stateQueue_.front().second;
76            this->stateQueue_.pop_front();
77            delete state;
78        }
79    }
80
81    /**
82    @brief
83        Method for creating a MultiTrigger object through XML.
84        For a detailed description of the parameters please see the class description in the header file.
85    */
86    void MultiTrigger::XMLPort(Element& xmlelement, XMLPort::Mode mode)
87    {
88        SUPER(MultiTrigger, XMLPort, xmlelement, mode);
89
90        XMLPortParam(MultiTrigger, "simultaneousTriggerers", setSimultaneousTriggerers, getSimultaneousTriggerers, xmlelement, mode);
91        XMLPortParam(MultiTrigger, "broadcast", setBroadcast, getBroadcast, xmlelement, mode);
92        XMLPortParamLoadOnly(MultiTrigger, "target", addTarget, xmlelement, mode).defaultValues("Pawn"); //TODO: Remove load only
93
94        orxout(verbose, context::triggers) << "MultiTrigger '" << this->getName() << "' (&" << this << ") created." << endl;
95    }
96
97
98    /**
99    @brief
100        A method that is executed each tick.
101        Most of the magic of MultiTriggers happens here.
102    @param dt
103        The duration of the last tick.
104    */
105    //TODO: Segment into some helper methods?
106    void MultiTrigger::tick(float dt)
107    {
108        // If this is the first tick.
109        if(this->bFirstTick_)
110        {
111            this->bFirstTick_ = false;
112            // Fire for all objects that are targets.
113            this->broadcast(false);
114        }
115
116        // Check if the object is active (this is NOT MultiTrigger::isActive()!), it is whether the MultiTrigger actually does anything, ever.
117        if (!this->BaseObject::isActive())
118            return;
119
120        SUPER(MultiTrigger, tick, dt);
121
122        // Let the MultiTrigger return the states that trigger and process the new states if there are any.
123        std::queue<MultiTriggerState*>* queue  = this->letTrigger();
124        if(queue != nullptr)
125        {
126            while(queue->size() > 0)
127            {
128                MultiTriggerState* state = queue->front();
129                // If the state is nullptr. (This really shouldn't happen)
130                if(state == nullptr)
131                {
132                    orxout(internal_error, context::triggers) << "In MultiTrigger '" << this->getName() << "' (&" << this << "), Error: State of new states queue was nullptr. State ignored." << endl;
133                    queue->pop();
134                    continue;
135                }
136
137                // The new triggered state dependent on the requested state, the mode and the invert-mode.
138                bool bTriggered = (state->bTriggered & this->isModeTriggered(state->originator)) ^ this->getInvert();
139
140                // If the 'triggered' state has changed or the MultiTrigger has delay and thus we don't know whether this state will actually change the 'triggered' state, a new state is added to the state queue.
141                if(this->getDelay() > 0.0f || bTriggered ^ this->isTriggered(state->originator))
142                {
143                    state->bTriggered = bTriggered;
144                    this->addState(state);
145                }
146                // Else the change is irrelevant.
147                else
148                    delete state;
149
150                queue->pop();
151            }
152            delete queue;
153        }
154
155        // Go through the state queue and activate all pending states whose remaining time has expired.
156        if(this->stateQueue_.size() > 0)
157        {
158            MultiTriggerState* state;
159            float timeRemaining;
160
161            // Go through all pending states.
162            for(int size = this->stateQueue_.size(); size >= 1; size--)
163            {
164                timeRemaining = this->stateQueue_.front().first;
165                state = this->stateQueue_.front().second;
166
167                // If the remaining time has expired, the state has to be set as the state of the MultiTrigger.
168                if(timeRemaining <= dt)
169                {
170                    // If the maximum number of objects simultaneously triggering this MultiTrigger is not exceeded.
171                    if(this->getSimultaneousTriggerers() == TriggerBase::INF_s || this->triggered_.size() < (unsigned int)this->getSimultaneousTriggerers())
172                    {
173                        bool bStateChanged = false;
174                        // If the 'triggered' state is different from what it is now, change it.
175                        if(state->bTriggered ^ this->isTriggered(state->originator))
176                        {
177                            // Add the originator to the objects triggering this MultiTrigger.
178                            if(state->bTriggered == true)
179                                this->triggered_.insert(state->originator);
180                            // Remove the originator from the objects triggering this MultiTrigger.
181                            else
182                                this->triggered_.erase(state->originator);
183
184                            bStateChanged = true;
185                        }
186
187                        // Get the activity of the new state.
188                        bool bActive;
189                        // If the MultiTrigger is in switch mode the 'active'-state only changes of the state changed to triggered.
190                        if(this->getSwitch() && !state->bTriggered)
191                            bActive = this->isActive(state->originator);
192                        else
193                            bActive = !this->isActive(state->originator);
194
195                        // If the activity is different from what it is now, change it and fire an Event.
196                        if(bActive ^ this->isActive(state->originator))
197                        {
198                            bool bFire = true;
199
200                            // Add the originator to the objects activating this MultiTrigger.
201                            if(bActive == true)
202                            {
203                                // If the MultiTrigger has not exceeded its remaining activations.
204                                if(this->hasRemainingActivations())
205                                {
206                                    this->active_.insert(state->originator);
207                                    if(this->remainingActivations_ != INF_s)
208                                        this->remainingActivations_--; // Decrement the remaining activations.
209                                }
210                                else
211                                    bFire = false;
212                            }
213                            // Remove the originator from the objects activating this MultiTrigger.
214                            else
215                            {
216                                // If the MultiTrigger doesn't stay active or hasn't' exceeded its remaining activations.
217                                if(!this->getStayActive() || this->hasRemainingActivations())
218                                    this->active_.erase(state->originator);
219                                else
220                                    bFire = false;
221                            }
222
223                            // Fire the Event if the activity has changed.
224                            if(bFire)
225                            {
226                                // If the MultiTrigger is set to broadcast and has no originator a boradcast is fired.
227                                if(this->getBroadcast() && state->originator == nullptr)
228                                    this->broadcast(bActive);
229                                // Else a normal event is fired.
230                                else
231                                    this->fire(bActive, state->originator);
232
233                                bStateChanged = true;
234                            }
235                        }
236
237                        if(bStateChanged)
238                        {
239                            // Print some debug output if the state has changed.
240                            if(state->originator != nullptr)
241                                orxout(verbose, context::triggers) << "MultiTrigger '" << this->getName() << "' (&" << this << ") changed state. originator: " << state->originator->getIdentifier()->getName() << " (&" << state->originator << "), active: " << bActive << ", triggered: " << state->bTriggered << "." << endl;
242                            else
243                                orxout(verbose, context::triggers) << "MultiTrigger '" << this->getName() << "' (&" << this << ") changed state. originator: nullptr, active: " << bActive << ", triggered: " << state->bTriggered << "." << endl;
244
245                            // If the MultiTrigger has a parent trigger, that is itself a MultiTrigger, it needs to call a method to notify him, that its activity has changed.
246                            if(this->parent_ != nullptr && this->parent_->isMultiTrigger())
247                                static_cast<MultiTrigger*>(this->parent_)->childActivityChanged(state->originator);
248                        }
249
250                        // If the MultiTrigger has exceeded its amount of activations and it doesn't stay active, it has to be deactivated.
251                        if(this->remainingActivations_ == 0 && !bActive)
252                        {
253                            this->BaseObject::setActive(false);
254                            orxout(verbose, context::triggers) << "MultiTrigger '" << this->getName() << "' (&" << this << ") ran out of activations. Setting it to inactive." << endl;
255                        }
256                    }
257
258                    // Remove the state from the state queue.
259                    this->stateQueue_.pop_front();
260                    delete state;
261                }
262                // If the remaining time has not yet expired. Decrement the remainig time and put the state at the end of the queue.
263                else
264                {
265                    this->stateQueue_.emplace_back(timeRemaining-dt, state);
266                    this->stateQueue_.pop_front();
267                }
268            }
269        }
270    }
271
272    /**
273    @brief
274        Check whether the MultiTrigger is active for a given object.
275    @param triggerer
276        A pointer to the object.
277    @return
278        Returns true if the MultiTrigger is active, false if not.
279    */
280    bool MultiTrigger::isActive(BaseObject* triggerer) const
281    {
282        std::set<BaseObject*>::const_iterator it = this->active_.find(triggerer);
283        if(it == this->active_.end())
284            return false;
285        return true;
286    }
287
288    /**
289    @brief
290        Add some target to the MultiTrigger.
291    @param targetStr
292        The target class name as a string.
293    */
294    void MultiTrigger::addTarget(const std::string& targetStr)
295    {
296        Identifier* target = ClassByString(targetStr);
297
298        // If the target is not a valid class name display an error.
299        if (target == nullptr)
300        {
301            orxout(internal_error, context::triggers) << "'" << targetStr << "' is not a valid class name to include in ClassTreeMask (in " << this->getName() << ", class " << this->getIdentifier()->getName() << ")" << endl;
302            return;
303        }
304
305        this->targetMask_.include(target);
306
307        // A MultiTrigger shouldn't react to itself or other triggers.
308        this->targetMask_.exclude(Class(TriggerBase), true);
309
310        // We only want WorldEntities
311        ClassTreeMask WEMask;
312        WEMask.include(Class(WorldEntity));
313        this->targetMask_ *= WEMask;
314    }
315
316    /**
317    @brief
318        Remove some target from the MultiTrigger.
319    @param targetStr
320        The target to be removed as a string.
321    */
322    void MultiTrigger::removeTarget(const std::string& targetStr)
323    {
324        Identifier* target = ClassByString(targetStr);
325
326        // If the target is not a valid class name display an error.
327        if (target == nullptr)
328        {
329            orxout(internal_error, context::triggers) << "'" << targetStr << "' is not a valid class name to include in ClassTreeMask (in " << this->getName() << ", class " << this->getIdentifier()->getName() << ")" << endl;
330            return;
331        }
332
333        this->targetMask_.exclude(target);
334    }
335
336    /**
337    @brief
338        This method is called by the MultiTrigger to get information about new trigger events that need to be looked at.
339        This method is the device for the behavior (the conditions under which the MultiTrigger triggers) of any derived class of MultiTrigger.
340    @return
341        Returns a pointer to a queue of MultiTriggerState pointers, containing all the necessary information to decide whether these states should indeed become new states of the MultiTrigger.
342        Please be aware that both the queue and the states in the queue need to be deleted once they have been used. This is already done in the tick() method of this class but would have to be done by any method calling this method.
343    */
344    std::queue<MultiTriggerState*>* MultiTrigger::letTrigger(void)
345    {
346        return nullptr;
347    }
348
349    /**
350    @brief
351        This method can be called by any class inheriting from MultiTrigger to change it's triggered status for a specified originator.
352
353        Compared to the letTrigger mode, which just polls and lets the pollee send it's state changed back, the changeTriggered method lets the trigger advertise its state changes just as they happen so it's much like the interrupt version of letTrigger.
354    @param originator
355        The originator which triggered the state change.
356    */
357    void MultiTrigger::changeTriggered(BaseObject* originator)
358    {
359        MultiTriggerState* state = new MultiTriggerState;
360        state->bTriggered = (!this->isTriggered(originator) & this->isModeTriggered(originator)) ^ this->getInvert();
361        state->originator = originator;
362        this->addState(state);
363    }
364
365    /**
366    @brief
367        This method is called by any child to advertise changes in its state to its parent.
368    @param originator
369        The object that caused the change in activity.
370    */
371    void MultiTrigger::childActivityChanged(BaseObject* originator)
372    {
373        MultiTriggerState* state = new MultiTriggerState;
374        state->bTriggered = (this->isTriggered(originator) & this->isModeTriggered(originator)) ^ this->getInvert();
375        state->originator = originator;
376        this->addState(state);
377    }
378
379    /**
380    @brief
381        Checks whether the children are in such a way that, according to the mode of the MultiTrigger, the MultiTrigger is triggered (only considering the children, not the state of MultiTrigger itself), for a given object.
382        To make an example: When the mode is <em>and</em>, then this would be true or a given object if all the children were triggered for the given object.
383    @param triggerer
384        The object.
385    @return
386        Returns true if the MultiTrigger is triggered concerning it's children.
387    */
388    bool MultiTrigger::isModeTriggered(BaseObject* triggerer)
389    {
390        if(this->children_.size() != 0)
391        {
392            bool triggered = false;
393
394            switch(this->mode_)
395            {
396                case TriggerMode::EventTriggerAND:
397                    triggered = checkAnd(triggerer);
398                    break;
399                case TriggerMode::EventTriggerOR:
400                    triggered = checkOr(triggerer);
401                    break;
402                case TriggerMode::EventTriggerXOR:
403                    triggered = checkXor(triggerer);
404                    break;
405                default: // This will never happen.
406                    triggered = false;
407                    break;
408            }
409
410            return triggered;
411        }
412
413        return true;
414    }
415
416    /**
417    @brief
418        Get whether the MultiTrigger is triggered for a given object.
419    @param triggerer
420        The object.
421    @return
422        Returns true if the MultiTrigger is triggered for the given object.
423    */
424    bool MultiTrigger::isTriggered(BaseObject* triggerer)
425    {
426        std::set<BaseObject*>::iterator it = this->triggered_.find(triggerer);
427        if(it == this->triggered_.end())
428            return false;
429        return true;
430    }
431
432    /**
433    @brief
434        Helper method. Creates an Event for the given status and originator and fires it.
435        Or more precisely creates a MultiTriggerContainer to encompass all neccesary information, creates an Event from that and sends it.
436    @param status
437        The status of the Event to be fired. This is equivalent to the activity of the MultiTrigger.
438    @param originator
439        The object that triggered the MultiTrigger to fire this Event.
440    */
441    void MultiTrigger::fire(bool status, BaseObject* originator)
442    {
443        // If the originator is nullptr, a normal event without MultiTriggerContainer is sent.
444        if(originator == nullptr)
445        {
446            this->fireEvent(status);
447            orxout(verbose, context::triggers) << "MultiTrigger '" <<  this->getName() << "' (&" << this << "): Fired event. status: " << status << "." << endl;
448            return;
449        }
450
451        MultiTriggerContainer* container = new MultiTriggerContainer(this->getContext(), this, originator);
452        this->fireEvent(status, container);
453        orxout(verbose, context::triggers) << "MultiTrigger '" <<  this->getName() << "' (&" << this << "): Fired event. originator: " << originator->getIdentifier()->getName() << " (&" << originator << "), status: " << status << "." << endl;
454        container->destroy();
455    }
456
457    /**
458    @brief
459        Helper method. Broadcasts an Event for every object that is a target.
460    @param status
461        The status of the Events to be fired. This is equivalent to the activity of the MultiTrigger.
462    */
463    void MultiTrigger::broadcast(bool status)
464    {
465        for(ClassTreeMaskObjectIterator it = this->getTargetMask().begin(); it != this->getTargetMask().end(); ++it)
466            this->fire(status, static_cast<BaseObject*>(*it));
467    }
468
469    /**
470    @brief
471        Helper method. Adds a state to the state queue, where the state will wait to become active.
472    @param state
473        The state to be added.
474    @return
475        Returns true if the state has been added, false if not. If the state has not been added this method destroys it.
476    */
477    bool MultiTrigger::addState(MultiTriggerState* state)
478    {
479        assert(state); // The state really shouldn't be nullptr.
480
481        // If the originator is no target of this MultiTrigger.
482        if(!this->isTarget(state->originator))
483        {
484            delete state;
485            return false;
486        }
487
488        // Add it ot the state queue with the delay specified for the MultiTrigger.
489        this->stateQueue_.emplace_back(this->getDelay(), state);
490
491        return true;
492    }
493
494    /**
495    @brief
496        Checks whether the children amount to true for the <em>and</em> mode for a given object.
497    @param triggerer
498        The object.
499    @return
500        Returns true if all the sub-triggers are active.
501    */
502    bool MultiTrigger::checkAnd(BaseObject* triggerer)
503    {
504        for(TriggerBase* trigger : this->children_)
505        {
506            if(trigger->isMultiTrigger())
507            {
508                if(!static_cast<MultiTrigger*>(trigger)->isActive(triggerer))
509                    return false;
510            }
511            else
512            {
513                if(!trigger->isActive())
514                    return false;
515            }
516        }
517        return true;
518    }
519
520    /**
521    @brief
522        Checks whether the children amount to true for the <em>or</em> mode for a given object.
523    @param triggerer
524        The object.
525    @return
526        Returns true if at least one sub-trigger is active.
527    */
528    bool MultiTrigger::checkOr(BaseObject* triggerer)
529    {
530        for(TriggerBase* trigger : this->children_)
531        {
532            if(trigger->isMultiTrigger())
533            {
534                if(static_cast<MultiTrigger*>(trigger)->isActive(triggerer))
535                    return true;
536            }
537            else
538            {
539                if(trigger->isActive())
540                    return true;
541            }
542        }
543        return false;
544    }
545
546    /**
547    @brief
548        Checks whether the children amount to true for the <em>xor</em> mode for a given object.
549    @param triggerer
550        The object.
551    @return
552        Returns true if exactly one sub-trigger is active.
553    */
554    bool MultiTrigger::checkXor(BaseObject* triggerer)
555    {
556        bool triggered = false;
557        for(TriggerBase* trigger : this->children_)
558        {
559            if(triggered)
560            {
561                if(trigger->isMultiTrigger())
562                {
563                    if(static_cast<MultiTrigger*>(trigger)->isActive(triggerer))
564                        return false;
565                }
566                else
567                {
568                    if(trigger->isActive())
569                        return false;
570                }
571            }
572
573            if(trigger->isMultiTrigger())
574            {
575                if(static_cast<MultiTrigger*>(trigger)->isActive(triggerer))
576                    triggered = true;
577            }
578            else
579            {
580                if(trigger->isActive())
581                    triggered = true;
582            }
583        }
584        return triggered;
585    }
586
587}
Note: See TracBrowser for help on using the repository browser.