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 §or, 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 §or)
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