Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/HUD_HS16/src/modules/pickup/PickupManager.cc @ 11948

Last change on this file since 11948 was 11558, checked in by patricwi, 7 years ago

no more segfaults but still not exiting properly - maybe never ending loop?

  • Property svn:eol-style set to native
File size: 22.9 KB
RevLine 
[6474]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:
[7494]23 *      Damian 'Mozork' Frick
[6474]24 *   Co-authors:
25 *      ...
26 *
27*/
28
29/**
[6540]30    @file PickupManager.cc
[6474]31    @brief Implementation of the PickupManager class.
32*/
33
34#include "PickupManager.h"
35
36#include "core/CoreIncludes.h"
[6711]37#include "core/LuaState.h"
38#include "core/GUIManager.h"
[9667]39#include "core/class/Identifier.h"
[10624]40#include "core/singleton/ScopedSingletonIncludes.h"
[7504]41#include "network/Host.h"
[10624]42#include "network/NetworkFunctionIncludes.h"
[11240]43#include "core/input/KeyBinderManager.h"    //for keybinding
44#include "core/input/KeyBinder.h"           //for keybinding
[11252]45#include "core/command/ConsoleCommandIncludes.h"
[7533]46
[7539]47#include "infos/PlayerInfo.h"
[6474]48#include "interfaces/PickupCarrier.h"
49#include "worldentities/pawns/Pawn.h"
[7533]50
[7163]51#include "CollectiblePickup.h"
[6474]52#include "PickupRepresentation.h"
[11305]53#include "overlays/hud/HUDPickupSystem.h"
[6474]54
55namespace orxonox
56{
[10624]57    ManageScopedSingleton(PickupManager, ScopeID::ROOT, false);
[7163]58
[7533]59    // Initialization of the name of the PickupInventory GUI.
[6711]60    /*static*/ const std::string PickupManager::guiName_s = "PickupInventory";
[7163]61
[7801]62    // Register static network functions that are used to communicate changes to pickups over the network, such that the PickupInventory can display the information about the pickups properly.
[7504]63    registerStaticNetworkFunction(PickupManager::pickupChangedUsedNetwork);
64    registerStaticNetworkFunction(PickupManager::pickupChangedPickedUpNetwork);
65    registerStaticNetworkFunction(PickupManager::dropPickupNetworked);
66    registerStaticNetworkFunction(PickupManager::usePickupNetworked);
67
[10624]68    RegisterAbstractClass(PickupManager).inheritsFrom<PickupListener>();
69
[11253]70    SetConsoleCommand("useUnusePickup", &PickupManager::useUnusePickup).addShortcut().setActive(true);
[11252]71
[6474]72    /**
73    @brief
74        Constructor. Registers the PickupManager and creates the default PickupRepresentation.
75    */
[11071]76    PickupManager::PickupManager() : guiLoaded_(false), pickupHighestIndex_(0), defaultRepresentation_(nullptr)
[6474]77    {
[7504]78        RegisterObject(PickupManager);
[7163]79
[6474]80        this->defaultRepresentation_ = new PickupRepresentation();
[7163]81
[11241]82        orxout(internal_info, context::pickups) << "PickupManager created." << endl;
[6474]83    }
[7163]84
[6474]85    /**
86    @brief
87        Destructor.
[7533]88        Destroys the default PickupRepresentation and does some cleanup.
[6474]89    */
90    PickupManager::~PickupManager()
91    {
[7533]92        // Destroying the default representation.
[11071]93        if(this->defaultRepresentation_ != nullptr)
[6474]94            this->defaultRepresentation_->destroy();
[7163]95
[6725]96        this->representations_.clear();
[7163]97
[7533]98        // Destroying all the PickupInventoryContainers that are still there.
[11071]99        for(const auto& mapEntry : this->pickupInventoryContainers_)
100            delete mapEntry.second;
[7533]101        this->pickupInventoryContainers_.clear();
[7504]102
[7533]103        // Destroying all the WeakPointers that are still there.
104        this->pickups_.clear();
105        this->indexes_.clear();
106
[11510]107        orxout() << "PickupManager destroyed." << endl;
[6474]108    }
[7163]109
[6474]110    /**
111    @brief
[9348]112        Registers a PickupRepresentation.
113    @param name
114        The representation's name.
[6474]115    @param representation
116        A pointer to the PickupRepresentation.
117    @return
118        Returns true if successful and false if not.
119    */
[9348]120    bool PickupManager::registerRepresentation(const std::string& name, PickupRepresentation* representation)
[7163]121    {
[7494]122        assert(representation);
123
[7533]124        // If the list is not empty and Pickupable already has a Representation registered.
[9348]125        if(!this->representations_.empty() && this->representations_.find(name) != this->representations_.end())
[6474]126            return false;
[7163]127
[9348]128        this->representations_[name] = representation;
[7163]129
[8858]130        orxout(verbose, context::pickups) << "PickupRepresentation &" << representation << " registered with the PickupManager." << endl;
[6474]131        return true;
132    }
[7163]133
[6474]134    /**
135    @brief
[9348]136        Unegisters a PickupRepresentation.
137    @param name
138        The representation's name.
[6725]139    @return
140        Returns true if successful and false if not.
141    */
[9348]142    bool PickupManager::unregisterRepresentation(const std::string& name)
[7163]143    {
[9348]144        std::map<std::string, PickupRepresentation*>::iterator it = this->representations_.find(name);
[7533]145        if(it == this->representations_.end()) // If the Pickupable is not registered in the first place.
[6725]146            return false;
[7163]147
[6725]148        this->representations_.erase(it);
[7163]149
[9348]150        orxout(verbose, context::pickups) << "PickupRepresentation &" << name << " unregistered with the PickupManager." << endl;
[6725]151        return true;
152    }
[7163]153
[7533]154    /**
155    @brief
[9348]156        Get the PickupRepresentation with the given name.
157    @param name
158        The name of the PickupRepresentation.
[7533]159    @return
[6474]160        Returns a pointer to the PickupRepresentation.
161    */
[9348]162    PickupRepresentation* PickupManager::getRepresentation(const std::string& name)
[6474]163    {
[9348]164        std::map<std::string, PickupRepresentation*>::iterator it = this->representations_.find(name);
165        if(it == this->representations_.end()) // If there is no PickupRepresentation associated with the input name.
[6474]166        {
[8858]167            orxout(verbose, context::pickups) << "PickupManager::getRepresentation() returned default representation." << endl;
[6474]168            return this->defaultRepresentation_;
169        }
[7163]170
[6474]171        return it->second;
172    }
[7163]173
[7206]174    /**
175    @brief
[7533]176        Is called by the PickupListener to notify the PickupManager, that the input Pickupable has transited to the input used state.
[7206]177    @param pickup
[7533]178        The Pickupable whose used status changed.
179    @param used
180        The used status the Pickupable changed to.
[7206]181    */
[7504]182    void PickupManager::pickupChangedUsed(Pickupable* pickup, bool used)
183    {
184        assert(pickup);
185
186        if(!GameMode::isMaster()) // If this is neither standalone nor the server.
187            return;
188
189        CollectiblePickup* collectible = orxonox_cast<CollectiblePickup*>(pickup);
[7533]190        // If the Pickupable is part of a PickupCollection it isn't displayed in the PickupInventory, just the PickupCollection is.
[11071]191        if(collectible != nullptr && collectible->isInCollection())
[7504]192            return;
193
[7533]194        // Getting clientId of the host this change of the pickup's used status concerns.
[7504]195        PickupCarrier* carrier = pickup->getCarrier();
[11071]196        while(carrier->getCarrierParent() != nullptr)
[7504]197            carrier = carrier->getCarrierParent();
198        Pawn* pawn = orxonox_cast<Pawn*>(carrier);
[11071]199        if(pawn == nullptr)
[7504]200            return;
201        PlayerInfo* info = pawn->getPlayer();
[11071]202        if(info == nullptr)
[7504]203            return;
204        unsigned int clientId = info->getClientID();
205
[7533]206        // Get the number identifying the pickup.
207        std::map<Pickupable*, uint32_t>::iterator it = this->indexes_.find(pickup);
[7504]208        assert(it != this->indexes_.end());
[7533]209        uint32_t index = it->second;
[7504]210
[7533]211        // If we're either in standalone mode or this is the host whom the change of the pickup's status concerns.
[7504]212        if(GameMode::isStandalone() || Host::getPlayerID() == clientId)
213        {
214            PickupManager::pickupChangedUsedNetwork(index, used, pickup->isUsable(), pickup->isUnusable());
215        }
[7533]216        // If the concerned host is somewhere in the network, we call pickupChangedUsedNetwork() on its PickupManager.
[7504]217        else
218        {
[10624]219            callStaticNetworkFunction(&PickupManager::pickupChangedUsedNetwork, clientId, index, used, pickup->isUsable(), pickup->isUnusable());
[7504]220        }
221    }
222
[7533]223    /**
224    @brief
225        Helper method to react to the change in the used status of a Pickupable.
226        Static method that is used by the server to inform the client it concerns about the status change.
227        The parameters that are given are used to update the information (i.e. the PickupInventoryContainer) the concerning PickupManager has about the Pickupable that changed.
228    @param pickup
229        A number identifying the Pickupable that changed its used status.
230    @param inUse
231        The used status the Pickupable changed to. (i.e. whether the Pickupable is in use or not).
232    @param usable
233        Whether the Pickupable's used status can be changed used in the PickupInventory.
234    @param unusable
235        Whether the Pickupable's used status can be changed to unused in the PickupInventory.
236    */
237    /*static*/ void PickupManager::pickupChangedUsedNetwork(uint32_t pickup, bool inUse, bool usable, bool unusable)
[7504]238    {
[7533]239        PickupManager& manager = PickupManager::getInstance(); // Get the PickupManager singleton on this host.
240        // If the input Pickupable (i.e its identifier) is not present in the list the PickupManager has.
[7504]241        if(manager.pickupInventoryContainers_.find(pickup) == manager.pickupInventoryContainers_.end())
242        {
[8858]243            orxout(internal_error, context::pickups) << "Pickupable &(" << pickup << ") was not registered with PickupManager for the PickupInventory, when it changed used." << endl;
[7504]244            return;
245        }
246
[7533]247        // Update the Pickupable's container with the information transferred.
[7504]248        manager.pickupInventoryContainers_[pickup]->inUse = inUse;
249        manager.pickupInventoryContainers_[pickup]->usable = usable;
250        manager.pickupInventoryContainers_[pickup]->unusable = unusable;
251
[7533]252        manager.updateGUI(); // Tell the PickupInventory that something has changed.
[7504]253    }
254
[7533]255    /**
256    @brief
257        Is called by the PickupListener to notify the PickupManager, that the input Pickupable has transited to the input pickedUp state.
258    @param pickup
259        The Pickupable whose pickedUp status changed.
260    @param pickedUp
261        The pickedUp status the Pickupable changed to.
262    */
[7504]263    void PickupManager::pickupChangedPickedUp(Pickupable* pickup, bool pickedUp)
264    {
265        assert(pickup);
266
[11540]267        orxout() << "just got called"<<endl;
[11323]268        for (HUDPickupSystem* hud : ObjectList<HUDPickupSystem>())
269            pickupSystem = hud;
270       
[7533]271        if(!GameMode::isMaster()) // If this is neither standalone nor the server.
[7504]272            return;
273
274        CollectiblePickup* collectible = orxonox_cast<CollectiblePickup*>(pickup);
[7533]275        // If the Pickupable is part of a PickupCollection it isn't displayed in the PickupInventory, just the PickupCollection is.
[11071]276        if(collectible != nullptr && collectible->isInCollection())
[7504]277            return;
278
[7533]279        // Getting clientId of the host this change of the pickup's pickedUp status concerns.
[7504]280        PickupCarrier* carrier = pickup->getCarrier();
[11071]281        while(carrier->getCarrierParent() != nullptr)
[7504]282            carrier = carrier->getCarrierParent();
283        Pawn* pawn = orxonox_cast<Pawn*>(carrier);
[11071]284        if(pawn == nullptr)
[7504]285            return;
[7533]286        PlayerInfo* info = pawn->getFormerPlayer();
[11071]287        if(info == nullptr)
[7504]288            return;
289        unsigned int clientId = info->getClientID();
290
[7533]291        uint32_t index = 0;
292        if(pickedUp) // If the Pickupable has changed to picked up, it is added to the required lists.
[7504]293        {
[11241]294            index = this->getPickupIndex(); // Get a new identifier (index) for the Pickupable.
[7533]295            // Add the Pickupable to the indexes_ and pickups_ lists.
[7504]296            this->indexes_[pickup] = index;
[10624]297            this->pickups_[index] = pickup;
[11241]298
[11314]299            this->picks.push_back(pickup);
[11305]300
[11336]301            if(pickupSystem)
[11558]302                pickupSystem->sync(picks, indexes_);
[11305]303           
[7504]304        }
[7533]305        else // If it was dropped, it is removed from the required lists.
[7504]306        {
[7533]307            // Get the indentifier (index) that identifies the input Pickupable.
308            std::map<Pickupable*, uint32_t>::iterator it = this->indexes_.find(pickup);
[7504]309            index = it->second;
[7533]310
[11241]311            this->indexes_.erase(pickup);
[11325]312            this->pickups_.erase(index); //set to null, so that can be identified as free slot by getPickupIndex()
[11314]313
314
315            this->picks.erase(std::remove(this->picks.begin(), this->picks.end(), pickup), this->picks.end()); //remove pickup from vector
316
[11336]317            if(pickupSystem)
[11558]318                pickupSystem->sync(picks, indexes_);
[11510]319            orxout() << "end of pickupChangedPickedUp" << endl;
[7504]320        }
321
[7533]322        // If we're either in standalone mode or this is the host whom the change of the pickup's status concerns.
[7504]323        if(GameMode::isStandalone() || Host::getPlayerID() == clientId)
324        {
[7533]325            // If there is no PickupRepresentation registered the default representation is used.
[9348]326            if(this->representations_.find(pickup->getRepresentationName()) == this->representations_.end())
327                PickupManager::pickupChangedPickedUpNetwork(index, pickup->isUsable(), this->defaultRepresentation_->getObjectID(), pickup->getRepresentationName(), pickedUp);
[7531]328            else
[9348]329                PickupManager::pickupChangedPickedUpNetwork(index, pickup->isUsable(), this->representations_[pickup->getRepresentationName()]->getObjectID(), pickup->getRepresentationName(), pickedUp);
[7504]330        }
[7533]331        // If the concerned host is somewhere in the network, we call pickupChangedPickedUpNetwork() on its PickupManager.
[7504]332        else
333        {
[7533]334            // If there is no PickupRepresentation registered the default representation is used.
[9348]335            if(this->representations_.find(pickup->getRepresentationName()) == this->representations_.end())
[7531]336            {
[10624]337                callStaticNetworkFunction(&PickupManager::pickupChangedPickedUpNetwork, clientId, index, pickup->isUsable(), this->defaultRepresentation_->getObjectID(), pickedUp);
[7531]338            }
339            else
340            {
[10624]341                callStaticNetworkFunction(&PickupManager::pickupChangedPickedUpNetwork, clientId, index, pickup->isUsable(), this->representations_[pickup->getRepresentationName()]->getObjectID(), pickedUp);
[7531]342            }
[7504]343        }
344
345    }
346
[11252]347    //This function is called by the command line or by the key binding
348    //it uses or unuses the pickup, depending on its current state
[11336]349    //or drops it (depends what you comment/uncomment)
[11277]350    void PickupManager::useUnusePickup(uint32_t index) 
[11252]351    {
[11253]352        PickupManager& manager = PickupManager::getInstance();
353
[11325]354        if(!manager.pickups_.count(index)) return; //if pickup is no longer here, dont do anything
355
[11253]356        Pickupable* pickup=manager.pickups_.find(index)->second;
357        if(pickup==nullptr)
358        {
359            return;                       //pickup does not exist
360        }
361
[11336]362        //if the pickup should be dropped upon key press
[11325]363        manager.dropPickup(index);
364
[11336]365        //if the pickup should be used/unused upon key press
366
[11323]367        // if(pickup->isUsed())
368        //     manager.usePickup(index, false);
369        // else
370        //     manager.usePickup(index, true);
[11241]371    }
372
[11252]373
[7533]374    /**
375    @brief
376        Helper method to react to the change in the pickedUp status of a Pickupable.
377        Static method that is used by the server to inform the client it concerns about the status change.
378        The parameters that are given are used to update the information (i.e. the PickupInventoryContainer) the concerning PickupManager has about the Pickupable that changed.
379    @param pickup
380        A number identifying the Pickupable that changed its pickedUp status.
[7547]381    @param usable
382        Whether the Pickupable's used status can be changed to used in the PickupInventory.
[7533]383    @param representationObjectId
384        The objectId identifying (over the network) the PickupRepresentation that represents this Pickupable.
[11099]385    @param representationName
386        The name of the associated PickupRepresentation
[7533]387    @param pickedUp
388        The pickedUp status the Pickupable changed to.
389    */
[9348]390    /*static*/ void PickupManager::pickupChangedPickedUpNetwork(uint32_t pickup, bool usable, uint32_t representationObjectId, const std::string& representationName, bool pickedUp)
[7504]391    {
[7533]392        PickupManager& manager = PickupManager::getInstance(); // Get the PickupManager singleton on this host.
393        // If the Pickupable has been picked up, we create a new PickupInventoryContainer for it.
[7504]394        if(pickedUp)
395        {
[7533]396            // Create a new PickupInventoryContainer for the Pickupable and set all the necessary information.
[7504]397            PickupInventoryContainer* container = new PickupInventoryContainer;
398            container->pickup = pickup;
399            container->inUse = false;
400            container->pickedUp = pickedUp;
401            container->usable = usable;
402            container->unusable = false;
403            container->representationObjectId = representationObjectId;
[9348]404            container->representationName = representationName;
[7533]405            // Insert the container into the pickupInventoryContainers_ list.
406            manager.pickupInventoryContainers_.insert(std::pair<uint32_t, PickupInventoryContainer*>(pickup, container));
[7504]407
[7533]408            manager.updateGUI(); // Tell the PickupInventory that something has changed.
[7504]409        }
[7533]410        // If the Pickupable has been dropped, we remove it from the pickupInventoryContainers_ list.
[7504]411        else
412        {
[7533]413            std::map<uint32_t, PickupInventoryContainer*>::iterator it = manager.pickupInventoryContainers_.find(pickup);
[7504]414            if(it != manager.pickupInventoryContainers_.end())
415                delete it->second;
416            manager.pickupInventoryContainers_.erase(pickup);
417
[7533]418            manager.updateGUI(); // Tell the PickupInventory that something has changed.
[7504]419        }
420    }
421
[7533]422    /**
423    @brief
424        Get the number of pickups currently picked up by the player.
425        This method is used in lua to populate the PickupInventory. The intended usage is to call this method to reset the iterator of the list of PickupInventoryContainers and then use popPickup() to get the individual PickupInventoryContainers.
426    @return
427        Returns the number of the players picked up Pickupables.
428    */
429    int PickupManager::getNumPickups(void)
430    {
431        this->pickupsIterator_ = this->pickupInventoryContainers_.begin(); // Reset iterator.
432
433        return this->pickupInventoryContainers_.size();
434    }
435
436    /**
437    @brief
438        Drop the input Pickupable.
439        This method checks whether the input Pickupable still exists and drops it, if so.
440    @param pickup
441        The identifier of the Pickupable to be dropped.
442    */
443    void PickupManager::dropPickup(uint32_t pickup)
444    {
445        // If we're either server or standalone and the list of pickups is not empty, we find and drop the input pickup.
[7539]446        if(GameMode::isMaster())
[7533]447        {
[7539]448            if(this->pickups_.empty())
449                return;
[10624]450            Pickupable* pickupable = this->pickups_.find(pickup)->second;
[11071]451            if(pickupable != nullptr)
[11241]452            {
[7533]453                pickupable->drop();
[11241]454
455            }
[7533]456        }
457        // If we're neither server nor standalone we drop the pickup by calling dropPickupNetworked() of the PickupManager on the server.
458        else
459        {
[10624]460            callStaticNetworkFunction(&PickupManager::dropPickupNetworked, 0, pickup);
[7533]461        }
462    }
463
464    /**
465    @brief
466        Helper method to drop the input pickup on the server.
467        Static method that is used by clients to instruct the server to drop the input pickup.
468    @param pickup
469        The identifier of the Pickupable to be dropped.
470    */
471    /*static*/ void PickupManager::dropPickupNetworked(uint32_t pickup)
472    {
473        if(GameMode::isServer()) // Obviously we only want to do this on the server.
474        {
475            PickupManager& manager = PickupManager::getInstance();
[7539]476            manager.dropPickup(pickup);
[7533]477        }
478    }
479
480    /**
481    @brief
482        Use (or unuse) the input Pickupable.
483        This method checks whether the input Pickupable still exists and uses (or unuses) it, if so,
484    @param pickup
485        The identifier of the Pickupable to be used (or unused).
486    @param use
487        If true the input Pickupable is used, if false it is unused.
488    */
489    void PickupManager::usePickup(uint32_t pickup, bool use)
490    {
491        // If we're either server or standalone and the list of pickups is not empty, we find and change the used status of the input pickup.
[7539]492        if(GameMode::isMaster())
[7533]493        {
[7539]494            if(this->pickups_.empty())
495                return;
[10624]496            Pickupable* pickupable = this->pickups_.find(pickup)->second;
[11071]497            if(pickupable != nullptr)
[7533]498                pickupable->setUsed(use);
499        }
500        // If we're neither server nor standalone we change the used status of the pickup by calling usePickupNetworked() of the PickupManager on the server.
501        else
502        {
[10624]503            callStaticNetworkFunction(&PickupManager::usePickupNetworked, 0, pickup, use);
[7533]504        }
505    }
506
507    /**
508    @brief
509        Helper method to use (or unuse) the input Pickupable on the server.
510        Static method that is used by clients to instruct the server to use (or unuse) the input pickup.
511    @param pickup
512        The identifier of the Pickupable to be used (or unused).
513    @param use
514        If true the input Pickupable is used, if false it is unused.
515    */
516    /*static*/ void PickupManager::usePickupNetworked(uint32_t pickup, bool use)
517    {
518        if(GameMode::isServer())
519        {
520            PickupManager& manager = PickupManager::getInstance();
[7539]521            manager.usePickup(pickup, use);
[7533]522        }
523    }
524
525    /**
526    @brief
527        Updates the PickupInventory GUI.
528        Also loads the PickupInventory GUI if is hasn't been done already.
529    */
[7504]530    inline void PickupManager::updateGUI(void)
531    {
[7533]532        // We only need to update (and load) the GUI if this host shows graphics.
[7504]533        if(GameMode::showsGraphics())
534        {
[7533]535            if(!this->guiLoaded_) // If the GUI hasn't been loaded, yet, we load it.
[7504]536            {
537                GUIManager::getInstance().loadGUI(PickupManager::guiName_s);
538                this->guiLoaded_ = true;
539            }
[7533]540
541            // Update the GUI.
[7504]542            GUIManager::getInstance().getLuaState()->doString(PickupManager::guiName_s + ".update()");
543        }
544    }
545
[7533]546    /**
547    @brief
[11241]548        Get a new index between 0 and 9 for a Pickupable.
549        If all slots are occupied, the Pickupable in the first slot will be dropped.
[7533]550    @return
551        Returns the new index.
552    */
553    uint32_t PickupManager::getPickupIndex(void)
[7504]554    {
[11241]555        //check if there are free slots available
556
557        for(uint32_t i=0; i<10; i++)
558        {
[11325]559            if(!pickups_.count(i)) return i;
[11241]560        }
561        //all slots are full and we have to drop sth
[11336]562        orxout(internal_info, context::pickups) << "everything was full and we have now dropped the first element" << endl;
[11241]563        this->dropPickup(0);
564        return 0;
[7504]565    }
566
[6474]567}
Note: See TracBrowser for help on using the repository browser.