Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/notifications/src/modules/objects/triggers/MultiTrigger.cc @ 7689

Last change on this file since 7689 was 7301, checked in by dafrick, 14 years ago

Improving documentation for MultiTriggers, also some small bugfixes, simplifications and added features.

  • Property svn:eol-style set to native
File size: 25.2 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*/
33
34#include "MultiTrigger.h"
35
36#include "core/CoreIncludes.h"
37#include "core/XMLPort.h"
38
39#include "MultiTriggerContainer.h"
40
41namespace orxonox
42{
43
44    // Initialization of some static (magic) variables.
45    /*static*/ const int MultiTrigger::INF_s = -1;
46    /*static*/ const std::string MultiTrigger::and_s = "and";
47    /*static*/ const std::string MultiTrigger::or_s = "or";
48    /*static*/ const std::string MultiTrigger::xor_s = "xor";
49
50    CreateFactory(MultiTrigger);
51
52    /**
53    @brief
54        Constructor. Registers the objects and initializes default values.
55    @param creator
56        The creator.
57    */
58    MultiTrigger::MultiTrigger(BaseObject* creator) : StaticEntity(creator)
59    {
60        RegisterObject(MultiTrigger);
61
62        this->bFirstTick_ = true;
63
64        this->delay_ = 0.0f;
65        this->bSwitch_ = false;
66        this->bStayActive_ = false;
67
68        this->remainingActivations_ = INF_s;
69        this->maxNumSimultaneousTriggerers_ = INF_s;
70
71        this->bInvertMode_ = false;
72        this->mode_ = MultiTriggerMode::EventTriggerAND;
73
74        this->bBroadcast_ = false;
75
76        this->parentTrigger_ = NULL;
77
78        this->targetMask_.exclude(Class(BaseObject));
79
80        this->setSyncMode(0x0);
81    }
82
83    /**
84    @brief
85        Destructor. Cleans up the state queue.
86    */
87    MultiTrigger::~MultiTrigger()
88    {
89        COUT(4) << "Destroying MultiTrigger &" << this << ". " << this->stateQueue_.size() << " states still in queue. Deleting." << std::endl;
90        while(this->stateQueue_.size() > 0)
91        {
92            MultiTriggerState* state = this->stateQueue_.front().second;
93            this->stateQueue_.pop_front();
94            delete state;
95        }
96    }
97
98    /**
99    @brief
100        Method for creating a MultiTrigger object through XML.
101        For a detailed description of the parameters please see the class description in the header file.
102    */
103    void MultiTrigger::XMLPort(Element& xmlelement, XMLPort::Mode mode)
104    {
105        SUPER(MultiTrigger, XMLPort, xmlelement, mode);
106
107        XMLPortParam(MultiTrigger, "delay", setDelay, getDelay, xmlelement, mode);
108        XMLPortParam(MultiTrigger, "switch", setSwitch, getSwitch, xmlelement, mode);
109        XMLPortParam(MultiTrigger, "stayactive", setStayActive, getStayActive, xmlelement, mode);
110        XMLPortParam(MultiTrigger, "activations", setActivations, getActivations, xmlelement, mode);
111        XMLPortParam(MultiTrigger, "simultaneousTriggerers", setSimultaneousTriggerers, getSimultaneousTriggerers, xmlelement, mode);
112        XMLPortParam(MultiTrigger, "invert", setInvert, getInvert, xmlelement, mode);
113        XMLPortParamTemplate(MultiTrigger, "mode", setMode, getModeString, xmlelement, mode, const std::string&);
114        XMLPortParam(MultiTrigger, "broadcast", setBroadcast, getBroadcast, xmlelement, mode);
115        XMLPortParamLoadOnly(MultiTrigger, "target", addTargets, xmlelement, mode).defaultValues("Pawn"); //TODO: Remove load only
116
117        XMLPortObject(MultiTrigger, MultiTrigger, "", addTrigger, getTrigger, xmlelement, mode);
118
119        COUT(4) << "MultiTrigger '" << this->getName() << "' (&" << this << ") created." << std::endl;
120    }
121
122
123    /**
124    @brief
125        A method that is executed each tick.
126    @param dt
127        The duration of the last tick.
128    */
129    void MultiTrigger::tick(float dt)
130    {
131        // If this is the first tick.
132        if(this->bFirstTick_)
133        {
134            this->bFirstTick_ = false;
135            // Fire for all objects that are targets.
136            this->broadcast(false);
137        }
138
139        // Check if the object is active (this is NOT MultiTrigger::isActive()!)
140        if (!this->BaseObject::isActive())
141            return;
142
143        SUPER(MultiTrigger, tick, dt);
144
145        // Let the MultiTrigger return the states that trigger and process the new states if there are any.
146        std::queue<MultiTriggerState*>* queue  = this->letTrigger();
147        if(queue != NULL)
148        {
149            while(queue->size() > 0)
150            {
151                MultiTriggerState* state = queue->front();
152                // If the state is NULL. (This really shouldn't happen)
153                if(state == NULL)
154                {
155                    COUT(1) << "In MultiTrigger '" << this->getName() << "' (&" << this << "), Error: State of new states queue was NULL. State ignored." << std::endl;
156                    queue->pop();
157                    continue;
158                }
159
160                // The new triggered state dependent on the requested state, the mode and the invert-mode.
161                bool bTriggered = (state->bTriggered & this->isModeTriggered(state->originator)) ^ this->bInvertMode_;
162
163                // 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.
164                if(this->delay_ > 0.0f || bTriggered ^ this->isTriggered(state->originator))
165                {
166                    state->bTriggered = bTriggered;
167                    this->addState(state);
168                }
169                // Else the change is irrelevant.
170                else
171                    delete state;
172
173                queue->pop();
174            }
175            delete queue;
176        }
177
178        // Go through the state queue and activate all pending states whose remaining time has expired.
179        if(this->stateQueue_.size() > 0)
180        {
181            MultiTriggerState* state;
182            float timeRemaining;
183
184            // Go through all pending states.
185            for(int size = this->stateQueue_.size(); size >= 1; size--)
186            {
187                timeRemaining = this->stateQueue_.front().first;
188                state = this->stateQueue_.front().second;
189
190                // If the remaining time has expired, the state has to be set as the state of the MultiTrigger.
191                if(timeRemaining <= dt)
192                {
193                    // If the maximum number of objects simultaneously triggering this MultiTrigger is not exceeded.
194                    if(this->maxNumSimultaneousTriggerers_ == INF_s || this->triggered_.size() < (unsigned int)this->maxNumSimultaneousTriggerers_)
195                    {
196                        bool bStateChanged = false;
197                        // If the 'triggered' state is different from what it is now, change it.
198                        if(state->bTriggered ^ this->isTriggered(state->originator))
199                        {
200                            // Add the originator to the objects triggering this MultiTrigger.
201                            if(state->bTriggered == true)
202                                this->triggered_.insert(state->originator);
203                            // Remove the originator from the objects triggering this MultiTrigger.
204                            else
205                                this->triggered_.erase(state->originator);
206
207                            bStateChanged = true;
208                        }
209
210                        // Get the activity of the new state.
211                        bool bActive;
212                        // If the MultiTrigger is in switch mode the 'active'-state only changes of the state changed to triggered.
213                        if(this->bSwitch_ && !state->bTriggered)
214                            bActive = this->isActive(state->originator);
215                        else
216                            bActive = !this->isActive(state->originator);
217
218                        // If the activity is different from what it is now, change it and fire an Event.
219                        if(bActive ^ this->isActive(state->originator))
220                        {
221
222                            bool bFire = true;
223
224                            // Add the originator to the objects activating this MultiTrigger.
225                            if(bActive == true)
226                            {
227                                // If the MultiTrigger has not exceeded its remaining activations.
228                                if(this->remainingActivations_ > 0)
229                                {
230                                    this->active_.insert(state->originator);
231                                    if(this->remainingActivations_ != INF_s)
232                                        this->remainingActivations_--; // Decrement the remaining activations.
233                                }
234                                else
235                                    bFire = false;
236                            }
237                            // Remove the originator from the objects activating this MultiTrigger.
238                            else
239                            {
240                                // If the MultiTrigger doesn't stay active or hasn't' exceeded its remaining activations.
241                                if(!this->bStayActive_ || this->remainingActivations_ > 0)
242                                    this->active_.erase(state->originator);
243                                else
244                                    bFire = false;
245                            }
246
247                            // Fire the Event if the activity has changed.
248                            if(bFire)
249                            {
250                                // If the MultiTrigger is set to broadcast and has no originator a boradcast is fired.
251                                if(this->bBroadcast_ && state->originator == NULL)
252                                    this->broadcast(bActive);
253                                // Else a normal event is fired.
254                                else
255                                    this->fire(bActive, state->originator);
256
257                                bStateChanged = true;
258                            }
259                        }
260
261                        if(bStateChanged)
262                        {
263                            // Print some debug output if the state has changed.
264                            if(state->originator != NULL)
265                                COUT(4) << "MultiTrigger '" << this->getName() << "' (&" << this << ") changed state. originator: " << state->originator->getIdentifier()->getName() << " (&" << state->originator << "), active: " << bActive << ", triggered: " << state->bTriggered << "." << std::endl;
266                            else
267                                COUT(4) << "MultiTrigger '" << this->getName() << "' (&" << this << ") changed state. originator: NULL, active: " << bActive << ", triggered: " << state->bTriggered << "." << std::endl;
268
269                            // If the MultiTrigger has a parent trigger it needs to call a method to notify him, that its activity has changed.
270                            if(this->parentTrigger_ != NULL)
271                                this->parentTrigger_->subTriggerActivityChanged(state->originator);
272                        }
273
274                        // If the MultiTrigger has exceeded its amount of activations and it doesn't stay active, it has to be deactivated.
275                        if(this->remainingActivations_ == 0 && !bActive)
276                        {
277                            this->BaseObject::setActive(false);
278                            COUT(4) << "MultiTrigger '" << this->getName() << "' (&" << this << ") ran out of activations. Setting it to inactive." << std::endl;
279                        }
280                    }
281
282                    // Remove the state from the state queue.
283                    this->stateQueue_.pop_front();
284                    delete state;
285                }
286                // If the remaining time has not yet expired. Decrement the remainig time and put the state at the end of the queue.
287                else
288                {
289                    this->stateQueue_.push_back(std::pair<float, MultiTriggerState*>(timeRemaining-dt, state));
290                    this->stateQueue_.pop_front();
291                }
292            }
293        }
294    }
295
296    /**
297    @brief
298        Get whether the MultiTrigger is active for a given object.
299    @param triggerer
300        A pointer to the object.
301    @return
302        Returns true if the MultiTrigger is active, false if not.
303    */
304    bool MultiTrigger::isActive(BaseObject* triggerer)
305    {
306        std::set<BaseObject*>::iterator it = this->active_.find(triggerer);
307        if(it == this->active_.end())
308            return false;
309        return true;
310    }
311
312    /**
313    @brief
314        Set the mode of the MultiTrigger.
315    @param modeName
316        The name of the mode as a string.
317    */
318    void MultiTrigger::setMode(const std::string& modeName)
319    {
320        if (modeName == MultiTrigger::and_s)
321            this->setMode(MultiTriggerMode::EventTriggerAND);
322        else if (modeName == MultiTrigger::or_s)
323            this->setMode(MultiTriggerMode::EventTriggerOR);
324        else if (modeName == MultiTrigger::xor_s)
325            this->setMode(MultiTriggerMode::EventTriggerXOR);
326        else
327            COUT(2) << "Invalid mode '" << modeName << "' in MultiTrigger " << this->getName() << " &(" << this << "). Leaving mode at '" << this->getModeString() << "'." << std::endl;
328    }
329
330    /**
331    @brief
332        Get the mode of the MultiTrigger.
333    @return
334        Returns the mode as a string.
335    */
336    const std::string& MultiTrigger::getModeString() const
337    {
338        if (this->mode_ == MultiTriggerMode::EventTriggerAND)
339            return MultiTrigger::and_s;
340        else if (this->mode_ == MultiTriggerMode::EventTriggerOR)
341            return MultiTrigger::or_s;
342        else if (this->mode_ == MultiTriggerMode::EventTriggerXOR)
343            return MultiTrigger::xor_s;
344        else // This can never happen, but the compiler needs it to feel secure.
345            return MultiTrigger::and_s;
346    }
347
348    /**
349    @brief
350        Add some target to the MultiTrigger.
351    @param targetStr
352        The target as a string.
353    */
354    void MultiTrigger::addTargets(const std::string& targetStr)
355    {
356        Identifier* target = ClassByString(targetStr);
357
358        // If the target is not a valid class name display an error.
359        if (target == NULL)
360        {
361            COUT(1) << "Error: '" << targetStr << "' is not a valid class name to include in ClassTreeMask (in " << this->getName() << ", class " << this->getIdentifier()->getName() << ")" << std::endl;
362            return;
363        }
364
365        this->targetMask_.include(target);
366
367        // A MultiTrigger shouldn't react to itself or other MultiTriggers.
368        this->targetMask_.exclude(Class(MultiTrigger), true);
369
370        // We only want WorldEntities
371        //TODO: Really?
372        ClassTreeMask WEMask;
373        WEMask.include(Class(WorldEntity));
374        this->targetMask_ *= WEMask;
375    }
376
377    /**
378    @brief
379        Remove some target from the MultiTrigger.
380    @param targetStr
381        The target to be removed as a string.
382    */
383    void MultiTrigger::removeTargets(const std::string& targetStr)
384    {
385        Identifier* target = ClassByString(targetStr);
386
387        // If the target is not a valid class name display an error.
388        if (target == NULL)
389        {
390            COUT(1) << "Error: '" << targetStr << "' is not a valid class name to include in ClassTreeMask (in " << this->getName() << ", class " << this->getIdentifier()->getName() << ")" << std::endl;
391            return;
392        }
393
394        this->targetMask_.exclude(target);
395    }
396
397    /**
398    @brief
399        Adds a MultiTrigger as a sub-trigger to the trigger.
400        Beware: Loops are not prevented and potentially very bad, so just don't create any loops.
401    @param trigger
402        The MultiTrigger to be added.
403    */
404    void MultiTrigger::addTrigger(MultiTrigger* trigger)
405    {
406        if (this != trigger && trigger != NULL)
407            this->subTriggers_.insert(trigger);
408        trigger->addParentTrigger(this);
409    }
410
411    /**
412    @brief
413        Get the sub-trigger of this MultiTrigger at the given index.
414    @param index
415        The index.
416    @return
417        Returns a pointer ot the MultiTrigger. NULL if no such trigger exists.
418    */
419    const MultiTrigger* MultiTrigger::getTrigger(unsigned int index) const
420    {
421        // If the index is greater than the number of sub-triggers.
422        if (this->subTriggers_.size() <= index)
423            return NULL;
424
425        std::set<MultiTrigger*>::const_iterator it;
426        it = this->subTriggers_.begin();
427
428        for (unsigned int i = 0; i != index; ++i)
429            ++it;
430
431        return (*it);
432    }
433
434    /**
435    @brief
436        This method is called by the MultiTrigger to get information about new trigger events that need to be looked at.
437        This method is the device for the behavior (the conditions under which the MultiTrigger triggers) of any derived class of MultiTrigger.
438    @return
439        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.
440        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.
441    */
442    std::queue<MultiTriggerState*>* MultiTrigger::letTrigger(void)
443    {
444        return NULL;
445    }
446
447    /**
448    @brief
449        This method can be called by any class inheriting from MultiTrigger to change it's triggered status for a specified originator.
450
451        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.
452    @param originator
453        The originator which triggered the state change.
454    */
455    void MultiTrigger::changeTriggered(BaseObject* originator)
456    {
457        MultiTriggerState* state = new MultiTriggerState;
458        state->bTriggered = (!this->isTriggered(originator) & this->isModeTriggered(originator)) ^ this->bInvertMode_;
459        state->originator = originator;
460        this->addState(state);
461    }
462
463    /**
464    @brief
465        This method is called by any sub-trigger to advertise changes in its state to its parent-trigger.
466    @param originator
467        The object that caused the change in activity.
468    */
469    void MultiTrigger::subTriggerActivityChanged(BaseObject* originator)
470    {
471        MultiTriggerState* state = new MultiTriggerState;
472        state->bTriggered = (this->isTriggered(originator) & this->isModeTriggered(originator)) ^ this->bInvertMode_;
473        state->originator = originator;
474        this->addState(state);
475    }
476
477    /**
478    @brief
479        Checks whether the sub-triggers are in such a way that, according to the mode of the MultiTrigger, the MultiTrigger is triggered (only considering the sub-triggers, not the state of MultiTrigger itself), for a given object.
480        To make an example: When the mode is 'and', then this would be true or a given object if all the sub-triggers were triggered for the given object.
481    @param triggerer
482        The object.
483    @return
484        Returns true if the MultiTrigger is triggered concerning it's sub-triggers.
485    */
486    bool MultiTrigger::isModeTriggered(BaseObject* triggerer)
487    {
488        if(this->subTriggers_.size() != 0)
489        {
490            bool returnVal = false;
491
492            switch(this->mode_)
493            {
494                case MultiTriggerMode::EventTriggerAND:
495                    returnVal = checkAnd(triggerer);
496                    break;
497                case MultiTriggerMode::EventTriggerOR:
498                    returnVal = checkOr(triggerer);
499                    break;
500                case MultiTriggerMode::EventTriggerXOR:
501                    returnVal = checkXor(triggerer);
502                    break;
503                default: // This will never happen.
504                    returnVal = false;
505                    break;
506            }
507
508            return returnVal;
509        }
510
511        return true;
512    }
513
514    /**
515    @brief
516        Get whether the MultiTrigger is triggered for a given object.
517    @param triggerer
518        The object.
519    @return
520        Returns true if the MultiTrigger is triggered for the given object.
521    */
522    bool MultiTrigger::isTriggered(BaseObject* triggerer)
523    {
524        std::set<BaseObject*>::iterator it = this->triggered_.find(triggerer);
525        if(it == this->triggered_.end())
526            return false;
527        return true;
528    }
529
530    /**
531    @brief
532        Helper method. Creates an Event for the given status and originator and fires it.
533        Or more precisely creates a MultiTriggerContainer to encompass all neccesary information, creates an Event from that and sends it.
534    @param status
535        The status of the Event to be fired. This is equivalent to the activity of the MultiTrigger.
536    @param originator
537        The object that triggered the MultiTrigger to fire this Event.
538    */
539    void MultiTrigger::fire(bool status, BaseObject* originator)
540    {
541        // If the originator is NULL, a normal event without MultiTriggerContainer is sent.
542        if(originator == NULL)
543        {
544            this->fireEvent(status);
545            COUT(4) << "MultiTrigger '" <<  this->getName() << "' (&" << this << "): Fired event. status: " << status << "." << std::endl;
546            return;
547        }
548
549        MultiTriggerContainer* container = new MultiTriggerContainer(this, this, originator);
550        this->fireEvent(status, container);
551        COUT(4) << "MultiTrigger '" <<  this->getName() << "' (&" << this << "): Fired event. originator: " << originator->getIdentifier()->getName() << " (&" << originator << "), status: " << status << "." << std::endl;
552        delete container;
553    }
554
555    /**
556    @brief
557        Helper method. Broadcasts an Event for every object that is a target.
558    @param status
559        The status of the Events to be fired. This is equivalent to the activity of the MultiTrigger.
560    */
561    void MultiTrigger::broadcast(bool status)
562    {
563        for(ClassTreeMaskObjectIterator it = this->getTargetMask().begin(); it != this->getTargetMask().end(); ++it)
564            this->fire(status, static_cast<BaseObject*>(*it));
565    }
566
567    /**
568    @brief
569        Helper method. Adds a state to the state queue, where the state will wait to become active.
570    @param state
571        The state to be added.
572    @return
573        Returns true if the state has been added, false if not. If the state has not been added this method destroys it.
574    */
575    bool MultiTrigger::addState(MultiTriggerState* state)
576    {
577        assert(state); // The state really shouldn't be NULL.
578
579        // If the originator is no target of this MultiTrigger.
580        if(!this->isTarget(state->originator))
581        {
582            delete state;
583            return false;
584        }
585
586        // Add it ot the state queue with the delay specified for the MultiTrigger.
587        this->stateQueue_.push_back(std::pair<float, MultiTriggerState*>(this->delay_, state));
588
589        return true;
590    }
591
592    /**
593    @brief
594        Checks whether the sub-triggers amount to true for the 'and' mode for a given object.
595    @param triggerer
596        The object.
597    @return
598        Returns true if all the sub-triggers are active.
599    */
600    bool MultiTrigger::checkAnd(BaseObject* triggerer)
601    {
602        for(std::set<MultiTrigger*>::iterator it = this->subTriggers_.begin(); it != this->subTriggers_.end(); ++it)
603        {
604            if(!(*it)->isActive(triggerer))
605                return false;
606        }
607        return true;
608    }
609
610    /**
611    @brief
612        Checks whether the sub-triggers amount to true for the 'or' mode for a given object.
613    @param triggerer
614        The object.
615    @return
616        Returns true if at least one sub-trigger is active.
617    */
618    bool MultiTrigger::checkOr(BaseObject* triggerer)
619    {
620        for(std::set<MultiTrigger*>::iterator it = this->subTriggers_.begin(); it != this->subTriggers_.end(); ++it)
621        {
622            if((*it)->isActive(triggerer))
623                return true;
624        }
625        return false;
626    }
627
628    /**
629    @brief
630        Checks whether the sub-triggers amount to true for the 'xor' mode for a given object.
631    @param triggerer
632        The object.
633    @return
634        Returns true if exactly one sub-trigger is active.
635    */
636    bool MultiTrigger::checkXor(BaseObject* triggerer)
637    {
638        bool test = false;
639        for(std::set<MultiTrigger*>::iterator it = this->subTriggers_.begin(); it != this->subTriggers_.end(); ++it)
640        {
641            if(test && (*it)->isActive(triggerer))
642                return false;
643
644            if((*it)->isActive(triggerer))
645                test = true;
646        }
647        return test;
648    }
649
650}
Note: See TracBrowser for help on using the repository browser.