1 /** @file worldsystem.cpp  World subsystem.
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2006-2015 Daniel Swanson <danij@dengine.net>
5  *
6  * @par License
7  * GPL: http://www.gnu.org/licenses/gpl.html
8  *
9  * <small>This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by the
11  * Free Software Foundation; either version 2 of the License, or (at your
12  * option) any later version. This program is distributed in the hope that it
13  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15  * Public License for more details. You should have received a copy of the GNU
16  * General Public License along with this program; if not, write to the Free
17  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18  * 02110-1301 USA</small>
19  */
20 
21 #include "world/clientserverworld.h"
22 
23 #include <map>
24 #include <utility>
25 #include <QMap>
26 #include <QtAlgorithms>
27 #include <de/memoryzone.h>
28 #include <de/timer.h>
29 #include <de/Binder>
30 #include <de/Context>
31 #include <de/Error>
32 #include <de/Log>
33 #include <de/Scheduler>
34 #include <de/Script>
35 #include <de/ScriptSystem>
36 #include <de/Process>
37 #include <de/Time>
38 #include <doomsday/doomsdayapp.h>
39 #include <doomsday/console/cmd.h>
40 #include <doomsday/console/exec.h>
41 #include <doomsday/console/var.h>
42 #include <doomsday/defs/mapinfo.h>
43 #include <doomsday/resource/mapmanifests.h>
44 #include <doomsday/world/MaterialManifest>
45 #include <doomsday/world/Materials>
46 
47 #include "dd_main.h"
48 #include "dd_def.h"
49 #include "dd_loop.h"
50 #include "def_main.h"  // ::defs
51 
52 #include "api_player.h"
53 
54 #ifdef __CLIENT__
55 #  include "clientapp.h"
56 #  include "client/cl_def.h"
57 #  include "client/cl_frame.h"
58 #  include "client/cl_player.h"
59 #  include "gl/gl_main.h"
60 #endif
61 
62 #ifdef __SERVER__
63 #  include "server/sv_pool.h"
64 #endif
65 
66 #include "network/net_main.h"
67 
68 #include "api_mapedit.h"
69 #include "world/p_players.h"
70 #include "world/p_ticker.h"
71 #include "world/sky.h"
72 #include "world/thinkers.h"
73 #include "world/bindings_world.h"
74 #include "edit_map.h"
75 #include "Plane"
76 #include "Sector"
77 #include "Subsector"
78 #include "Surface"
79 
80 #ifdef __CLIENT__
81 #  include "world/contact.h"
82 #  include "client/cledgeloop.h"
83 #  include "client/clientsubsector.h"
84 #  include "Lumobj"
85 #  include "render/viewports.h"  // R_ResetViewer
86 #  include "render/rend_fakeradio.h"
87 #  include "render/rend_main.h"
88 #  include "render/rendersystem.h"
89 #  include "render/rendpoly.h"
90 #  include "MaterialAnimator"
91 #  include "ui/progress.h"
92 #  include "ui/inputsystem.h"
93 #endif
94 
95 using namespace de;
96 using namespace world;
97 
98 dint validCount = 1;  // Increment every time a check is made.
99 
100 #ifdef __CLIENT__
101 //static dfloat handDistance = 300;  //cvar
rendSys()102 static inline RenderSystem &rendSys()
103 {
104     return ClientApp::renderSystem();
105 }
106 #endif
107 
108 /**
109  * Observes the progress of a map conversion and records any issues/problems that
110  * are encountered in the process. When asked, compiles a human-readable report
111  * intended to assist mod authors in debugging their maps.
112  *
113  * @todo Consolidate with the missing material reporting done elsewhere -ds
114  */
115 class MapConversionReporter
116 : DENG2_OBSERVES(Map,     UnclosedSectorFound)
117 , DENG2_OBSERVES(Map,     OneWayWindowFound)
118 , DENG2_OBSERVES(BaseMap, Deletion)
119 {
120     /// Record "unclosed sectors".
121     /// Sector index => world point relatively near to the problem area.
122     typedef std::map<dint, Vector2i> UnclosedSectorMap;
123 
124     /// Record "one-way window lines".
125     /// Line index => Sector index the back side faces.
126     typedef std::map<dint, dint> OneWayWindowMap;
127 
128     /// Maximum number of warnings to output (of each type) about any problems
129     /// encountered during the build process.
130     static dint const maxWarningsPerType;
131 
132 public:
133     /**
134      * Construct a new conversion reporter.
135      * @param map
136      */
MapConversionReporter(Map * map=nullptr)137     MapConversionReporter(Map *map = nullptr)
138     {
139         setMap(map);
140     }
141 
~MapConversionReporter()142     ~MapConversionReporter()
143     {
144         observeMap(false);
145     }
146 
147     /**
148      * Change the map to be reported on. Note that any existing report data is
149      * retained until explicitly cleared.
150      */
setMap(Map * newMap)151     void setMap(Map *newMap)
152     {
153         if (_map != newMap)
154         {
155             observeMap(false);
156             _map = newMap;
157             observeMap(true);
158         }
159     }
160 
161     /// @see setMap(), clearReport()
setMapAndClearReport(Map * newMap)162     inline void setMapAndClearReport(Map *newMap)
163     {
164         setMap(newMap);
165         clearReport();
166     }
167 
168     /// Same as @code setMap(nullptr); @endcode
clearMap()169     inline void clearMap() { setMap(nullptr); }
170 
171     /**
172      * Clear any existing conversion report data.
173      */
clearReport()174     void clearReport()
175     {
176         _unclosedSectors.clear();
177         _oneWayWindows.clear();
178     }
179 
180     /**
181      * Compile and output any existing report data to the message log.
182      */
writeLog()183     void writeLog()
184     {
185         if (dint numToLog = maxWarnings(unclosedSectorCount()))
186         {
187             String str;
188 
189             UnclosedSectorMap::const_iterator it = _unclosedSectors.begin();
190             for (dint i = 0; i < numToLog; ++i, ++it)
191             {
192                 if (i != 0) str += "\n";
193                 str += String("Sector #%1 is unclosed near %2")
194                            .arg(it->first).arg(it->second.asText());
195             }
196 
197             if (numToLog < unclosedSectorCount())
198                 str += String("\n(%1 more like this)").arg(unclosedSectorCount() - numToLog);
199 
200             LOGDEV_MAP_WARNING("%s") << str;
201         }
202 
203         if (dint numToLog = maxWarnings(oneWayWindowCount()))
204         {
205             String str;
206 
207             OneWayWindowMap::const_iterator it = _oneWayWindows.begin();
208             for (dint i = 0; i < numToLog; ++i, ++it)
209             {
210                 if (i != 0) str += "\n";
211                 str += String("Line #%1 seems to be a One-Way Window (back faces sector #%2).")
212                            .arg(it->first).arg(it->second);
213             }
214 
215             if (numToLog < oneWayWindowCount())
216                 str += String("\n(%1 more like this)").arg(oneWayWindowCount() - numToLog);
217 
218             LOGDEV_MAP_MSG("%s") << str;
219         }
220     }
221 
222 protected:
223     /// Observes Map UnclosedSectorFound.
unclosedSectorFound(Sector & sector,Vector2d const & nearPoint)224     void unclosedSectorFound(Sector &sector, Vector2d const &nearPoint)
225     {
226         _unclosedSectors.insert(std::make_pair(sector.indexInArchive(), nearPoint.toVector2i()));
227     }
228 
229     /// Observes Map OneWayWindowFound.
oneWayWindowFound(Line & line,Sector & backFacingSector)230     void oneWayWindowFound(Line &line, Sector &backFacingSector)
231     {
232         _oneWayWindows.insert(std::make_pair(line.indexInArchive(), backFacingSector.indexInArchive()));
233     }
234 
235     /// Observes Map Deletion.
mapBeingDeleted(BaseMap const & map)236     void mapBeingDeleted(BaseMap const &map)
237     {
238         DENG2_ASSERT(&map == _map);  // sanity check.
239         DENG2_UNUSED(map);
240         _map = nullptr;
241     }
242 
243 private:
unclosedSectorCount() const244     inline dint unclosedSectorCount() const { return dint( _unclosedSectors.size() ); }
oneWayWindowCount() const245     inline dint oneWayWindowCount() const   { return dint( _oneWayWindows.size() ); }
246 
maxWarnings(dint issueCount)247     static inline dint maxWarnings(dint issueCount)
248     {
249 #ifdef DENG2_DEBUG
250         return issueCount; // No limit.
251 #else
252         return de::min(issueCount, maxWarningsPerType);
253 #endif
254     }
255 
observeMap(bool yes)256     void observeMap(bool yes)
257     {
258         if (!_map) return;
259 
260         if (yes)
261         {
262             _map->audienceForDeletion()          += this;
263             _map->audienceForOneWayWindowFound   += this;
264             _map->audienceForUnclosedSectorFound += this;
265         }
266         else
267         {
268             _map->audienceForDeletion()          -= this;
269             _map->audienceForOneWayWindowFound   -= this;
270             _map->audienceForUnclosedSectorFound -= this;
271         }
272     }
273 
274     Map *_map = nullptr;  ///< Map currently being reported on, if any (not owned).
275     UnclosedSectorMap _unclosedSectors;
276     OneWayWindowMap   _oneWayWindows;
277 };
278 
279 dint const MapConversionReporter::maxWarningsPerType = 10;
280 
281 dd_bool ddMapSetup;
282 
283 // Should we be caching successfully loaded maps?
284 //static byte mapCache = true; // cvar
285 
286 static char const *mapCacheDir = "mapcache/";
287 
288 /// Determine the identity key for maps loaded from the specified @a sourcePath.
cacheIdForMap(String const & sourcePath)289 static String cacheIdForMap(String const &sourcePath)
290 {
291     DENG2_ASSERT(!sourcePath.isEmpty());
292 
293     dushort id = 0;
294     for (dint i = 0; i < sourcePath.size(); ++i)
295     {
296         id ^= sourcePath.at(i).unicode() << ((i * 3) % 11);
297     }
298 
299     return String("%1").arg(id, 4, 16);
300 }
301 
DENG2_PIMPL(ClientServerWorld)302 DENG2_PIMPL(ClientServerWorld)
303 {
304     Binder binder;               ///< Doomsday Script bindings for the World.
305     Record worldModule;
306     Record fallbackMapInfo;      ///< Used when no effective MapInfo definition.
307 
308     timespan_t time = 0;         ///< World-wide time.
309     Scheduler scheduler;
310 #if 0
311 #ifdef __CLIENT__
312     std::unique_ptr<Hand> hand;  ///< For map editing/manipulation.
313 #endif
314 #endif // 0
315 
316     Impl(Public *i) : Base(i)
317     {
318         world::initBindings(binder, worldModule);
319         ScriptSystem::get().addNativeModule("World", worldModule);
320 
321         // One time init of the fallback MapInfo definition.
322         defn::MapInfo(fallbackMapInfo).resetToDefaults();
323 
324         // Callbacks.
325         world::DmuArgs::setPointerToIndexFunc(P_ToIndex);
326 #ifdef __CLIENT__
327         world::MaterialManifest::setMaterialConstructor([] (world::MaterialManifest &m) -> world::Material * {
328             return new ClientMaterial(m);
329         });
330         Sector::setSubsectorConstructor([] (QVector<world::ConvexSubspace *> const &sl) -> world::Subsector * {
331             return new ClientSubsector(sl);
332         });
333 #else
334         world::MaterialManifest::setMaterialConstructor([] (world::MaterialManifest &m) -> world::Material * {
335             return new world::Material(m);
336         });
337         Sector::setSubsectorConstructor([] (QVector<world::ConvexSubspace *> const &sl) -> world::Subsector * {
338             return new Subsector(sl);
339         });
340 #endif
341     }
342 
343     /**
344      * Compose the relative path (relative to the runtime directory) to the
345      * directory of the cache where maps from this source (e.g., the add-on
346      * which contains the map) will reside.
347      *
348      * @param sourcePath  Path to the primary resource file (the source) for
349      *                    the original map data.
350      *
351      * @return  The composed path.
352      */
353     static Path cachePath(String sourcePath)
354     {
355         if (sourcePath.isEmpty()) return String();
356 
357         // Compose the final path.
358         return mapCacheDir + App_CurrentGame().id()
359                / sourcePath.fileNameWithoutExtension()
360                + '-' + cacheIdForMap(sourcePath);
361     }
362 
363     /**
364      * Attempt JIT conversion of the map data with the help of a plugin. Note that
365      * the map is left in an editable state in case the caller wishes to perform
366      * any further changes.
367      *
368      * @param reporter  Reporter which will observe the conversion process.
369      *
370      * @return  The newly converted map (if any).
371      */
372     Map *convertMap(res::MapManifest const &mapManifest, MapConversionReporter *reporter = nullptr)
373     {
374         // We require a map converter for this.
375         if (!Plug_CheckForHook(HOOK_MAP_CONVERT))
376             return nullptr;
377 
378         LOG_DEBUG("Attempting \"%s\"...") << mapManifest.composeUri().path();
379 
380         if (!mapManifest.sourceFile()) return nullptr;
381 
382         // Initiate the conversion process.
383         MPE_Begin(nullptr/*dummy*/);
384 
385         Map *newMap = MPE_Map();
386 
387         // Associate the map with its corresponding manifest.
388         newMap->setManifest(&const_cast<res::MapManifest &>(mapManifest));
389 
390         if (reporter)
391         {
392             // Instruct the reporter to begin observing the conversion.
393             reporter->setMap(newMap);
394         }
395 
396         // Ask each converter in turn whether the map format is recognizable
397         // and if so to interpret and transfer it to us via the runtime map
398         // editing interface.
399         if (!DoomsdayApp::plugins().callAllHooks(HOOK_MAP_CONVERT, 0,
400                                                 const_cast<Id1MapRecognizer *>(&mapManifest.recognizer())))
401             return nullptr;
402 
403         // A converter signalled success.
404 
405         // End the conversion process (if not already).
406         MPE_End();
407 
408         // Take ownership of the map.
409         return MPE_TakeMap();
410     }
411 
412 #if 0
413     /**
414      * Returns @c true iff data for the map is available in the cache.
415      */
416     bool haveCachedMap(res::MapManifest &mapManifest)
417     {
418         // Disabled?
419         if (!mapCache) return false;
420         return DAM_MapIsValid(mapManifest.cachePath, mapManifest.id());
421     }
422 
423     /**
424      * Attempt to load data for the map from the cache.
425      *
426      * @see isCachedDataAvailable()
427      *
428      * @return  @c true if loading completed successfully.
429      */
430     Map *loadMapFromCache(MapManifest &mapManifest)
431     {
432         Uri const mapUri = mapManifest.composeUri();
433         Map *map = DAM_MapRead(mapManifest.cachePath);
434         if (!map)
435             /// Failed to load the map specified from the data cache.
436             throw Error("loadMapFromCache", "Failed loading map \"" + mapUri.asText() + "\" from cache");
437 
438         map->_uri = mapUri;
439         return map;
440     }
441 #endif
442 
443     /**
444      * Attempt to load the associated map data.
445      *
446      * @return  The loaded map if successful. Ownership given to the caller.
447      */
448     Map *loadMap(res::MapManifest &mapManifest, MapConversionReporter *reporter = nullptr)
449     {
450         LOG_AS("ClientServerWorld::loadMap");
451 
452         /*if (mapManifest.lastLoadAttemptFailed && !forceRetry)
453             return nullptr;
454 
455         // Load from cache?
456         if (haveCachedMap(mapManifest))
457         {
458             return loadMapFromCache(mapManifest);
459         }*/
460 
461         // Try a JIT conversion with the help of a plugin.
462         Map *map = convertMap(mapManifest, reporter);
463         if (!map)
464         {
465             LOG_WARNING("Failed conversion of \"%s\".") << mapManifest.composeUri().path();
466             //mapManifest.lastLoadAttemptFailed = true;
467         }
468         return map;
469     }
470 
471     /**
472      * Replace the current map with @a map.
473      */
474     void makeCurrent(Map *map)
475     {
476         // This is now the current map (if any).
477         self().setMap(map);
478         if (!map) return;
479 
480         // We cannot make an editable map current.
481         DENG2_ASSERT(!map->isEditable());
482 
483         // Should we cache this map?
484         /*if (mapCache && !haveCachedMap(&map->def()))
485         {
486             // Ensure the destination directory exists.
487             F_MakePath(map->def().cachePath.toUtf8().constData());
488 
489             // Cache the map!
490             DAM_MapWrite(map);
491         }*/
492 
493 #ifdef __CLIENT__
494         // Connect the map to world audiences:
495         /// @todo The map should instead be notified when it is made current
496         /// so that it may perform the connection itself. Such notification
497         /// would also afford the map the opportunity to prepare various data
498         /// which is only needed when made current (e.g., caches for render).
499         self().audienceForFrameBegin() += map;
500 #endif
501 
502         // Print summary information about this map.
503         LOG_MAP_NOTE(_E(b) "Current map elements:");
504         LOG_MAP_NOTE("%s") << map->elementSummaryAsStyledText();
505 
506         // See what MapInfo says about this map.
507         Record const &mapInfo = map->mapInfo();
508 
509         map->_ambientLightLevel = mapInfo.getf("ambient") * 255;
510         map->_globalGravity     = mapInfo.getf("gravity");
511         map->_effectiveGravity  = map->_globalGravity;
512 
513 #ifdef __CLIENT__
514         // Reconfigure the sky.
515         defn::Sky skyDef;
516         if (Record const *def = DED_Definitions()->skies.tryFind("id", mapInfo.gets("skyId")))
517         {
518             skyDef = *def;
519         }
520         else
521         {
522             skyDef = mapInfo.subrecord("sky");
523         }
524         map->sky().configure(&skyDef);
525 
526         // Set up the SkyDrawable to get its config from the map's Sky.
527         map->skyAnimator().setSky(&rendSys().sky().configure(&map->sky()));
528 #endif
529 
530         // Init the thinker lists (public and private).
531         map->thinkers().initLists(0x1 | 0x2);
532 
533         // Must be called before we go any further.
534         P_InitUnusedMobjList();
535 
536         // Must be called before any mobjs are spawned.
537         map->initNodePiles();
538 
539 #ifdef __CLIENT__
540         // Prepare the client-side data.
541         Cl_ResetFrame();
542         Cl_InitPlayers();  // Player data, too.
543 
544         /// @todo Defer initial generator spawn until after finalization.
545         map->initGenerators();
546 #endif
547 
548         // The game may need to perform it's own finalization now that the
549         // "current" map has changed.
550         de::Uri const mapUri = (map->hasManifest() ? map->manifest().composeUri() : de::makeUri("Maps:"));
551         if (gx.FinalizeMapChange)
552         {
553             gx.FinalizeMapChange(reinterpret_cast<uri_s const *>(&mapUri));
554         }
555 
556         if (gameTime > 20000000 / TICSPERSEC)
557         {
558             // In very long-running games, gameTime will become so large that
559             // it cannot be accurately converted to 35 Hz integer tics. Thus it
560             // needs to be reset back to zero.
561             gameTime = 0;
562         }
563 
564         // Init player values.
565         DoomsdayApp::players().forAll([] (Player &plr)
566         {
567             plr.extraLight        = 0;
568             plr.targetExtraLight  = 0;
569             plr.extraLightCounter = 0;
570 
571 #ifdef __CLIENT__
572             auto &client = plr.as<ClientPlayer>();
573 
574             // Determine the "invoid" status.
575             client.inVoid = true;
576             if (mobj_t const *mob = plr.publicData().mo)
577             {
578                 if (Mobj_HasSubsector(*mob))
579                 {
580                     auto const &subsec = Mobj_Subsector(*mob).as<ClientSubsector>();
581                     if (   mob->origin[2] >= subsec.visFloor  ().heightSmoothed()
582                         && mob->origin[2] <  subsec.visCeiling().heightSmoothed() - 4)
583                     {
584                         client.inVoid = false;
585                     }
586                 }
587             }
588 #endif
589 
590             return LoopContinue;
591         });
592 
593 #ifdef __CLIENT__
594         /*
595         /// @todo Refactor away:
596         map->forAllSectors([] (Sector &sector)
597         {
598             return sector.forAllSubsectors([] (Subsector &subsec)
599             {
600                 return subsec.as<ClientSubsector>().forAllEdgeLoops([] (ClEdgeLoop &loop)
601                 {
602                     loop.fixSurfacesMissingMaterials();
603                     return LoopContinue;
604                 });
605             });
606         });
607         */
608 #endif
609 
610         map->initPolyobjs();
611 
612 #ifdef __CLIENT__
613         App_AudioSystem().worldMapChanged();
614 #endif
615 
616 #ifdef __SERVER__
617         if (::isServer)
618         {
619             // Init server data.
620             Sv_InitPools();
621         }
622 #endif
623 
624 #ifdef __CLIENT__
625         GL_SetupFogFromMapInfo(mapInfo.accessedRecordPtr());
626 
627         //map->initLightGrid();
628         map->initSkyFix();
629         map->spawnPlaneParticleGens();
630 
631         // Precaching from 100 to 200.
632         Con_SetProgress(100);
633         Time begunPrecacheAt;
634         // Sky models usually have big skins.
635         rendSys().sky().cacheAssets();
636         App_Resources().cacheForCurrentMap();
637         App_Resources().processCacheQueue();
638         LOG_RES_VERBOSE("Precaching completed in %.2f seconds") << begunPrecacheAt.since();
639 
640         rendSys().clearDrawLists();
641         R_InitRendPolyPools();
642         Rend_UpdateLightModMatrix();
643 
644         map->initRadio();
645         map->initContactBlockmaps();
646         R_InitContactLists(*map);
647         rendSys().worldSystemMapChanged(*map);
648         //map->initBias();  // Shadow bias sources and surfaces.
649 
650         // Rewind/restart material animators.
651         /// @todo Only rewind animators responsible for map-surface contexts.
652         world::Materials::get().updateLookup();
653         world::Materials::get().forAnimatedMaterials([] (world::Material &material)
654         {
655             return material.as<ClientMaterial>().forAllAnimators([] (MaterialAnimator &animator)
656             {
657                 animator.rewind();
658                 return LoopContinue;
659             });
660         });
661 #endif
662 
663         /*
664          * Post-change map setup has now been fully completed.
665          */
666 
667         // Run any commands specified in MapInfo.
668         String execute = mapInfo.gets("execute");
669         if (!execute.isEmpty())
670         {
671             Con_Execute(CMDS_SCRIPT, execute.toUtf8().constData(), true, false);
672         }
673 
674         // Run the special map setup command, which the user may alias to do
675         // something useful.
676         if (!mapUri.isEmpty())
677         {
678             String cmd = String("init-") + mapUri.path();
679             if (Con_IsValidCommand(cmd.toUtf8().constData()))
680             {
681                 Con_Executef(CMDS_SCRIPT, false, "%s", cmd.toUtf8().constData());
682             }
683         }
684 
685         // Reset world time.
686         time = 0;
687 
688         // Now that the setup is done, let's reset the timer so that it will
689         // appear that no time has passed during the setup.
690         DD_ResetTimer();
691 
692 #ifdef __CLIENT__
693         // Make sure that the next frame doesn't use a filtered viewer.
694         R_ResetViewer();
695 
696         // Clear any input events that might have accumulated during setup.
697         ClientApp::inputSystem().clearEvents();
698 
699         // Inform the timing system to suspend the starting of the clock.
700         firstFrameAfterLoad = true;
701 #endif
702 
703         Z_PrintStatus();
704 
705         // Inform interested parties that the "current" map has changed.
706         self().notifyMapChange();
707     }
708 
709     /// @todo Split this into subtasks (load, make current, cache assets).
710     bool changeMap(res::MapManifest *mapManifest = nullptr)
711     {
712         Map *map = self().mapPtr();
713 
714         scheduler.clear();
715 
716 #ifdef __CLIENT__
717         if (map)
718         {
719             // Remove the current map from our audiences.
720             /// @todo Map should handle this.
721             self().audienceForFrameBegin() -= map;
722         }
723 #endif
724 
725         // As the memory zone does not provide the mechanisms to prepare another
726         // map in parallel we must free the current map first.
727         /// @todo The memory zone would still be useful if the purge and tagging
728         /// mechanisms allowed more fine grained control. It is no longer useful
729         /// for allocating memory used elsewhere so it should be repurposed for
730         /// this usage specifically.
731 #ifdef __CLIENT__
732         R_DestroyContactLists();
733 #endif
734         delete map;
735         self().setMap(nullptr);
736 
737         Z_FreeTags(PU_MAP, PU_PURGELEVEL - 1);
738 
739         // Are we just unloading the current map?
740         if (!mapManifest) return true;
741 
742         LOG_MSG("Loading map \"%s\"...") << mapManifest->composeUri().path();
743 
744         // A new map is about to be set up.
745         ::ddMapSetup = true;
746 
747         // Attempt to load in the new map.
748         MapConversionReporter reporter;
749         Map *newMap = loadMap(*mapManifest, &reporter);
750         if (newMap)
751         {
752             // The map may still be in an editable state -- switch to playable.
753             bool const mapIsPlayable = newMap->endEditing();
754 
755             // Cancel further reports about the map.
756             reporter.setMap(nullptr);
757 
758             if (!mapIsPlayable)
759             {
760                 // Darn. Discard the useless data.
761                 delete newMap; newMap = nullptr;
762             }
763         }
764 
765         // This becomes the new current map.
766         makeCurrent(newMap);
767 
768         // We've finished setting up the map.
769         ::ddMapSetup = false;
770 
771         // Output a human-readable report of any issues encountered during conversion.
772         reporter.writeLog();
773 
774         return self().hasMap();
775     }
776 
777 #ifdef __CLIENT__
778 #if 0
779     void updateHandOrigin()
780     {
781         DENG2_ASSERT(hand != nullptr && self().hasMap());
782 
783         viewdata_t const *viewData = &::viewPlayer->viewport();
784         hand->setOrigin(viewData->current.origin + viewData->frontVec.xzy() * handDistance);
785     }
786 #endif // 0
787 
788     DENG2_PIMPL_AUDIENCE(FrameBegin)
789     DENG2_PIMPL_AUDIENCE(FrameEnd)
790 #endif
791 };
792 
793 #ifdef __CLIENT__
DENG2_AUDIENCE_METHOD(ClientServerWorld,FrameBegin)794 DENG2_AUDIENCE_METHOD(ClientServerWorld, FrameBegin)
795 DENG2_AUDIENCE_METHOD(ClientServerWorld, FrameEnd)
796 #endif
797 
798 ClientServerWorld::ClientServerWorld()
799     : World()
800     , d(new Impl(this))
801 {}
802 
map() const803 Map &ClientServerWorld::map() const
804 {
805     if (!hasMap())
806     {
807         /// @throw MapError Attempted with no map loaded.
808         throw MapError("ClientServerWorld::map", "No map is currently loaded");
809     }
810     return World::map().as<Map>();
811 }
812 
changeMap(de::Uri const & mapUri)813 bool ClientServerWorld::changeMap(de::Uri const &mapUri)
814 {
815     res::MapManifest *mapDef = nullptr;
816 
817     if (!mapUri.path().isEmpty())
818     {
819         mapDef = App_Resources().mapManifests().tryFindMapManifest(mapUri);
820     }
821 
822     // Switch to busy mode (if we haven't already) except when simply unloading.
823     if (!mapUri.path().isEmpty() && !DoomsdayApp::app().busyMode().isActive())
824     {
825         /// @todo Use progress bar mode and update progress during the setup.
826         return DoomsdayApp::app().busyMode().runNewTaskWithName(
827                     BUSYF_ACTIVITY | BUSYF_PROGRESS_BAR | BUSYF_TRANSITION | (::verbose ? BUSYF_CONSOLE_OUTPUT : 0),
828                     "Loading map...", [this, &mapDef] (void *)
829         {
830             return d->changeMap(mapDef);
831         });
832     }
833     else
834     {
835         return d->changeMap(mapDef);
836     }
837 }
838 
reset()839 void ClientServerWorld::reset()
840 {
841     World::reset();
842 
843 #ifdef __CLIENT__
844     if (isClient)
845     {
846         Cl_ResetFrame();
847         Cl_InitPlayers();
848     }
849 #endif
850 
851     // If a map is currently loaded -- unload it.
852     unloadMap();
853 }
854 
update()855 void ClientServerWorld::update()
856 {
857     DoomsdayApp::players().forAll([] (Player &plr)
858     {
859         // States have changed, the state pointers are unknown.
860         for (ddpsprite_t &pspr : plr.publicData().pSprites)
861         {
862             pspr.statePtr = nullptr;
863         }
864         return LoopContinue;
865     });
866 
867     // Update the current map, also.
868     if (hasMap())
869     {
870         map().update();
871     }
872 }
873 
scheduler()874 Scheduler &ClientServerWorld::scheduler()
875 {
876     return d->scheduler;
877 }
878 
mapInfoForMapUri(de::Uri const & mapUri) const879 Record const &ClientServerWorld::mapInfoForMapUri(de::Uri const &mapUri) const
880 {
881     // Is there a MapInfo definition for the given URI?
882     if (Record const *def = DED_Definitions()->mapInfos.tryFind("id", mapUri.compose()))
883     {
884         return *def;
885     }
886     // Is there is a default definition (for all maps)?
887     if (Record const *def = DED_Definitions()->mapInfos.tryFind("id", de::Uri("Maps", Path("*")).compose()))
888     {
889         return *def;
890     }
891     // Use the fallback.
892     return d->fallbackMapInfo;
893 }
894 
advanceTime(timespan_t delta)895 void ClientServerWorld::advanceTime(timespan_t delta)
896 {
897 #if defined (__CLIENT__)
898     if (!::clientPaused)
899 #endif
900     {
901         d->time += delta;
902         d->scheduler.advanceTime(TimeSpan(delta));
903     }
904 }
905 
time() const906 timespan_t ClientServerWorld::time() const
907 {
908     return d->time;
909 }
910 
tick(timespan_t elapsed)911 void ClientServerWorld::tick(timespan_t elapsed)
912 {
913 #ifdef __CLIENT__
914     if (hasMap())
915     {
916         map().skyAnimator().advanceTime(elapsed);
917 
918         if (DD_IsSharpTick())
919         {
920             map().thinkers().forAll(reinterpret_cast<thinkfunc_t>(gx.MobjThinker), 0x1, [] (thinker_t *th)
921             {
922                 Mobj_AnimateHaloOcclussion(*reinterpret_cast<mobj_t *>(th));
923                 return LoopContinue;
924             });
925         }
926     }
927 #else
928     DENG2_UNUSED(elapsed);
929 #endif
930 }
931 
932 #ifdef __CLIENT__
933 #if 0
934 Hand &ClientServerWorld::hand(ddouble *distance) const
935 {
936     // Time to create the hand?
937     if (!d->hand)
938     {
939         d->hand.reset(new Hand());
940         audienceForFrameEnd() += *d->hand;
941         if (hasMap())
942         {
943             d->updateHandOrigin();
944         }
945     }
946     if (distance)
947     {
948         *distance = handDistance;
949     }
950     return *d->hand;
951 }
952 #endif // 0
953 
beginFrame(bool resetNextViewer)954 void ClientServerWorld::beginFrame(bool resetNextViewer)
955 {
956     // Notify interested parties that a new frame has begun.
957     DENG2_FOR_AUDIENCE2(FrameBegin, i) i->worldSystemFrameBegins(resetNextViewer);
958 }
959 
endFrame()960 void ClientServerWorld::endFrame()
961 {
962 #if 0
963     if (hasMap() && d->hand)
964     {
965         d->updateHandOrigin();
966 
967         // If the HueCircle is active update the current edit color.
968         if (HueCircle *hueCircle = SBE_HueCircle())
969         {
970             viewdata_t const *viewData = &viewPlayer->viewport();
971             d->hand->setEditColor(hueCircle->colorAt(viewData->frontVec));
972         }
973     }
974 #endif
975 
976     // Notify interested parties that the current frame has ended.
977     DENG2_FOR_AUDIENCE2(FrameEnd, i) i->worldSystemFrameEnds();
978 }
979 
980 #endif  // __CLIENT__
981 
consoleRegister()982 void ClientServerWorld::consoleRegister()  // static
983 {
984     //C_VAR_BYTE ("map-cache", &mapCache, 0, 0, 1);
985 #ifdef __CLIENT__
986     //C_VAR_FLOAT("edit-bias-grab-distance", &handDistance, 0, 10, 1000);
987 #endif
988     Map::consoleRegister();
989 }
990 
contextMobj(const Context & ctx)991 mobj_t &ClientServerWorld::contextMobj(const Context &ctx) // static
992 {
993     /// @todo Not necessarily always the current map. -jk
994     const int id = ctx.selfInstance().geti(QStringLiteral("__id__"), 0);
995     mobj_t *mo = App_World().map().thinkers().mobjById(id);
996     if (!mo)
997     {
998         throw Map::MissingObjectError("ClientServerWorld::contextMobj",
999                                       String::format("Mobj %d does not exist", id));
1000     }
1001     return *mo;
1002 }
1003