1 /** @file p_mobj.cpp World map objects.
2 *
3 * Various routines for moving mobjs, collision and Z checking.
4 *
5 * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
6 * @authors Copyright © 2006-2015 Daniel Swanson <danij@dengine.net>
7 * @authors Copyright © 1993-1996 by id Software, Inc.
8 *
9 * @par License
10 * GPL: http://www.gnu.org/licenses/gpl.html
11 *
12 * <small>This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by the
14 * Free Software Foundation; either version 2 of the License, or (at your
15 * option) any later version. This program is distributed in the hope that it
16 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
17 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
18 * Public License for more details. You should have received a copy of the GNU
19 * General Public License along with this program; if not, write to the Free
20 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21 * 02110-1301 USA</small>
22 */
23
24 #include "de_base.h"
25 #include "world/p_object.h"
26
27 #include <cmath>
28 #include <de/vector1.h>
29 #include <de/Error>
30 #include <de/LogBuffer>
31 #include <doomsday/console/cmd.h>
32 #include <doomsday/console/exec.h>
33 #include <doomsday/console/var.h>
34 #include <doomsday/defs/sprite.h>
35 #include <doomsday/res/Textures>
36 #include <doomsday/res/Sprites>
37 #include <doomsday/world/mobjthinkerdata.h>
38 #include <doomsday/world/Materials>
39
40 #include "def_main.h"
41 #include "api_sound.h"
42 #include "network/net_main.h"
43
44 #ifdef __CLIENT__
45 # include "client/cl_mobj.h"
46 # include "client/clientsubsector.h"
47 # include "gl/gl_tex.h"
48 # include "network/net_demo.h"
49 # include "render/viewports.h"
50 # include "render/rend_main.h"
51 # include "render/rend_model.h"
52 # include "render/rend_halo.h"
53 # include "render/billboard.h"
54 #endif
55
56 #include "world/clientserverworld.h" // validCount
57 #include "world/p_object.h"
58 #include "world/p_players.h"
59 #include "world/thinkers.h"
60 #include "BspLeaf"
61 #include "ConvexSubspace"
62 #include "Subsector"
63
64 #ifdef __CLIENT__
65 # include "Generator"
66 # include "Lumobj"
67 #endif
68
69 using namespace de;
70 using namespace world;
71
72 static String const VAR_MATERIAL("material");
73
74 static mobj_t *unusedMobjs;
75
76 /*
77 * Console variables:
78 */
79 dint useSRVO = 2; ///< @c 1= models only, @c 2= sprites + models
80 dint useSRVOAngle = 1;
81
82 #ifdef __CLIENT__
83 static byte mobjAutoLights = true;
84 #endif
85
86 /**
87 * Called during map loading.
88 */
P_InitUnusedMobjList()89 void P_InitUnusedMobjList()
90 {
91 // Any zone memory allocated for the mobjs will have already been purged.
92 ::unusedMobjs = nullptr;
93 }
94
95 /**
96 * All mobjs must be allocated through this routine. Part of the public API.
97 */
P_MobjCreate(thinkfunc_t function,Vector3d const & origin,angle_t angle,coord_t radius,coord_t height,dint ddflags)98 mobj_t *P_MobjCreate(thinkfunc_t function, Vector3d const &origin, angle_t angle,
99 coord_t radius, coord_t height, dint ddflags)
100 {
101 if (!function)
102 App_Error("P_MobjCreate: Think function invalid, cannot create mobj.");
103
104 #ifdef DENG2_DEBUG
105 if (::isClient)
106 {
107 LOG_VERBOSE("P_MobjCreate: Client creating mobj at %s")
108 << origin.asText();
109 }
110 #endif
111
112 // Do we have any unused mobjs we can reuse?
113 mobj_t *mob;
114 if (::unusedMobjs)
115 {
116 mob = ::unusedMobjs;
117 ::unusedMobjs = ::unusedMobjs->sNext;
118 }
119 else
120 {
121 // No, we need to allocate another.
122 mob = MobjThinker(Thinker::AllocateMemoryZone).take();
123 }
124
125 V3d_Set(mob->origin, origin.x, origin.y, origin.z);
126 mob->angle = angle;
127 mob->visAngle = mob->angle >> 16; // "angle-servo"; smooth actor turning.
128 mob->radius = radius;
129 mob->height = height;
130 mob->ddFlags = ddflags;
131 mob->lumIdx = -1;
132 mob->thinker.function = function;
133 Mobj_Map(*mob).thinkers().add(mob->thinker);
134
135 return mob;
136 }
137
138 /**
139 * All mobjs must be destroyed through this routine. Part of the public API.
140 *
141 * @note Does not actually destroy the mobj. Instead, mobj is marked as
142 * awaiting removal (which occurs when its turn for thinking comes around).
143 */
144 #undef Mobj_Destroy
Mobj_Destroy(mobj_t * mo)145 DENG_EXTERN_C void Mobj_Destroy(mobj_t *mo)
146 {
147 #ifdef _DEBUG
148 if (mo->ddFlags & DDMF_MISSILE)
149 {
150 LOG_AS("Mobj_Destroy");
151 LOG_MAP_XVERBOSE("Destroying missile %i", mo->thinker.id);
152 }
153 #endif
154
155 // Unlink from sector and block lists.
156 Mobj_Unlink(mo);
157
158 S_StopSound(0, mo);
159
160 Mobj_Map(*mo).thinkers().remove(reinterpret_cast<thinker_t &>(*mo));
161 }
162
163 /**
164 * Called when a mobj is actually removed (when it's thinking turn comes around).
165 * The mobj is moved to the unused list to be reused later.
166 */
P_MobjRecycle(mobj_t * mo)167 void P_MobjRecycle(mobj_t* mo)
168 {
169 // Release the private data.
170 MobjThinker::zap(*mo);
171
172 // The sector next link is used as the unused mobj list links.
173 mo->sNext = unusedMobjs;
174 unusedMobjs = mo;
175 }
176
Mobj_IsSectorLinked(mobj_t const & mob)177 bool Mobj_IsSectorLinked(mobj_t const &mob)
178 {
179 return (mob._bspLeaf != nullptr && mob.sPrev != nullptr);
180 }
181
182 #undef Mobj_SetState
Mobj_SetState(mobj_t * mob,int statenum)183 DENG_EXTERN_C void Mobj_SetState(mobj_t *mob, int statenum)
184 {
185 if (!mob) return;
186
187 state_t const *oldState = mob->state;
188
189 DENG2_ASSERT(statenum >= 0 && statenum < DED_Definitions()->states.size());
190
191 mob->state = &runtimeDefs.states[statenum];
192 mob->tics = mob->state->tics;
193 mob->sprite = mob->state->sprite;
194 mob->frame = mob->state->frame;
195
196 if (!(mob->ddFlags & DDMF_REMOTE))
197 {
198 String const exec = DED_Definitions()->states[statenum].gets("execute");
199 if (!exec.isEmpty())
200 {
201 Con_Execute(CMDS_SCRIPT, exec.toUtf8(), true, false);
202 }
203 }
204
205 // Notify private data about the changed state.
206 if (!mob->thinker.d)
207 {
208 Thinker_InitPrivateData(&mob->thinker);
209 }
210 if (MobjThinkerData *data = THINKER_DATA_MAYBE(mob->thinker, MobjThinkerData))
211 {
212 data->stateChanged(oldState);
213 }
214 }
215
Mobj_Origin(mobj_t const & mob)216 Vector3d Mobj_Origin(mobj_t const &mob)
217 {
218 return Vector3d(mob.origin);
219 }
220
Mobj_Center(mobj_t & mob)221 Vector3d Mobj_Center(mobj_t &mob)
222 {
223 return Vector3d(mob.origin[0], mob.origin[1], mob.origin[2] + mob.height / 2);
224 }
225
Mobj_SetOrigin(struct mobj_s * mob,coord_t x,coord_t y,coord_t z)226 dd_bool Mobj_SetOrigin(struct mobj_s *mob, coord_t x, coord_t y, coord_t z)
227 {
228 if(!gx.MobjTryMoveXYZ)
229 {
230 return false;
231 }
232 return gx.MobjTryMoveXYZ(mob, x, y, z);
233 }
234
235 #undef Mobj_OriginSmoothed
Mobj_OriginSmoothed(mobj_t * mob,coord_t origin[3])236 DENG_EXTERN_C void Mobj_OriginSmoothed(mobj_t *mob, coord_t origin[3])
237 {
238 if (!origin) return;
239
240 V3d_Set(origin, 0, 0, 0);
241 if (!mob) return;
242
243 V3d_Copy(origin, mob->origin);
244
245 // Apply a Short Range Visual Offset?
246 if (useSRVO && mob->state && mob->tics >= 0)
247 {
248 ddouble const mul = mob->tics / dfloat( mob->state->tics );
249 vec3d_t srvo;
250
251 V3d_Copy(srvo, mob->srvo);
252 V3d_Scale(srvo, mul);
253 V3d_Sum(origin, origin, srvo);
254 }
255
256 #ifdef __CLIENT__
257 if (mob->dPlayer)
258 {
259 /// @todo What about splitscreen? We have smoothed origins for all local players.
260 if (P_GetDDPlayerIdx(mob->dPlayer) == consolePlayer
261 // $voodoodolls: Must be a real player to use the smoothed origin.
262 && mob->dPlayer->mo == mob)
263 {
264 viewdata_t const *vd = &DD_Player(consolePlayer)->viewport();
265 V3d_Set(origin, vd->current.origin.x, vd->current.origin.y, vd->current.origin.z);
266 }
267 // The client may have a Smoother for this object.
268 else if (isClient)
269 {
270 Smoother_Evaluate(DD_Player(P_GetDDPlayerIdx(mob->dPlayer))->smoother(), origin);
271 }
272 }
273 #endif
274 }
275
Mobj_Map(mobj_t const & mob)276 world::Map &Mobj_Map(mobj_t const &mob)
277 {
278 return Thinker_Map(mob.thinker);
279 }
280
Mobj_IsLinked(mobj_t const & mob)281 bool Mobj_IsLinked(mobj_t const &mob)
282 {
283 return mob._bspLeaf != 0;
284 }
285
Mobj_BspLeafAtOrigin(mobj_t const & mob)286 BspLeaf &Mobj_BspLeafAtOrigin(mobj_t const &mob)
287 {
288 if (Mobj_IsLinked(mob))
289 {
290 return *(BspLeaf *)mob._bspLeaf;
291 }
292 throw Error("Mobj_BspLeafAtOrigin", "Mobj is not yet linked");
293 }
294
Mobj_HasSubsector(mobj_t const & mob)295 bool Mobj_HasSubsector(mobj_t const &mob)
296 {
297 if (!Mobj_IsLinked(mob)) return false;
298 BspLeaf const &bspLeaf = Mobj_BspLeafAtOrigin(mob);
299 if (!bspLeaf.hasSubspace()) return false;
300 return bspLeaf.subspace().hasSubsector();
301 }
302
Mobj_Subsector(mobj_t const & mob)303 Subsector &Mobj_Subsector(mobj_t const &mob)
304 {
305 return Mobj_BspLeafAtOrigin(mob).subspace().subsector();
306 }
307
Mobj_SubsectorPtr(mobj_t const & mob)308 Subsector *Mobj_SubsectorPtr(mobj_t const &mob)
309 {
310 return Mobj_HasSubsector(mob) ? &Mobj_Subsector(mob) : nullptr;
311 }
312
313 #undef Mobj_Sector
Mobj_Sector(mobj_t const * mob)314 DENG_EXTERN_C Sector *Mobj_Sector(mobj_t const *mob)
315 {
316 if (!mob || !Mobj_IsLinked(*mob)) return nullptr;
317 return Mobj_BspLeafAtOrigin(*mob).sectorPtr();
318 }
319
Mobj_SpawnParticleGen(mobj_t * mob,ded_ptcgen_t const * def)320 void Mobj_SpawnParticleGen(mobj_t *mob, ded_ptcgen_t const *def)
321 {
322 #ifdef __CLIENT__
323 DENG2_ASSERT(mob && def);
324
325 //if (!useParticles) return;
326
327 Generator *gen = Mobj_Map(*mob).newGenerator();
328 if (!gen) return;
329
330 /*LOG_INFO("SpawnPtcGen: %s/%i (src:%s typ:%s mo:%p)")
331 << def->state << (def - defs.ptcgens) << defs.states[mob->state-states].id
332 << defs.mobjs[mob->type].id << source;*/
333
334 // Initialize the particle generator.
335 gen->count = def->particles;
336 // Size of source sector might determine count.
337 if (def->flags & Generator::ScaledRate)
338 {
339 gen->spawnRateMultiplier = Mobj_BspLeafAtOrigin(*mob).sectorPtr()->roughArea() / (128 * 128);
340 }
341 else
342 {
343 gen->spawnRateMultiplier = 1;
344 }
345
346 gen->configureFromDef(def);
347 gen->source = mob;
348 gen->srcid = mob->thinker.id;
349
350 // Is there a need to pre-simulate?
351 gen->presimulate(def->preSim);
352 #else
353 DENG2_UNUSED2(mob, def);
354 #endif
355 }
356
357 #undef Mobj_SpawnDamageParticleGen
Mobj_SpawnDamageParticleGen(mobj_t const * mob,mobj_t const * inflictor,int amount)358 DENG_EXTERN_C void Mobj_SpawnDamageParticleGen(mobj_t const *mob, mobj_t const *inflictor, int amount)
359 {
360 #ifdef __CLIENT__
361 if (!mob || !inflictor || amount <= 0) return;
362
363 // Are particles allowed?
364 //if (!useParticles) return;
365
366 ded_ptcgen_t const *def = Def_GetDamageGenerator(mob->type);
367 if (def)
368 {
369 Generator *gen = Mobj_Map(*mob).newGenerator();
370 if (!gen) return; // No more generators.
371
372 gen->count = def->particles;
373 gen->configureFromDef(def);
374 gen->setUntriggered();
375
376 gen->spawnRateMultiplier = de::max(amount, 1);
377
378 // Calculate appropriate center coordinates.
379 gen->originAtSpawn[0] += FLT2FIX(mob->origin[0]);
380 gen->originAtSpawn[1] += FLT2FIX(mob->origin[1]);
381 gen->originAtSpawn[2] += FLT2FIX(mob->origin[2] + mob->height / 2);
382
383 // Calculate launch vector.
384 vec3f_t vecDelta;
385 V3f_Set(vecDelta, inflictor->origin[0] - mob->origin[0],
386 inflictor->origin[1] - mob->origin[1],
387 (inflictor->origin[2] - inflictor->height / 2) - (mob->origin[2] + mob->height / 2));
388
389 vec3f_t vector;
390 V3f_SetFixed(vector, gen->vector[0], gen->vector[1], gen->vector[2]);
391 V3f_Sum(vector, vector, vecDelta);
392 V3f_Normalize(vector);
393
394 gen->vector[0] = FLT2FIX(vector[0]);
395 gen->vector[1] = FLT2FIX(vector[1]);
396 gen->vector[2] = FLT2FIX(vector[2]);
397
398 // Is there a need to pre-simulate?
399 gen->presimulate(def->preSim);
400 }
401 #else
402 DENG2_UNUSED3(mob, inflictor, amount);
403 #endif
404 }
405
406 #ifdef __CLIENT__
407
Mobj_OriginBehindVisPlane(mobj_t * mob)408 dd_bool Mobj_OriginBehindVisPlane(mobj_t *mob)
409 {
410 if (!mob || !Mobj_HasSubsector(*mob)) return false;
411
412 auto &subsec = Mobj_Subsector(*mob).as<ClientSubsector>();
413
414 if (&subsec.sector().floor() != &subsec.visFloor()
415 && mob->origin[2] < subsec.visFloor().heightSmoothed())
416 return true;
417
418 if (&subsec.sector().ceiling() != &subsec.visCeiling()
419 && mob->origin[2] > subsec.visCeiling().heightSmoothed())
420 return true;
421
422 return false;
423 }
424
Mobj_UnlinkLumobjs(mobj_t * mob)425 void Mobj_UnlinkLumobjs(mobj_t *mob)
426 {
427 if (!mob) return;
428 mob->lumIdx = Lumobj::NoIndex;
429 }
430
lightDefByMobjState(state_t const * state)431 static ded_light_t *lightDefByMobjState(state_t const *state)
432 {
433 if (state)
434 {
435 return runtimeDefs.stateInfo[runtimeDefs.states.indexOf(state)].light;
436 }
437 return nullptr;
438 }
439
lightmap(de::Uri const * textureUri)440 static inline ClientTexture *lightmap(de::Uri const *textureUri)
441 {
442 if(!textureUri) return nullptr;
443 return static_cast<ClientTexture *>
444 (res::Textures::get().tryFindTextureByResourceUri(QStringLiteral("Lightmaps"), *textureUri));
445 }
446
Mobj_GenerateLumobjs(mobj_t * mob)447 void Mobj_GenerateLumobjs(mobj_t *mob)
448 {
449 if (!mob) return;
450
451 Mobj_UnlinkLumobjs(mob);
452
453 if (!Mobj_HasSubsector(*mob)) return;
454 auto &subsec = Mobj_Subsector(*mob).as<ClientSubsector>();
455
456 if (!(((mob->state && (mob->state->flags & STF_FULLBRIGHT))
457 && !(mob->ddFlags & DDMF_DONTDRAW))
458 || (mob->ddFlags & DDMF_ALWAYSLIT)))
459 {
460 return;
461 }
462
463 // Are the automatically calculated light values for fullbright sprite frames in use?
464 if (mob->state
465 && (!mobjAutoLights || (mob->state->flags & STF_NOAUTOLIGHT))
466 && !runtimeDefs.stateInfo[runtimeDefs.states.indexOf(mob->state)].light)
467 {
468 return;
469 }
470
471 // If the mobj's origin is outside the BSP leaf it is linked within, then
472 // this means it is outside the playable map (and no light should be emitted).
473 /// @todo Optimize: Mobj_Link() should do this and flag the mobj accordingly.
474 if (!Mobj_BspLeafAtOrigin(*mob).subspace().contains(mob->origin))
475 return;
476
477 // Always use the front view of the Sprite when determining light properties.
478 Record const *spriteRec = Mobj_SpritePtr(*mob);
479 if (!spriteRec) return;
480
481 // Lookup the Material for the Sprite and prepare the animator.
482 MaterialAnimator *matAnimator = Rend_SpriteMaterialAnimator(*spriteRec);
483 if (!matAnimator) return;
484 matAnimator->prepare(); // Ensure we have up-to-date info.
485
486 TextureVariant *tex = matAnimator->texUnit(MaterialAnimator::TU_LAYER0).texture;
487 if (!tex) return; // Unloadable texture?
488 Vector2i const &texOrigin = tex->base().origin();
489
490 // Will the visual be allowed to go inside the floor?
491 /// @todo Handle this as occlusion so that the halo fades smoothly.
492 coord_t impacted = mob->origin[2] + -texOrigin.y - matAnimator->dimensions().y
493 - subsec.visFloor().heightSmoothed();
494
495 // If the floor is a visual plane then no light should be emitted.
496 if (impacted < 0 && &subsec.visFloor() != &subsec.sector().floor())
497 return;
498
499 // Attempt to generate luminous object from the sprite.
500 std::unique_ptr<Lumobj> lum(Rend_MakeLumobj(*spriteRec));
501 if (!lum) return;
502
503 lum->setSourceMobj(mob);
504
505 // A light definition may override the (auto-calculated) defaults.
506 if (ded_light_t *def = lightDefByMobjState(mob->state))
507 {
508 if (!de::fequal(def->size, 0))
509 {
510 lum->setRadius(de::max(def->size, 32.f / (40 * lum->radiusFactor())));
511 }
512
513 if (!de::fequal(def->offset[1], 0))
514 {
515 lum->setZOffset(-texOrigin.y - def->offset[1]);
516 }
517
518 if (Vector3f(def->color) != Vector3f(0, 0, 0))
519 {
520 lum->setColor(def->color);
521 }
522
523 lum->setLightmap(Lumobj::Side, lightmap(def->sides))
524 .setLightmap(Lumobj::Down, lightmap(def->down))
525 .setLightmap(Lumobj::Up, lightmap(def->up));
526 }
527
528 // Translate to the mobj's origin in map space.
529 lum->move(mob->origin);
530
531 // Does the mobj need a Z origin offset?
532 coord_t zOffset = -mob->floorClip - Mobj_BobOffset(*mob);
533 if (!(mob->ddFlags & DDMF_NOFITBOTTOM) && impacted < 0)
534 {
535 // Raise the light out of the impacted surface.
536 zOffset -= impacted;
537 }
538 lum->setZOffset(lum->zOffset() + zOffset);
539
540 // Insert a copy of the temporary lumobj in the map and remember it's unique
541 // index in the mobj (this'll allow a halo to be rendered).
542 mob->lumIdx = subsec.sector().map().addLumobj(lum.release()).indexInMap();
543 }
544
Mobj_AnimateHaloOcclussion(mobj_t & mob)545 void Mobj_AnimateHaloOcclussion(mobj_t &mob)
546 {
547 for (dint i = 0; i < DDMAXPLAYERS; ++i)
548 {
549 dbyte *haloFactor = &mob.haloFactors[i];
550
551 // Set the high bit of halofactor if the light is clipped. This will
552 // make P_Ticker diminish the factor to zero. Take the first step here
553 // and now, though.
554 if (mob.lumIdx == Lumobj::NoIndex || R_ViewerLumobjIsClipped(mob.lumIdx))
555 {
556 if (*haloFactor & 0x80)
557 {
558 dint f = (*haloFactor & 0x7f); // - haloOccludeSpeed;
559 if (f < 0) f = 0;
560 *haloFactor = f;
561 }
562 }
563 else
564 {
565 if (!(*haloFactor & 0x80))
566 {
567 dint f = (*haloFactor & 0x7f); // + haloOccludeSpeed;
568 if (f > 127) f = 127;
569 *haloFactor = 0x80 | f;
570 }
571 }
572
573 // Handle halofactor.
574 dint f = *haloFactor & 0x7f;
575 if (*haloFactor & 0x80)
576 {
577 // Going up.
578 f += ::haloOccludeSpeed;
579 if (f > 127)
580 f = 127;
581 }
582 else
583 {
584 // Going down.
585 f -= ::haloOccludeSpeed;
586 if (f < 0)
587 f = 0;
588 }
589
590 *haloFactor &= ~0x7f;
591 *haloFactor |= f;
592 }
593 }
594
Mobj_ShadowStrength(mobj_t const & mob)595 dfloat Mobj_ShadowStrength(mobj_t const &mob)
596 {
597 static dfloat const minSpriteAlphaLimit = .1f;
598
599 // A shadow is not cast if the map-object is not linked in the map.
600 if (!Mobj_HasSubsector(mob)) return 0;
601 // ...or the current state is invalid or full-bright.
602 if (!mob.state || (mob.state->flags & STF_FULLBRIGHT)) return 0;
603 // ...or it won't be drawn at all.
604 if (mob.ddFlags & DDMF_DONTDRAW) return 0;
605 // ...or is "always lit" (?).
606 if (mob.ddFlags & DDMF_ALWAYSLIT) return 0;
607
608 // Evaluate the ambient light level at our map origin.
609 auto const &subsec = Mobj_Subsector(mob).as<ClientSubsector>();
610 dfloat ambientLightLevel;
611 #if 0
612 if (::useBias && subsec.sector().map().hasLightGrid())
613 {
614 ambientLightLevel = subsec.sector().map().lightGrid().evaluateIntensity(mob.origin);
615 }
616 else
617 #endif
618 {
619 ambientLightLevel = subsec.lightSourceIntensity();
620 }
621 Rend_ApplyLightAdaptation(ambientLightLevel);
622
623 // Sprites have their own shadow strength factor.
624 dfloat strength = .65f; ///< Default.
625 if (!::useModels || !Mobj_ModelDef(mob))
626 {
627 if (Record const *spriteRec = Mobj_SpritePtr(mob))
628 {
629 auto &matAnimator = *Rend_SpriteMaterialAnimator(*spriteRec); // world::Materials::get().materialPtr(sprite.viewMaterial(0)))
630 matAnimator.prepare(); // Ensure we have up-to-date info.
631
632 if (TextureVariant const *texture = matAnimator.texUnit(MaterialAnimator::TU_LAYER0).texture)
633 {
634 auto const *aa = (averagealpha_analysis_t const *)texture->base().analysisDataPointer(res::Texture::AverageAlphaAnalysis);
635 DENG2_ASSERT(aa);
636
637 // We use an average which factors in the coverage ratio of
638 // alpha:non-alpha pixels.
639 /// @todo Constant weights could stand some tweaking...
640 dfloat weightedSpriteAlpha = aa->alpha * (0.4f + (1 - aa->coverage) * 0.6f);
641
642 // Almost entirely translucent sprite? => no shadow.
643 if(weightedSpriteAlpha < minSpriteAlphaLimit) return 0;
644
645 // Apply this factor.
646 strength *= de::min(1.f, .2f + weightedSpriteAlpha);
647 }
648 }
649 }
650
651 // Factor in Mobj alpha.
652 strength *= Mobj_Alpha(mob);
653
654 /// @note This equation is the same as that used for fakeradio.
655 return (0.6f - ambientLightLevel * 0.4f) * strength;
656 }
657
Mobj_SpritePtr(mobj_t const & mob)658 Record const *Mobj_SpritePtr(mobj_t const &mob)
659 {
660 return res::Sprites::get().spritePtr(mob.sprite, mob.frame);
661 }
662
Mobj_ModelDef(mobj_t const & mo,FrameModelDef ** retNextModef,float * retInter)663 FrameModelDef *Mobj_ModelDef(mobj_t const &mo, FrameModelDef **retNextModef, float *retInter)
664 {
665 // By default there are no models.
666 if (retNextModef) *retNextModef = 0;
667 if (retInter) *retInter = -1;
668
669 // On the client it is possible that we don't know the mobj's state.
670 if (!mo.state) return 0;
671
672 state_t &st = *mo.state;
673 FrameModelDef *modef = App_Resources().modelDefForState(runtimeDefs.states.indexOf(&st), mo.selector);
674 if (!modef) return 0; // No model available.
675
676 dfloat interp = -1;
677
678 // World time animation?
679 bool worldTime = false;
680 if (modef->flags & MFF_WORLD_TIME_ANIM)
681 {
682 dfloat duration = modef->interRange[0];
683 dfloat offset = modef->interRange[1];
684
685 // Validate/modify the values.
686 if (duration == 0) duration = 1;
687
688 if (offset == -1)
689 {
690 offset = M_CycleIntoRange(MOBJ_TO_ID(&mo), duration);
691 }
692
693 interp = M_CycleIntoRange(App_World().time() / duration + offset, 1);
694 worldTime = true;
695 }
696 else
697 {
698 // Calculate the currently applicable intermark.
699 interp = 1.0f - (mo.tics - frameTimePos) / dfloat( st.tics );
700 }
701
702 /*#if _DEBUG
703 if (mo.dPlayer)
704 {
705 qDebug() << "itp:" << interp << " mot:" << mo.tics << " stt:" << st.tics;
706 }
707 #endif*/
708
709 // First find the modef for the interpoint. Intermark is 'stronger' than interrange.
710
711 // Scan interlinks.
712 while (modef->interNext && modef->interNext->interMark <= interp)
713 {
714 modef = modef->interNext;
715 }
716
717 if (!worldTime)
718 {
719 // Scale to the modeldef's interpolation range.
720 interp = modef->interRange[0] + interp
721 * (modef->interRange[1] - modef->interRange[0]);
722 }
723
724 // What would be the next model? Check interlinks first.
725 if (retNextModef)
726 {
727 if (modef->interNext)
728 {
729 *retNextModef = modef->interNext;
730 }
731 else if (worldTime)
732 {
733 *retNextModef = App_Resources().modelDefForState(runtimeDefs.states.indexOf(&st), mo.selector);
734 }
735 else if (st.nextState > 0) // Check next state.
736 {
737 // Find the appropriate state based on interrange.
738 state_t *it = &runtimeDefs.states[st.nextState];
739 bool foundNext = false;
740 if (modef->interRange[1] < 1)
741 {
742 // Current modef doesn't interpolate to the end, find the proper destination
743 // modef (it isn't just the next one). Scan the states that follow (and
744 // interlinks of each).
745 bool stopScan = false;
746 dint max = 20; // Let's not be here forever...
747 while (!stopScan)
748 {
749 if(!(( !App_Resources().modelDefForState(runtimeDefs.states.indexOf(it))
750 || App_Resources().modelDefForState(runtimeDefs.states.indexOf(it), mo.selector)->interRange[0] > 0)
751 && it->nextState > 0))
752 {
753 stopScan = true;
754 }
755 else
756 {
757 // Scan interlinks, then go to the next state.
758 FrameModelDef *mdit = App_Resources().modelDefForState(runtimeDefs.states.indexOf(it), mo.selector);
759 if (mdit && mdit->interNext)
760 {
761 forever
762 {
763 mdit = mdit->interNext;
764 if (mdit)
765 {
766 if (mdit->interRange[0] <= 0) // A new beginning?
767 {
768 *retNextModef = mdit;
769 foundNext = true;
770 }
771 }
772
773 if (!mdit || foundNext)
774 {
775 break;
776 }
777 }
778 }
779
780 if (foundNext)
781 {
782 stopScan = true;
783 }
784 else
785 {
786 it = &runtimeDefs.states[it->nextState];
787 }
788 }
789
790 if (max-- <= 0)
791 stopScan = true;
792 }
793 // @todo What about max == -1? What should 'it' be then?
794 }
795
796 if (!foundNext)
797 {
798 *retNextModef = App_Resources().modelDefForState(runtimeDefs.states.indexOf(it), mo.selector);
799 }
800 }
801 }
802
803 if (retInter) *retInter = interp;
804
805 return modef;
806 }
807
808 #endif // __CLIENT__
809
810 #undef Mobj_AngleSmoothed
Mobj_AngleSmoothed(mobj_t * mob)811 DENG_EXTERN_C angle_t Mobj_AngleSmoothed(mobj_t *mob)
812 {
813 if (!mob) return 0;
814
815 #ifdef __CLIENT__
816 if (mob->dPlayer)
817 {
818 /// @todo What about splitscreen? We have smoothed angles for all local players.
819 if (P_GetDDPlayerIdx(mob->dPlayer) == ::consolePlayer
820 // $voodoodolls: Must be a real player to use the smoothed angle.
821 && mob->dPlayer->mo == mob)
822 {
823 viewdata_t const *vd = &DD_Player(::consolePlayer)->viewport();
824 return vd->current.angle();
825 }
826 }
827
828 // Apply a Short Range Visual Offset?
829 if (::useSRVOAngle && !::netGame && !::playback)
830 {
831 return mob->visAngle << 16;
832 }
833 #endif
834
835 return mob->angle;
836 }
837
Mobj_ApproxPointDistance(mobj_t const * mob,coord_t const * point)838 coord_t Mobj_ApproxPointDistance(mobj_t const *mob, coord_t const *point)
839 {
840 if (!mob || !point) return 0;
841 return M_ApproxDistance(point[2] - mob->origin[2],
842 M_ApproxDistance(point[0] - mob->origin[0],
843 point[1] - mob->origin[1]));
844 }
845
Mobj_BobOffset(mobj_t const & mob)846 coord_t Mobj_BobOffset(mobj_t const &mob)
847 {
848 if (mob.ddFlags & DDMF_BOB)
849 {
850 return (sin(MOBJ_TO_ID(&mob) + App_World().time() / 1.8286 * 2 * PI) * 8);
851 }
852 return 0;
853 }
854
Mobj_Alpha(mobj_t const & mob)855 dfloat Mobj_Alpha(mobj_t const &mob)
856 {
857 dfloat alpha = (mob.ddFlags & DDMF_BRIGHTSHADOW)? .80f :
858 (mob.ddFlags & DDMF_SHADOW )? .33f :
859 (mob.ddFlags & DDMF_ALTSHADOW )? .66f : 1;
860
861 // The three highest bits of the selector are used for alpha.
862 // 0 = opaque (alpha -1)
863 // 1 = 1/8 transparent
864 // 4 = 1/2 transparent
865 // 7 = 7/8 transparent
866 dint selAlpha = mob.selector >> DDMOBJ_SELECTOR_SHIFT;
867 if (selAlpha & 0xe0)
868 {
869 alpha *= 1 - ((selAlpha & 0xe0) >> 5) / 8.0f;
870 }
871 else if (mob.translucency)
872 {
873 alpha *= 1 - mob.translucency * reciprocal255;
874 }
875 return alpha;
876 }
877
Mobj_Radius(mobj_t const & mobj)878 coord_t Mobj_Radius(mobj_t const &mobj)
879 {
880 return mobj.radius;
881 }
882
883 #ifdef __CLIENT__
Mobj_ShadowRadius(mobj_t const & mobj)884 coord_t Mobj_ShadowRadius(mobj_t const &mobj)
885 {
886 if (useModels)
887 {
888 if (FrameModelDef *modef = Mobj_ModelDef(mobj))
889 {
890 if (modef->shadowRadius > 0)
891 {
892 return modef->shadowRadius;
893 }
894 }
895 }
896 // Fall back to the visual radius.
897 return Mobj_VisualRadius(mobj);
898 }
899 #endif
900
Mobj_VisualRadius(mobj_t const & mob)901 coord_t Mobj_VisualRadius(mobj_t const &mob)
902 {
903 #ifdef __CLIENT__
904 // Is a model in effect?
905 if (useModels)
906 {
907 if (FrameModelDef *modef = Mobj_ModelDef(mob))
908 {
909 return modef->visualRadius;
910 }
911 }
912
913 // Is a sprite in effect?
914 if (Record const *sprite = Mobj_SpritePtr(mob))
915 {
916 return Rend_VisualRadius(*sprite);
917 }
918 #endif
919
920 // Use the physical radius.
921 return Mobj_Radius(mob);
922 }
923
Mobj_Bounds(mobj_t const & mobj)924 AABoxd Mobj_Bounds(mobj_t const &mobj)
925 {
926 Vector2d const origin = Mobj_Origin(mobj);
927 ddouble const radius = Mobj_Radius(mobj);
928 return AABoxd(origin.x - radius, origin.y - radius,
929 origin.x + radius, origin.y + radius);
930 }
931
D_CMD(InspectMobj)932 D_CMD(InspectMobj)
933 {
934 DENG2_UNUSED(src);
935
936 if (argc != 2)
937 {
938 LOG_SCR_NOTE("Usage: %s (mobj-id)") << argv[0];
939 return true;
940 }
941
942 // Get the ID.
943 auto const id = thid_t( String(argv[1]).toInt() );
944 // Find the map-object.
945 mobj_t *mob = App_World().map().thinkers().mobjById(id);
946 if (!mob)
947 {
948 LOG_MAP_ERROR("Mobj with id %i not found") << id;
949 return false;
950 }
951
952 char const *mobType = "Mobj";
953 #ifdef __CLIENT__
954 ClientMobjThinkerData::RemoteSync *info = ClMobj_GetInfo(mob);
955 if (info) mobType = "CLMOBJ";
956 #endif
957
958 LOG_MAP_MSG("%s %i [%p] State:%s (%i)")
959 << mobType << id << mob << Def_GetStateName(mob->state) << ::runtimeDefs.states.indexOf(mob->state);
960 LOG_MAP_MSG("Type:%s (%i) Info:[%p] %s")
961 << DED_Definitions()->getMobjName(mob->type) << mob->type << mob->info
962 << (mob->info ? QString(" (%1)").arg(::runtimeDefs.mobjInfo.indexOf(mob->info)) : "");
963 LOG_MAP_MSG("Tics:%i ddFlags:%08x") << mob->tics << mob->ddFlags;
964 #ifdef __CLIENT__
965 if (info)
966 {
967 LOG_MAP_MSG("Cltime:%i (now:%i) Flags:%04x") << info->time << Timer_RealMilliseconds() << info->flags;
968 }
969 #endif
970 LOG_MAP_MSG("Flags:%08x Flags2:%08x Flags3:%08x") << mob->flags << mob->flags2 << mob->flags3;
971 LOG_MAP_MSG("Height:%f Radius:%f") << mob->height << mob->radius;
972 LOG_MAP_MSG("Angle:%x Pos:%s Mom:%s")
973 << mob->angle
974 << Vector3d(mob->origin).asText()
975 << Vector3d(mob->mom).asText();
976 #ifdef __CLIENT__
977 LOG_MAP_MSG("VisAngle:%x") << mob->visAngle;
978 #endif
979 LOG_MAP_MSG("%sZ:%f %sZ:%f")
980 << Sector::planeIdAsText(Sector::Floor ).upperFirstChar() << mob->floorZ
981 << Sector::planeIdAsText(Sector::Ceiling).upperFirstChar() << mob->ceilingZ;
982
983 if (Subsector *subsec = Mobj_SubsectorPtr(*mob))
984 {
985 LOG_MAP_MSG("Sector:%i (%sZ:%f %sZ:%f)")
986 << subsec->sector().indexInMap()
987 << Sector::planeIdAsText(Sector::Floor ) << subsec->sector().floor ().height()
988 << Sector::planeIdAsText(Sector::Ceiling) << subsec->sector().ceiling().height();
989 }
990
991 if (mob->onMobj)
992 {
993 LOG_MAP_MSG("onMobj:%i") << mob->onMobj->thinker.id;
994 }
995
996 return true;
997 }
998
Mobj_ConsoleRegister()999 void Mobj_ConsoleRegister()
1000 {
1001 C_CMD("inspectmobj", "i", InspectMobj);
1002
1003 #ifdef __CLIENT__
1004 C_VAR_BYTE("rend-mobj-light-auto", &mobjAutoLights, 0, 0, 1);
1005 #endif
1006 }
1007