1 //**************************************************************************
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
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.
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.
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/>.
25 //**************************************************************************
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.
33 //**************************************************************************
34 #include "../gamedefs.h"
36 # include "../drawer.h"
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"
44 #include "p_levelinfo.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
);
69 //==========================================================================
71 // VThinker::FindTypedField
73 //==========================================================================
74 VField
*VThinker::FindTypedField (VClass
*klass
, const char *fldname
, EType type
, VClass
*refclass
) {
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());
85 //==========================================================================
87 // VThinker::ThinkerStaticInit
89 //==========================================================================
90 void VThinker::ThinkerStaticInit () {
91 eexCls
= FindClassChecked("EntityEx");
92 actorCls
= FindClassChecked("Actor");
96 //==========================================================================
100 //==========================================================================
101 void VThinker::Destroy () {
102 // close any thinker channels
104 if (XLevel
->NetContext
) XLevel
->NetContext
->ThinkerDestroyed(this);
106 if (XLevel
->Renderer
) XLevel
->Renderer
->ThinkerDestroyed(this);
113 //==========================================================================
115 // VThinker::SerialiseOther
117 //==========================================================================
118 void VThinker::SerialiseOther (VStream
&Strm
) {
119 Super::SerialiseOther(Strm
);
120 if (Strm
.IsLoading()) XLevel
->AddThinker(this);
124 //==========================================================================
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);
147 if (XLevel
->Renderer
) XLevel
->Renderer
->ThinkerDestroyed(this);
154 //==========================================================================
156 // VThinker::AddedToLevel
158 //==========================================================================
159 void VThinker::AddedToLevel () {
162 if (XLevel
->Renderer
) XLevel
->Renderer
->ThinkerAdded(this);
168 //==========================================================================
170 // VThinker::RemovedFromLevel
172 //==========================================================================
173 void VThinker::RemovedFromLevel () {
176 if (XLevel
->Renderer
) XLevel
->Renderer
->ThinkerDestroyed(this);
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
);
236 VSndSeqInfo
&Seq
= XLevel
->ActiveSequences
.Alloc();
238 Seq
.OriginId
= OriginId
;
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
);
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 //==========================================================================
340 //==========================================================================
341 class VScriptThinkerIterator
: public VScriptIterator
{
349 inline VScriptThinkerIterator (VThinker
*ASelf
, VClass
*AClass
, VThinker
**AOut
) noexcept
355 //GCon->Logf(NAME_Debug, "VScriptThinkerIterator: ASelf=%p; AClass=%p", ASelf, AClass);
358 virtual bool GetNext () override
{
360 Current
= (Self
? Self
->XLevel
->ThinkerHead
: nullptr);
362 Current
= Current
->Next
;
366 if (Current
->IsA(Class
) && !Current
->IsGoingToDie()) {
370 Current
= Current
->Next
;
377 class VActivePlayersIterator
: public VScriptIterator
{
384 inline VActivePlayersIterator (VThinker
*ASelf
, VBasePlayer
**AOut
) noexcept
390 virtual bool GetNext () override
{
391 while (Index
< MAXPLAYERS
) {
392 VBasePlayer
*P
= (Self
? Self
->Level
->Game
->Players
[Index
] : nullptr);
394 if (P
&& (P
->PlayerFlags
&VBasePlayer::PF_Spawned
)) {
404 //==========================================================================
408 //==========================================================================
409 VThinker
*VThinker::SpawnCommon (bool allowNoneClass
, bool checkKillEntityEx
, bool hasDesiredClass
) {
410 VClass
*desiredClass
= nullptr;
412 VOptParamVec
AOrigin(TVec(0, 0, 0));
413 VOptParamAVec
AAngles(TAVec(0, 0, 0));
414 VOptParamPtr
<mthing_t
> mthing
;
415 VOptParamBool
AllowReplace(true);
418 if (hasDesiredClass
) {
419 vobjGetParam(Self
, desiredClass
, Class
, AOrigin
, AAngles
, mthing
, AllowReplace
);
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
428 if (!AOrigin
.specified
) AOrigin
= SelfEnt
->Origin
;
429 if (!AAngles
.specified
) { /*AAngles = SelfEnt->Angles;*/ AAngles
.value
.yaw
= SelfEnt
->Angles
.yaw
; }
432 if (!allowNoneClass
) { VObject::VMDumpCallStack(); Sys_Error("Trying to spawn `None` class"); }
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
);
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();
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();
458 IMPLEMENT_FUNCTION(VThinker
, SpawnThinker
) {
459 VThinker
*th
= SpawnCommon(/*allowNoneClass*/false, /*checkKillEntityEx*/false, /*hasDesiredClass*/false);
463 IMPLEMENT_FUNCTION(VThinker
, SpawnNoTypeCheck
) {
464 VThinker
*th
= SpawnCommon(/*allowNoneClass*/true, /*checkKillEntityEx*/false, /*hasDesiredClass*/false);
468 IMPLEMENT_FUNCTION(VThinker
, Spawn
) {
469 VThinker
*th
= SpawnCommon(/*allowNoneClass*/true, /*checkKillEntityEx*/true, /*hasDesiredClass*/false);
473 IMPLEMENT_FUNCTION(VThinker
, SpawnEntityChecked
) {
474 VThinker
*th
= SpawnCommon(/*allowNoneClass*/true, /*checkKillEntityEx*/true, /*hasDesiredClass*/true);
478 IMPLEMENT_FUNCTION(VThinker
, Destroy
) {
480 Self
->DestroyThinker();
483 IMPLEMENT_FUNCTION(VThinker
, bprint
) {
484 VStr Msg
= PF_FormatString();
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
) {
494 VOptParamInt
lightid(-1);
495 vobjGetParamSelf(Owner
, lorg
, radius
, lightid
);
496 if (radius
< 0) radius
= 0;
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
));
505 //native final bool ShiftDlightHeight (int lightid, float zdelta);
506 IMPLEMENT_FUNCTION(VThinker
, ShiftDlightHeight
) {
509 vobjGetParamSelf(lightid
, zdelta
);
510 if (!Self
) { VObject::VMDumpCallStack(); Sys_Error("null self in VThinker::ShiftDlightOrigin"); }
512 if (!Self
->XLevel
|| !Self
->XLevel
->Renderer
) { RET_BOOL(false); return; }
513 dlight_t
*dl
= Self
->XLevel
->Renderer
->FindDlightById(lightid
);
515 //GCon->Logf("fixing dlight with id %d, delta=%g", lightid, zdelta);
516 dl
->origin
.z
+= zdelta
;
526 IMPLEMENT_FUNCTION(VThinker
, NewParticle
) {
528 vobjGetParamSelf(porg
);
530 if (GGameInfo
->IsPaused() || !Self
->XLevel
|| !Self
->XLevel
->Renderer
) {
533 RET_PTR(Self
->XLevel
->Renderer
->NewParticle(porg
));
540 IMPLEMENT_FUNCTION(VThinker
, GetAmbientSound
) {
543 RET_PTR(GSoundManager
->GetAmbientSound(Idx
));
546 IMPLEMENT_FUNCTION(VThinker
, AllThinkers
) {
549 vobjGetParamSelf(Class
, Thinker
);
550 RET_PTR(new VScriptThinkerIterator(Self
, Class
, Thinker
));
554 // ////////////////////////////////////////////////////////////////////////// //
555 class VScriptMonsterLevelIterator
: public VScriptIterator
{
566 FlagOnlyVisible
= 1u<<0,
567 FlagAllowPlayers
= 1u<<1,
568 FlagRadiusCheck
= 1u<<2,
569 FlagAllowSelf
= 1u<<3,
573 VScriptMonsterLevelIterator (VThinker
*ASelf
, VClass
*AClass
, VThinker
**AOut
,
574 bool AOnlyVisible
, bool AAllowPlayers
,
575 bool AAllowSelf
, float ARadius
)
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
;
586 (AOnlyVisible
? FlagOnlyVisible
: FlagNone
)|
587 (AAllowPlayers
? FlagAllowPlayers
: FlagNone
)|
588 (AAllowSelf
? FlagAllowSelf
: FlagNone
)|
589 (ARadius
== ARadius
&& ARadius
> 0.0f
? FlagRadiusCheck
: FlagNone
)|
593 virtual bool GetNext () override
{
594 Current
= (Current
? Current
->Next
: Class
? Self
->XLevel
->ThinkerHead
: 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;
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
) {
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
) {
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
) {
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
) {
653 vobjGetParamSelf(EntPtr
, Org
, Radius
);
654 RET_PTR(new VRadiusThingsIterator(Self
, EntPtr
, Org
, Radius
));
658 //==========================================================================
662 //==========================================================================
663 COMMAND(Info_ThinkerCount
) {
664 VBasePlayer
*plr
= GGameInfo
->Players
[0];
665 if (!plr
|| !plr
->Level
|| !plr
->Level
->XLevel
) return;
667 for (VThinker
*th
= plr
->Level
->XLevel
->ThinkerHead
; th
; th
= th
->Next
) {
670 GCon
->Logf("%d thinkers on level", count
);
674 //==========================================================================
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
{
693 //==========================================================================
697 //==========================================================================
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;
717 TMapNC
<VClass
*, int> thmap
;
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
);
732 GCon
->Logf("\034K=== %d thinkers on level ===", count
);
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();
739 e
.count
= it
.getValue();
741 smsort_r(list
.ptr(), list
.length(), sizeof(ThinkerListEntry
), &classTLECompare
, nullptr);
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
);
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);
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
);