render: added "r_sprite_lighting_type" variable to control sprite lighting with light...
[k8vavoom.git] / source / psim / p_thinker.cpp
blob4b2e9f77a735d35df68c80fb1a39672d10274975
1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
12 //**
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
16 //**
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
21 //**
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
24 //**
25 //**************************************************************************
26 //**
27 //** THINKERS
28 //**
29 //** All thinkers should be allocated by Z_Malloc so they can be operated
30 //** on uniformly. The actual structures will vary in size, but the first
31 //** element must be VThinker.
32 //**
33 //**************************************************************************
34 #include "../gamedefs.h"
35 #ifdef CLIENT
36 # include "../drawer.h"
37 #endif
38 #include "../net/network.h"
39 #include "../server/server.h"
40 #include "../client/cl_local.h" // for dlight_t
41 #include "../sound/sound.h"
42 #include "p_thinker.h"
43 #include "p_entity.h"
44 #include "p_levelinfo.h"
45 #include "p_world.h"
46 #include "p_player.h"
49 static VClass *eexCls = nullptr;
50 static VClass *actorCls = nullptr;
53 IMPLEMENT_CLASS(V, Thinker)
56 //==========================================================================
58 // VThinker::FindClassChecked
60 //==========================================================================
61 VClass *VThinker::FindClassChecked (const char *classname) {
62 if (!classname || !classname[0]) Sys_Error("cannot find nonamed class");
63 VClass *res = VClass::FindClass(classname);
64 if (!res) Sys_Error("cannot find required class `%s`", classname);
65 return res;
69 //==========================================================================
71 // VThinker::FindTypedField
73 //==========================================================================
74 VField *VThinker::FindTypedField (VClass *klass, const char *fldname, EType type, VClass *refclass) {
75 vassert(klass);
76 VField *fld = klass->FindField(fldname);
77 if (!fld) Sys_Error("required field `%s` not found in class `%s`", fldname, klass->GetName());
78 if (fld->Type.Type != type || (type == TYPE_Reference && fld->Type.Class != refclass)) {
79 Sys_Error("required field `%s` in class `%s` is of invalid type `%s`", fldname, klass->GetName(), *fld->Type.GetName());
81 return fld;
85 //==========================================================================
87 // VThinker::ThinkerStaticInit
89 //==========================================================================
90 void VThinker::ThinkerStaticInit () {
91 eexCls = FindClassChecked("EntityEx");
92 actorCls = FindClassChecked("Actor");
96 //==========================================================================
98 // VThinker::Destroy
100 //==========================================================================
101 void VThinker::Destroy () {
102 // close any thinker channels
103 if (XLevel) {
104 if (XLevel->NetContext) XLevel->NetContext->ThinkerDestroyed(this);
105 #ifdef CLIENT
106 if (XLevel->Renderer) XLevel->Renderer->ThinkerDestroyed(this);
107 #endif
109 Super::Destroy();
113 //==========================================================================
115 // VThinker::SerialiseOther
117 //==========================================================================
118 void VThinker::SerialiseOther (VStream &Strm) {
119 Super::SerialiseOther(Strm);
120 if (Strm.IsLoading()) XLevel->AddThinker(this);
124 //==========================================================================
126 // VThinker::Tick
128 //==========================================================================
129 void VThinker::Tick (float DeltaTime) {
130 if (DeltaTime <= 0.0f) return;
131 static VMethodProxy method("Tick");
132 vobjPutParamSelf(DeltaTime);
133 VMT_RET_VOID(method);
137 //==========================================================================
139 // VThinker::DestroyThinker
141 //==========================================================================
142 void VThinker::DestroyThinker () {
143 // remove from network layer here, because GC is not called on each frame
144 if (!IsGoingToDie() && XLevel) {
145 if (XLevel->NetContext) XLevel->NetContext->ThinkerDestroyed(this);
146 #ifdef CLIENT
147 if (XLevel->Renderer) XLevel->Renderer->ThinkerDestroyed(this);
148 #endif
150 SetDelayedDestroy();
154 //==========================================================================
156 // VThinker::AddedToLevel
158 //==========================================================================
159 void VThinker::AddedToLevel () {
160 #ifdef CLIENT
161 if (XLevel) {
162 if (XLevel->Renderer) XLevel->Renderer->ThinkerAdded(this);
164 #endif
168 //==========================================================================
170 // VThinker::RemovedFromLevel
172 //==========================================================================
173 void VThinker::RemovedFromLevel () {
174 if (XLevel) {
175 #ifdef CLIENT
176 if (XLevel->Renderer) XLevel->Renderer->ThinkerDestroyed(this);
177 #endif
178 if (XLevel->NetContext) XLevel->NetContext->ThinkerDestroyed(this);
183 //==========================================================================
185 // VThinker::StartSound
187 //==========================================================================
188 void VThinker::StartSound (const TVec &origin, vint32 origin_id,
189 vint32 sound_id, vint32 channel, float volume, float Attenuation,
190 bool Loop, float Pitch)
192 if (!Level || !Level->Game) return; //FIXME! for client-side entities (this should be fixed, client-side entities can emit sounds)
193 for (int i = 0; i < MAXPLAYERS; ++i) {
194 if (!Level->Game->Players[i]) continue;
195 if (!(Level->Game->Players[i]->PlayerFlags&VBasePlayer::PF_Spawned)) continue;
196 Level->Game->Players[i]->eventClientStartSound(sound_id, origin, origin_id, channel, volume, Attenuation, Loop, Pitch);
201 //==========================================================================
203 // VThinker::StopSound
205 // oid 0 means "do nothing" (there is another way to stop all local sounds)
206 // channel 0 means "all channels for this origin id"
208 //==========================================================================
209 void VThinker::StopSound (vint32 origin_id, vint32 channel) {
210 if (!Level || !Level->Game) return; //FIXME! for client-side entities (this should be fixed, client-side entities can emit sounds)
211 if (origin_id <= 0) return; // there is another way to stop all local sounds
212 for (int i = 0; i < MAXPLAYERS; ++i) {
213 if (!Level->Game->Players[i]) continue;
214 if (!(Level->Game->Players[i]->PlayerFlags&VBasePlayer::PF_Spawned)) continue;
215 Level->Game->Players[i]->eventClientStopSound(origin_id, channel);
220 //==========================================================================
222 // VThinker::StartSoundSequence
224 //==========================================================================
225 void VThinker::StartSoundSequence (const TVec &Origin, vint32 OriginId, VName Name, vint32 ModeNum) {
226 if (!Level || !Level->Game) return; //FIXME! for client-side entities (this should be fixed, client-side entities can emit sounds)
228 // remove any existing sequences of this origin
229 for (int i = 0; i < XLevel->ActiveSequences.length(); ++i) {
230 if (XLevel->ActiveSequences[i].OriginId == OriginId) {
231 XLevel->ActiveSequences.RemoveIndex(i);
232 --i;
236 VSndSeqInfo &Seq = XLevel->ActiveSequences.Alloc();
237 Seq.Name = Name;
238 Seq.OriginId = OriginId;
239 Seq.Origin = Origin;
240 Seq.ModeNum = ModeNum;
242 for (int i = 0; i < MAXPLAYERS; ++i) {
243 if (!Level->Game->Players[i]) continue;
244 if (!(Level->Game->Players[i]->PlayerFlags&VBasePlayer::PF_Spawned)) continue;
245 Level->Game->Players[i]->eventClientStartSequence(Origin, OriginId, Name, ModeNum);
250 //==========================================================================
252 // VThinker::AddSoundSequenceChoice
254 //==========================================================================
255 void VThinker::AddSoundSequenceChoice (int origin_id, VName Choice) {
256 if (!Level || !Level->Game) return; //FIXME! for client-side entities (this should be fixed, client-side entities can emit sounds)
258 // remove it from server's sequences list
259 for (int i = 0; i < XLevel->ActiveSequences.length(); ++i) {
260 if (XLevel->ActiveSequences[i].OriginId == origin_id) {
261 XLevel->ActiveSequences[i].Choices.Append(Choice);
265 for (int i = 0; i < MAXPLAYERS; ++i) {
266 if (!Level->Game->Players[i]) continue;
267 if (!(Level->Game->Players[i]->PlayerFlags&VBasePlayer::PF_Spawned)) continue;
268 Level->Game->Players[i]->eventClientAddSequenceChoice(origin_id, Choice);
273 //==========================================================================
275 // VThinker::StopSoundSequence
277 //==========================================================================
278 void VThinker::StopSoundSequence (int origin_id) {
279 if (!Level || !Level->Game) return; //FIXME! for client-side entities (this should be fixed, client-side entities can emit sounds)
281 // remove it from server's sequences list
282 for (int i = 0; i < XLevel->ActiveSequences.length(); ++i) {
283 if (XLevel->ActiveSequences[i].OriginId == origin_id) {
284 XLevel->ActiveSequences.RemoveIndex(i);
285 --i;
289 for (int i = 0; i < MAXPLAYERS; ++i) {
290 if (!Level->Game->Players[i]) continue;
291 if (!(Level->Game->Players[i]->PlayerFlags&VBasePlayer::PF_Spawned)) continue;
292 Level->Game->Players[i]->eventClientStopSequence(origin_id);
297 //==========================================================================
299 // VThinker::BroadcastPrint
301 //==========================================================================
302 void VThinker::BroadcastPrint (const char *s) {
303 if (!Level || !Level->Game) return; // for client-side entities
304 for (int i = 0; i < svs.max_clients; ++i) {
305 if (Level->Game->Players[i]) Level->Game->Players[i]->eventClientPrint(s);
310 //==========================================================================
312 // VThinker::BroadcastChatPrint
314 //==========================================================================
315 void VThinker::BroadcastChatPrint (VStr nick, VStr str) {
316 if (!Level || !Level->Game) return; // for client-side entities
317 for (int i = 0; i < svs.max_clients; ++i) {
318 if (Level->Game->Players[i]) Level->Game->Players[i]->eventClientChatPrint(nick, str);
323 //==========================================================================
325 // VThinker::BroadcastCenterPrint
327 //==========================================================================
328 void VThinker::BroadcastCenterPrint (const char *s) {
329 if (!Level) return; // for client-side entities
330 for (int i = 0; i < svs.max_clients; ++i) {
331 if (Level->Game->Players[i]) Level->Game->Players[i]->eventClientCenterPrint(s);
336 //==========================================================================
338 // Script iterators
340 //==========================================================================
341 class VScriptThinkerIterator : public VScriptIterator {
342 private:
343 VThinker *Self;
344 VClass *Class;
345 VThinker **Out;
346 VThinker *Current;
348 public:
349 inline VScriptThinkerIterator (VThinker *ASelf, VClass *AClass, VThinker **AOut) noexcept
350 : Self(ASelf)
351 , Class(AClass)
352 , Out(AOut)
353 , Current(nullptr)
355 //GCon->Logf(NAME_Debug, "VScriptThinkerIterator: ASelf=%p; AClass=%p", ASelf, AClass);
358 virtual bool GetNext () override {
359 if (!Current) {
360 Current = (Self ? Self->XLevel->ThinkerHead : nullptr);
361 } else {
362 Current = Current->Next;
364 *Out = nullptr;
365 while (Current) {
366 if (Current->IsA(Class) && !Current->IsGoingToDie()) {
367 *Out = Current;
368 break;
370 Current = Current->Next;
372 return !!*Out;
377 class VActivePlayersIterator : public VScriptIterator {
378 private:
379 VThinker *Self;
380 VBasePlayer **Out;
381 int Index;
383 public:
384 inline VActivePlayersIterator (VThinker *ASelf, VBasePlayer **AOut) noexcept
385 : Self(ASelf)
386 , Out(AOut)
387 , Index(0)
390 virtual bool GetNext () override {
391 while (Index < MAXPLAYERS) {
392 VBasePlayer *P = (Self ? Self->Level->Game->Players[Index] : nullptr);
393 ++Index;
394 if (P && (P->PlayerFlags&VBasePlayer::PF_Spawned)) {
395 *Out = P;
396 return true;
399 return false;
404 //==========================================================================
406 // Script natives
408 //==========================================================================
409 VThinker *VThinker::SpawnCommon (bool allowNoneClass, bool checkKillEntityEx, bool hasDesiredClass) {
410 VClass *desiredClass = nullptr;
411 VClass *Class;
412 VOptParamVec AOrigin(TVec(0, 0, 0));
413 VOptParamAVec AAngles(TAVec(0, 0, 0));
414 VOptParamPtr<mthing_t> mthing;
415 VOptParamBool AllowReplace(true);
416 vobjDeclareSelf;
418 if (hasDesiredClass) {
419 vobjGetParam(Self, desiredClass, Class, AOrigin, AAngles, mthing, AllowReplace);
420 } else {
421 vobjGetParam(Self, Class, AOrigin, AAngles, mthing, AllowReplace);
424 if (!Self) { VObject::VMDumpCallStack(); Sys_Error("empty self in `Thinker::SpawnXXX()`"); }
425 VEntity *SelfEnt = Cast<VEntity>(Self);
426 // if spawner is entity, default to it's origin and yaw
427 if (SelfEnt) {
428 if (!AOrigin.specified) AOrigin = SelfEnt->Origin;
429 if (!AAngles.specified) { /*AAngles = SelfEnt->Angles;*/ AAngles.value.yaw = SelfEnt->Angles.yaw; }
431 if (!Class) {
432 if (!allowNoneClass) { VObject::VMDumpCallStack(); Sys_Error("Trying to spawn `None` class"); }
433 return nullptr;
435 if (!Self->XLevel) { VObject::VMDumpCallStack(); Sys_Error("empty XLevel self in `Thinker::SpawnXXX()`"); }
437 //if (checkKillEntityEx && !Class->IsChildOf(eexCls)) { VObject::VMDumpCallStack(); Sys_Error("trying to spawn non-EntityEx class `%s`", Class->GetName()); }
439 VThinker *th = Self->XLevel->SpawnThinker(Class, AOrigin, AAngles, mthing, AllowReplace);
441 // check it
442 if (th && checkKillEntityEx && !th->IsA(eexCls)) {
443 GCon->Logf(NAME_Warning, "%s: tried to spawn class `%s`, got class `%s`, which is not `EntityEx` (this is mostly harmless)", Self->GetClass()->GetName(), Class->GetName(), th->GetClass()->GetName());
444 th->DestroyThinker();
445 th = nullptr;
448 if (th && desiredClass && !th->IsA(desiredClass)) {
449 GCon->Logf(NAME_Warning, "%s: tried to spawn class `%s`, got class `%s`, which is not `%s`", Self->GetClass()->GetName(), Class->GetName(), th->GetClass()->GetName(), desiredClass->GetName());
450 th->DestroyThinker();
451 th = nullptr;
454 return th;
458 IMPLEMENT_FUNCTION(VThinker, SpawnThinker) {
459 VThinker *th = SpawnCommon(/*allowNoneClass*/false, /*checkKillEntityEx*/false, /*hasDesiredClass*/false);
460 RET_REF(th);
463 IMPLEMENT_FUNCTION(VThinker, SpawnNoTypeCheck) {
464 VThinker *th = SpawnCommon(/*allowNoneClass*/true, /*checkKillEntityEx*/false, /*hasDesiredClass*/false);
465 RET_REF(th);
468 IMPLEMENT_FUNCTION(VThinker, Spawn) {
469 VThinker *th = SpawnCommon(/*allowNoneClass*/true, /*checkKillEntityEx*/true, /*hasDesiredClass*/false);
470 RET_REF(th);
473 IMPLEMENT_FUNCTION(VThinker, SpawnEntityChecked) {
474 VThinker *th = SpawnCommon(/*allowNoneClass*/true, /*checkKillEntityEx*/true, /*hasDesiredClass*/true);
475 RET_REF(th);
478 IMPLEMENT_FUNCTION(VThinker, Destroy) {
479 vobjGetParamSelf();
480 Self->DestroyThinker();
483 IMPLEMENT_FUNCTION(VThinker, bprint) {
484 VStr Msg = PF_FormatString();
485 vobjGetParamSelf();
486 Self->BroadcastPrint(*Msg);
489 // native final dlight_t *AllocDlight(Thinker Owner, TVec origin, /*optional*/ float radius, optional int lightid);
490 IMPLEMENT_FUNCTION(VThinker, AllocDlight) {
491 VThinker *Owner;
492 TVec lorg;
493 float radius;
494 VOptParamInt lightid(-1);
495 vobjGetParamSelf(Owner, lorg, radius, lightid);
496 if (radius < 0) radius = 0;
497 #ifdef CLIENT
498 if (!Self->XLevel || !Self->XLevel->Renderer) { RET_PTR(nullptr); return; } // for dedicated server
499 RET_PTR(Self->XLevel->Renderer->AllocDlight(Owner, lorg, radius, lightid));
500 #else
501 RET_PTR(nullptr);
502 #endif
505 //native final bool ShiftDlightHeight (int lightid, float zdelta);
506 IMPLEMENT_FUNCTION(VThinker, ShiftDlightHeight) {
507 int lightid;
508 float zdelta;
509 vobjGetParamSelf(lightid, zdelta);
510 if (!Self) { VObject::VMDumpCallStack(); Sys_Error("null self in VThinker::ShiftDlightOrigin"); }
511 #ifdef CLIENT
512 if (!Self->XLevel || !Self->XLevel->Renderer) { RET_BOOL(false); return; }
513 dlight_t *dl = Self->XLevel->Renderer->FindDlightById(lightid);
514 if (dl) {
515 //GCon->Logf("fixing dlight with id %d, delta=%g", lightid, zdelta);
516 dl->origin.z += zdelta;
517 RET_BOOL(true);
518 } else {
519 RET_BOOL(false);
521 #else
522 RET_BOOL(false);
523 #endif
526 IMPLEMENT_FUNCTION(VThinker, NewParticle) {
527 TVec porg;
528 vobjGetParamSelf(porg);
529 #ifdef CLIENT
530 if (GGameInfo->IsPaused() || !Self->XLevel || !Self->XLevel->Renderer) {
531 RET_PTR(nullptr);
532 } else {
533 RET_PTR(Self->XLevel->Renderer->NewParticle(porg));
535 #else
536 RET_PTR(nullptr);
537 #endif
540 IMPLEMENT_FUNCTION(VThinker, GetAmbientSound) {
541 int Idx;
542 vobjGetParam(Idx);
543 RET_PTR(GSoundManager->GetAmbientSound(Idx));
546 IMPLEMENT_FUNCTION(VThinker, AllThinkers) {
547 VClass *Class;
548 VThinker **Thinker;
549 vobjGetParamSelf(Class, Thinker);
550 RET_PTR(new VScriptThinkerIterator(Self, Class, Thinker));
554 // ////////////////////////////////////////////////////////////////////////// //
555 class VScriptMonsterLevelIterator : public VScriptIterator {
556 private:
557 VEntity *Self;
558 VClass *Class;
559 VThinker **Out;
560 VThinker *Current;
561 unsigned Flags;
562 float Radius;
564 enum {
565 FlagNone = 0u,
566 FlagOnlyVisible = 1u<<0,
567 FlagAllowPlayers = 1u<<1,
568 FlagRadiusCheck = 1u<<2,
569 FlagAllowSelf = 1u<<3,
572 public:
573 VScriptMonsterLevelIterator (VThinker *ASelf, VClass *AClass, VThinker **AOut,
574 bool AOnlyVisible, bool AAllowPlayers,
575 bool AAllowSelf, float ARadius)
576 : Self(nullptr)
577 , Class(AClass)
578 , Out(AOut)
579 , Current(nullptr)
580 , Radius(ARadius*ARadius)
582 if (!AClass->IsChildOf(actorCls)) Class = nullptr;
583 else if (!ASelf->IsA(VThinker::StaticClass())) Class = nullptr;
584 else Self = (VEntity *)ASelf;
585 Flags =
586 (AOnlyVisible ? FlagOnlyVisible : FlagNone)|
587 (AAllowPlayers ? FlagAllowPlayers : FlagNone)|
588 (AAllowSelf ? FlagAllowSelf : FlagNone)|
589 (ARadius == ARadius && ARadius > 0.0f ? FlagRadiusCheck : FlagNone)|
590 FlagNone;
593 virtual bool GetNext () override {
594 Current = (Current ? Current->Next : Class ? Self->XLevel->ThinkerHead : nullptr);
595 *Out = nullptr;
596 for (; Current; Current = Current->Next) {
597 if (Current->IsGoingToDie()) continue;
598 if (!Current->IsA(Class)) continue;
599 VEntity *e = (VEntity *)Current;
600 if (e->Health <= 0) continue;
601 if (!e->IsMonster()) {
602 if (!(Flags&FlagAllowPlayers) || !e->IsPlayer()) continue;
604 if (!(Flags&FlagAllowSelf) && e == Self) continue;
605 if ((Flags&FlagRadiusCheck) && (e->Origin-Self->Origin).lengthSquared() > Radius) continue;
606 if ((Flags&FlagOnlyVisible) && !e->CanSee(Self)) continue;
607 *Out = Current;
608 break;
610 return !!*Out;
616 native final iterator AllMonsters (class!Actor Class, out Actor Thinker,
617 optional bool onlyVisible, optional bool allowPlayers,
618 optional bool allowSelf, optional float radius);
620 IMPLEMENT_FUNCTION(VThinker, AllMonsters) {
621 VClass *Class;
622 VThinker **Thinker;
623 VOptParamBool onlyVisible(false);
624 VOptParamBool allowPlayers(false);
625 VOptParamBool allowSelf(false);
626 VOptParamFloat radius(0.0f);
627 vobjGetParamSelf(Class, Thinker, onlyVisible, allowPlayers, allowSelf, radius);
628 RET_PTR(new VScriptMonsterLevelIterator(Self, Class, Thinker, onlyVisible, allowPlayers, allowSelf, radius));
631 IMPLEMENT_FUNCTION(VThinker, AllActivePlayers) {
632 VBasePlayer **Out;
633 vobjGetParamSelf(Out);
634 RET_PTR(new VActivePlayersIterator(Self, Out));
637 // native final iterator PathTraverse (out intercept_t In, TVec p0, TVec p1, int flags, optional int planeflags, optional int lineflags);
638 IMPLEMENT_FUNCTION(VThinker, PathTraverse) {
639 intercept_t *In;
640 TVec p0, p1;
641 int flags;
642 VOptParamInt planeflags(SPF_NOBLOCKING|SPF_NOBLOCKSHOOT);
643 VOptParamInt lineflags(ML_BLOCKEVERYTHING|ML_BLOCKHITSCAN);
644 vobjGetParamSelf(In, p0, p1, flags, planeflags, lineflags);
645 RET_PTR(new VPathTraverse(Self, In, p0, p1, flags, (vuint32)planeflags, (vuint32)lineflags));
648 // native final iterator RadiusThings (out Entity Ent, TVec Org, float Radius);
649 IMPLEMENT_FUNCTION(VThinker, RadiusThings) {
650 VEntity **EntPtr;
651 TVec Org;
652 float Radius;
653 vobjGetParamSelf(EntPtr, Org, Radius);
654 RET_PTR(new VRadiusThingsIterator(Self, EntPtr, Org, Radius));
658 //==========================================================================
660 // Info_ThinkerCount
662 //==========================================================================
663 COMMAND(Info_ThinkerCount) {
664 VBasePlayer *plr = GGameInfo->Players[0];
665 if (!plr || !plr->Level || !plr->Level->XLevel) return;
666 int count = 0;
667 for (VThinker *th = plr->Level->XLevel->ThinkerHead; th; th = th->Next) {
668 ++count;
670 GCon->Logf("%d thinkers on level", count);
674 //==========================================================================
676 // classNameCompare
678 //==========================================================================
679 static int classNameCompare (const void *aa, const void *bb, void * /*udata*/) {
680 if (aa == bb) return 0;
681 VClass *a = *(VClass **)aa;
682 VClass *b = *(VClass **)bb;
683 return VStr::ICmp(a->GetName(), b->GetName());
687 struct ThinkerListEntry {
688 VClass *cls;
689 int count;
693 //==========================================================================
695 // classTLECompare
697 //==========================================================================
698 extern "C" {
699 static int classTLECompare (const void *aa, const void *bb, void * /*udata*/) {
700 if (aa == bb) return 0;
701 const ThinkerListEntry *a = (const ThinkerListEntry *)aa;
702 const ThinkerListEntry *b = (const ThinkerListEntry *)bb;
703 return (a->count-b->count);
708 //==========================================================================
710 // Info_ThinkerCountDetail
712 //==========================================================================
713 COMMAND(Info_ThinkerCountDetail) {
714 VBasePlayer *plr = GGameInfo->Players[0];
715 if (!plr || !plr->Level || !plr->Level->XLevel) return;
716 // collect
717 TMapNC<VClass *, int> thmap;
718 int count = 0;
719 int maxlen = 1;
720 for (VThinker *th = plr->Level->XLevel->ThinkerHead; th; th = th->Next) {
721 int nlen = VStr::length(th->GetClass()->GetName());
722 if (maxlen < nlen) maxlen = nlen;
723 VClass *tc = th->GetClass();
724 auto tcp = thmap.get(tc);
725 if (tcp) {
726 ++(*tcp);
727 } else {
728 thmap.put(tc, 1);
730 ++count;
732 GCon->Logf("\034K=== %d thinkers on level ===", count);
733 // sort
734 if (Args.length() > 1 && Args[1].length() && Args[1][0] == 't') {
735 TArray<ThinkerListEntry> list;
736 for (auto it = thmap.first(); it; ++it) {
737 ThinkerListEntry &e = list.alloc();
738 e.cls = it.getKey();
739 e.count = it.getValue();
741 smsort_r(list.ptr(), list.length(), sizeof(ThinkerListEntry), &classTLECompare, nullptr);
742 // dump
743 for (int f = 0; f < list.length(); ++f) {
744 GCon->Logf("\034K%*s\034-: \034D%d", maxlen, list[f].cls->GetName(), list[f].count);
746 } else {
747 TArray<VClass *> list;
748 for (auto it = thmap.first(); it; ++it) list.append(it.getKey());
749 smsort_r(list.ptr(), list.length(), sizeof(VClass *), &classNameCompare, nullptr);
750 // dump
751 for (int f = 0; f < list.length(); ++f) {
752 auto tcp = thmap.get(list[f]);
753 GCon->Logf("\034K%*s\034-: \034D%d", maxlen, list[f]->GetName(), *tcp);