Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

Last change on this file since 6855 was 6855, checked in by dafrick, 15 years ago

Done documenting MultiTrigger. Also some simplification and resilience improvement on the code done.

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 *      Benjamin Knecht
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->maxNumSimultaniousTriggerers_ = INF_s;
70
71        this->bInvertMode_ = false;
72        this->mode_ = MultiTriggerMode::EventTriggerAND;
73       
74        this->targetMask_.exclude(Class(BaseObject));
75
76        this->setSyncMode(0x0);
77    }
78   
79    /**
80    @brief
81        Destructor. Cleans up the state queue.
82    */
83    MultiTrigger::~MultiTrigger()
84    {
85        COUT(4) << "Destorying MultiTrigger &" << this << ". " << this->stateQueue_.size() << " states still in queue. Deleting." << std::endl;
86        while(this->stateQueue_.size() > 0)
87        {
88            MultiTriggerState* state = this->stateQueue_.front().second;
89            this->stateQueue_.pop_front();
90            delete state;
91        }
92    }
93   
94    /**
95    @brief
96        Method for creating a MultiTrigger object through XML.
97        For a detailed description of the parameters please see the class description in the header file.
98    */
99    void MultiTrigger::XMLPort(Element& xmlelement, XMLPort::Mode mode)
100    {
101        SUPER(MultiTrigger, XMLPort, xmlelement, mode);
102
103        XMLPortParam(MultiTrigger, "delay", setDelay, getDelay, xmlelement, mode);
104        XMLPortParam(MultiTrigger, "switch", setSwitch, getSwitch, xmlelement, mode);
105        XMLPortParam(MultiTrigger, "stayactive", setStayActive, getStayActive, xmlelement, mode);
106        XMLPortParam(MultiTrigger, "activations", setActivations, getActivations, xmlelement, mode);
107        XMLPortParam(MultiTrigger, "simultaniousTriggerers", setSimultaniousTriggerers, getSimultaniousTriggerers, xmlelement, mode);
108        XMLPortParam(MultiTrigger, "invert", setInvert, getInvert, xmlelement, mode);
109        XMLPortParamTemplate(MultiTrigger, "mode", setMode, getModeString, xmlelement, mode, const std::string&);
110        XMLPortParamLoadOnly(MultiTrigger, "target", addTargets, xmlelement, mode).defaultValues("ControllableEntity"); //TODO: Remove load only
111
112        //TODO: Maybe nicer with explicit subgroup, e.g. triggers
113        XMLPortObject(MultiTrigger, MultiTrigger, "", addTrigger, getTrigger, xmlelement, mode);
114       
115        COUT(4) << "MultiTrigger '" << this->getName() << "' (&" << this << ") created." << std::endl;
116    }
117   
118
119    /**
120    @brief
121        A method that is executed each tick.
122    @param dt
123        The duration of the last tick.
124    */
125    void MultiTrigger::tick(float dt)
126    {
127        // If this is the first tick.
128        //TODO: Determine need for this, else kick it out.
129        if(this->bFirstTick_)
130        {
131            this->bFirstTick_ = false;
132            this->fire(false);
133        }
134
135        // Check if the object is active (this is NOT MultiTrigger::isActive()!)
136        if (!this->BaseObject::isActive())
137            return;
138
139        SUPER(MultiTrigger, tick, dt);
140
141        // Let the MultiTrigger return the states that trigger and process the new states if there are any.
142        std::queue<MultiTriggerState*>* queue  = this->letTrigger();
143        if(queue != NULL)
144        {
145            while(queue->size() > 0)
146            {
147                MultiTriggerState* state = queue->front();
148                // If the state is NULL. (This really shouldn't happen)
149                if(state == NULL)
150                {
151                    COUT(1) << "In MultiTrigger '" << this->getName() << "' (&" << this << "), Error: State of new states queue was NULL." << std::endl;
152                    queue->pop();
153                    continue;
154                }
155
156                // The new triggered state dependent on the requested state, the mode and the invert-mode.
157                bool bTriggered = (state->bTriggered & this->isModeTriggered(state->originator)) ^ this->bInvertMode_;
158
159                // If the 'triggered' state has changed a new state is added to the state queue.
160                //TODO: Do something against flooding, when there is delay.
161                if(this->delay_ != 0.0f || bTriggered ^ this->isTriggered(state->originator))
162                {
163                    state->bTriggered = bTriggered;
164                    this->addState(state);
165                }
166                else
167                    delete state;
168               
169                queue->pop();
170            }
171            delete queue;
172        }
173
174        // Go through the state queue and activate all pending states whose remaining time has expired.
175        if (this->stateQueue_.size() > 0)
176        {
177            MultiTriggerState* state;
178            float timeRemaining;
179           
180            // Go through all pending states.
181            for(int size = this->stateQueue_.size(); size >= 1; size--)
182            {
183                timeRemaining = this->stateQueue_.front().first;
184                state = this->stateQueue_.front().second;
185
186                // If the remaining time has expired, the state has to be set as the state of the MultiTrigger.
187                if(timeRemaining <= dt)
188                {
189                    // If the maximum number of objects simultaniously triggering this MultiTrigger is not exceeded.
190                    if(this->maxNumSimultaniousTriggerers_ == INF_s || this->triggered_.size() < (unsigned int)this->maxNumSimultaniousTriggerers_)
191                    {
192                        bool bStateChanged = false;
193                        // If the 'triggered' state is different form what it is now, change it.
194                        if(state->bTriggered ^ this->isTriggered(state->originator))
195                        {
196                            // Add the originator to the objects triggering this MultiTrigger.
197                            if(state->bTriggered == true)
198                            {
199                                this->triggered_.insert(state->originator);
200                            }
201                            // Remove the originator from the objects triggering this MultiTrigger.
202                            else
203                            {
204                                this->triggered_.erase(state->originator);
205                            }
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.
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(this->remainingActivations_ != 0)
228                                {
229                                    this->active_.insert(state->originator);
230                                    if(this->remainingActivations_ != INF_s)
231                                        this->remainingActivations_--; // Decrement the remaining activations.
232                                }
233                                else
234                                {
235                                    bFire = false;
236                                }
237                            }
238                            // Remove the originator from the objects activating this MultiTrigger.
239                            else
240                            {
241                                if(!this->bStayActive_ || this->remainingActivations_ != 0)
242                                {
243                                    this->active_.erase(state->originator);
244                                }
245                                else
246                                {
247                                    bFire = false;
248                                }
249                            }
250
251                            // Fire the Event if the activity has changed.
252                            if(bFire)
253                            {
254                                this->fire(bActive, state->originator);
255                                bStateChanged = true;
256                            }
257                        }
258
259                        // Print some debug output if the state has changed.
260                        if(bStateChanged)
261                            COUT(4) << "MultiTrigger '" << this->getName() << "' (&" << this << ") changed state. originator: " << state->originator->getIdentifier()->getName() << " (&" << state->originator << "), active: " << bActive << ", triggered: " << state->bTriggered << "." << std::endl;
262
263                        // If the MultiTrigger has exceeded its amount of activations and it doesn't stay active, it has to be destroyed,
264                        if(this->remainingActivations_ == 0 && !bActive)
265                        {
266                            this->BaseObject::setActive(false);
267                            COUT(4) << "MultiTrigger '" << this->getName() << "' (&" << this << ") ran out of activations. Setting it to inactive." << std::endl;
268                        }
269                    }
270                   
271                    // Remove the state from the state queue.
272                    this->stateQueue_.pop_front();
273                    delete state;
274                    size -= 1;
275                }
276                // If the remaining time has not yet expired. Decrement the remainig time.
277                else
278                {
279                    this->stateQueue_.push_back(std::pair<float, MultiTriggerState*>(timeRemaining-dt, state));
280                    this->stateQueue_.pop_front();
281                }
282            }
283        }
284    }
285
286    /**
287    @brief
288        Get whether the MultiTrigger is active for a given object.
289    @param triggerers
290        A pointer to the object.
291    @return
292        Returns true if the MultiTrigger is active, false if not.
293    */
294    bool MultiTrigger::isActive(BaseObject* triggerer)
295    {
296        std::set<BaseObject*>::iterator it = this->active_.find(triggerer);
297        if(it == this->active_.end())
298            return false;
299        return true;
300    }
301
302    /**
303    @brief
304        Set the mode of the MultiTrigger.
305    @param modeName
306        The name of the mode as a string.
307    */
308    void MultiTrigger::setMode(const std::string& modeName)
309    {
310        if (modeName == MultiTrigger::and_s)
311            this->setMode(MultiTriggerMode::EventTriggerAND);
312        else if (modeName == MultiTrigger::or_s)
313            this->setMode(MultiTriggerMode::EventTriggerOR);
314        else if (modeName == MultiTrigger::xor_s)
315            this->setMode(MultiTriggerMode::EventTriggerXOR);
316    }
317
318    /**
319    @brief
320        Get the mode of the MultiTrigger.
321    @return
322        Returns the mode as a string.
323    */
324    const std::string& MultiTrigger::getModeString() const
325    {
326        if (this->mode_ == MultiTriggerMode::EventTriggerAND)
327            return MultiTrigger::and_s;
328        else if (this->mode_ == MultiTriggerMode::EventTriggerOR)
329            return MultiTrigger::or_s;
330        else if (this->mode_ == MultiTriggerMode::EventTriggerXOR)
331            return MultiTrigger::xor_s;
332        else
333            return MultiTrigger::and_s;
334    }
335
336    /**
337    @brief
338        Add some target to the MultiTrigger.
339    @param targetStr
340        The target as a string.
341    */
342    void MultiTrigger::addTargets(const std::string& targetStr)
343    {
344        Identifier* target = ClassByString(targetStr);
345
346        if (target == NULL)
347        {
348            COUT(1) << "Error: \"" << targetStr << "\" is not a valid class name to include in ClassTreeMask (in " << this->getName() << ", class " << this->getIdentifier()->getName() << ')' << std::endl;
349            return;
350        }
351
352        this->targetMask_.include(target);
353
354        // A MultiTriggers shouldn't react to itself or other MultiTriggers.
355        this->targetMask_.exclude(Class(MultiTrigger), true);
356
357        // We only want WorldEntities
358        ClassTreeMask WEMask;
359        WEMask.include(Class(WorldEntity));
360        this->targetMask_ *= WEMask;
361
362        this->notifyMaskUpdate();
363    }
364
365    /**
366    @brief
367        Remove some target from the MultiTrigger.
368    @param targetStr
369        The target to be removed as a string.
370    */
371    void MultiTrigger::removeTargets(const std::string& targetStr)
372    {
373        Identifier* target = ClassByString(targetStr);
374        this->targetMask_.exclude(target);
375    }
376
377    /**
378    @brief
379        Adds a MultiTrigger as a sub-trigger to the trigger.
380        Beware: Loops are not prevented and potentially very bad, so just don't create any loops.
381    @param trigger
382        The trigger to be added.
383    */
384    void MultiTrigger::addTrigger(MultiTrigger* trigger)
385    {
386        if (this != trigger && trigger != NULL)
387            this->subTriggers_.insert(trigger);
388    }
389
390    /**
391    @brief
392        Get the sub-trigger of this MultiTrigger at the given index.
393    @param index
394        The index.
395    @return
396        Returns a pointer ot the MultiTrigger. NULL if no such trigger exists.
397    */
398    const MultiTrigger* MultiTrigger::getTrigger(unsigned int index) const
399    {
400        // If the index is greater than the number of sub-triggers.
401        if (this->subTriggers_.size() <= index)
402            return NULL;
403
404        std::set<MultiTrigger*>::const_iterator it;
405        it = this->subTriggers_.begin();
406
407        for (unsigned int i = 0; i != index; ++i)
408            ++it;
409
410        return (*it);
411    }
412
413    /**
414    @brief
415        This method is called by the MultiTrigger to get information about new trigger events that need to be looked at.
416        This method is the device for the behaviour (the conditions under which the MultiTrigger triggers) of any derived class from MultiTrigger.
417
418        In this implementation it collects all objects triggering all sub-triggers nd the MultiTrigger itself and creates a state for each of them.
419    @return
420        A pointer to a queue of MultiTriggerState pointers is returned, containing all the neccessary information to decide whether these states should indeed become new states of the MultiTrigger.
421        Please be aware that both the queue and the states in the queue need to be deleted one 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.
422    */
423    std::queue<MultiTriggerState*>* MultiTrigger::letTrigger(void)
424    {
425        // Goes through all sub-triggers and gets the objects triggering them.
426        std::set<BaseObject*>* triggerers = new std::set<BaseObject*>();
427        std::set<BaseObject*>::iterator objIt;
428        for(std::set<MultiTrigger*>::iterator it = this->subTriggers_.begin(); it != this->subTriggers_.end(); it ++)
429        {
430            std::set<BaseObject*> set = (*it)->getActive();
431            for(objIt = set.begin(); objIt != set.end(); objIt++)
432            {
433                triggerers->insert(*objIt);
434            }
435        }
436
437        // Goes through all the triggerers of this trigger.
438        for(objIt = this->active_.begin(); objIt != this->active_.end(); objIt++)
439        {
440            triggerers->insert(*objIt);
441        }
442
443        // If no objects are triggering this MultiTrigger or the sub-triggers.
444        if(triggerers->size() == 0)
445            return NULL;
446
447        // Create a state for each object triggering this MultiTrigger or any of the sub-triggers and append it to the queue.
448        std::queue<MultiTriggerState*>* queue = new std::queue<MultiTriggerState*>();
449        MultiTriggerState* state = NULL;
450        for(std::set<BaseObject*>::iterator it = triggerers->begin(); it != triggerers->end(); it++)
451        {
452            state = new MultiTriggerState;
453            state->bTriggered = true;
454            state->originator = *it;
455            queue->push(state);
456        }
457        delete triggerers;
458
459        return queue;
460    }
461
462    /**
463    @brief
464        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.
465        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 ofr the given object.
466    @param triggerer
467        The object.
468    @return
469        Returns true if the MultiTrigger is triggered concerning it's sub-triggers.
470    */
471    bool MultiTrigger::isModeTriggered(BaseObject* triggerer)
472    {
473        if (this->subTriggers_.size() != 0)
474        {
475            bool returnVal = false;
476
477            switch (this->mode_)
478            {
479                case MultiTriggerMode::EventTriggerAND:
480                    returnVal = checkAnd(triggerer);
481                    break;
482                case MultiTriggerMode::EventTriggerOR:
483                    returnVal = checkOr(triggerer);
484                    break;
485                case MultiTriggerMode::EventTriggerXOR:
486                    returnVal = checkXor(triggerer);
487                    break;
488                default:
489                    returnVal = false;
490                    break;
491            }
492
493            return returnVal;
494        }
495       
496        return true;
497    }
498   
499    /**
500    @brief
501        Get whether the MultiTrigger is triggered for a given object.
502    @param triggerer
503        The object.
504    @return
505        Returns true if the MultiTrigger is triggered for the given object.
506    */
507    bool MultiTrigger::isTriggered(BaseObject* triggerer)
508    {
509        std::set<BaseObject*>::iterator it = this->triggered_.find(triggerer);
510        if(it == this->triggered_.end())
511            return false;
512        return true;
513    }
514
515    /**
516    @brief
517        Helper method. Creates an event for the given status and originator and fires it.
518        Or more precisely creates a MultiTriggerContainer to encompass all neccesary information and creates an Event from that and sends it.
519    @param status
520        The status of the event to be fired. This is equivalent to the activity of the MultiTrigger.
521    @param originator
522        The object that triggered the MultiTrigger to fire this Event.
523    */
524    void MultiTrigger::fire(bool status, BaseObject* originator)
525    {
526        // If the originator is NULL, a normal event without MultiTriggerContainer is sent.
527        if(originator == NULL)
528        {
529            this->fireEvent(status);
530            return;
531        }
532       
533        MultiTriggerContainer* container = new MultiTriggerContainer(this, this, originator);
534        this->fireEvent(status, container);
535        COUT(4) << "MultiTrigger '" <<  this->getName() << "' (&" << this << "): Fired event. originator: " << originator->getIdentifier()->getName() << " (&" << originator << "), status: " << status << "." << std::endl;
536        delete container;
537    }
538
539    /**
540    @brief
541        Helper method. Adds a state to the state queue, where the state will wait to become active.
542    @param state
543        The state to be added.
544    */
545    bool MultiTrigger::addState(MultiTriggerState* state)
546    {
547        assert(state);
548       
549        // If the originator is no target of this MultiTrigger.
550        if(!this->isTarget(state->originator))
551            return false;
552       
553        // Add it ot the state queue.
554        this->stateQueue_.push_back(std::pair<float, MultiTriggerState*>(this->delay_, state));
555
556        return true;
557    }
558
559    /**
560    @brief
561        Checks whether the sub-triggers amount to true for the 'and' mode for a given object.
562    @param triggerer
563        The object.
564    @return
565        Returns true if they do.
566    */
567    bool MultiTrigger::checkAnd(BaseObject* triggerer)
568    {
569        std::set<MultiTrigger*>::iterator it;
570        for(it = this->subTriggers_.begin(); it != this->subTriggers_.end(); ++it)
571        {
572            if (!(*it)->isActive(triggerer))
573                return false;
574        }
575        return true;
576    }
577
578    /**
579    @brief
580        Checks whether the sub-triggers amount to true for the 'or' mode for a given object.
581    @param triggerer
582        The object.
583    @return
584        Returns true if they do.
585    */
586    bool MultiTrigger::checkOr(BaseObject* triggerer)
587    {
588        std::set<MultiTrigger*>::iterator it;
589        for(it = this->subTriggers_.begin(); it != this->subTriggers_.end(); ++it)
590        {
591            if ((*it)->isActive(triggerer))
592                return true;
593        }
594        return false;
595    }
596
597    /**
598    @brief
599        Checks whether the sub-triggers amount to true for the 'xor' mode for a given object.
600    @param triggerer
601        The object.
602    @return
603        Returns true if they do.
604    */
605    bool MultiTrigger::checkXor(BaseObject* triggerer)
606    {
607        std::set<MultiTrigger*>::iterator it;
608        bool test = false;
609        for(it = this->subTriggers_.begin(); it != this->subTriggers_.end(); ++it)
610        {
611            if (test && (*it)->isActive(triggerer))
612                return false;
613            if ((*it)->isActive(triggerer))
614                test = true;
615        }
616        return test;
617    }
618
619}
Note: See TracBrowser for help on using the repository browser.