-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathhelp_queue.c
More file actions
5107 lines (4738 loc) · 158 KB
/
help_queue.c
File metadata and controls
5107 lines (4738 loc) · 158 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* CHelpQueue - FIFO of counselor/GM help requests posted by players.
*
* Each request carries the speaker, their location, and the text of
* the page; entries are popped in order as GMs respond. The binary
* used std::list here; we keep the same semantics with a singly-
* linked list.
*/
#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "container.h"
#include "dynamic.h"
#include "egg.h"
#include "gm_names.h"
#include "help_queue.h"
#include "main.h"
#include "multi.h"
#include "npc.h"
#include "packet_handler.h"
#include "packet_manager.h"
#include "player.h"
#include "resource_regrowth.h"
#include "skill.h"
#include "taglist.h"
#include "template.h"
#include "time.h"
#include "vtable.h"
#include "weapon.h"
#include "wombat_compile.h"
#include "world.h"
/*
* Help request entry (0x28 bytes on 32-bit) stored in the
* std::list<CHelpEntry> wrapped by CHelpQueue.
*/
typedef struct CHelpEntry {
uint32_t serial;
uint8_t type;
uint8_t priority;
uint8_t _pad06[2];
CString name;
CString message;
} CHelpEntry;
/*
* Counselor/GM assistance record (0x38 bytes on 32-bit) submitted via the
* help-request packet handler.
*/
typedef struct CAssistance {
uint32_t serial;
CString name;
uint8_t type;
uint8_t level;
uint8_t _pad[2];
CString subject;
CString body;
} CAssistance;
/*
* Assistance dispatch node (0x34 bytes on 32-bit) paired with CAssistance
* for the request-type C/D record variants.
*/
typedef struct CAssistanceNode {
uint32_t id1;
uint32_t id2;
uint16_t field;
uint8_t _pad0A[2];
CString str1;
uint8_t typeFlag;
uint8_t _pad1[3];
CString str2;
uint16_t field1;
uint16_t field2;
} CAssistanceNode;
static void GM_TargetRename(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetHue(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetMulti(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetSay(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetWalkDest(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetWalkNPC(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetAttackTarget(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetAttackNPC(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static int CounselorDeleteChar(CItem *self, CPlayer *entity); // 0x0044DEE6
static void GM_CreateReagentBag(CPlayer *player, CItem *container);
static void GM_CreateRuneBag(CPlayer *player, CItem *container);
static void *StdHelpList_Init(StdPtrList *list, void *src); // 0x0044F4C0
static void StdHelpList_PushBack(StdPtrList *list, void *value); // 0x0044F500
static void StdHelpList_DoInsert(StdPtrList *list, StdPtrNode **result, StdPtrNode *pos, void *value); // 0x0044F5F0
static StdPtrNode *StdHelpList_Buynode(StdPtrList *list, StdPtrNode *nextHint, StdPtrNode *prevHint); // 0x0044F6A0
static void StdHelpList_Destroy(void *list, void *element); // 0x0044F730
static void StdHelpList_ConstructorWrapper(void *list, void *dst, void *src); // 0x0044F770
static void *HelpEntry_DestructorWrapper(void *entry); // 0x0044F790
static void *HelpEntry_Constructor(void *dst, void *src); // 0x0044F7A0
static CHelpEntry *CHelpEntry_ScalarDelete(CHelpEntry *self, int flags); // 0x0044F7E0
static CHelpEntry *CHelpEntry_CopyConstructor(CHelpEntry *self, CHelpEntry *src); // 0x0044F810
static CHelpEntry *CHelpEntry_Constructor(CHelpEntry *self, uint32_t serial, uint8_t type, uint8_t priority, CString *name, CString *message); // 0x0044F8A0
static void CHelpEntry_Destructor(CHelpEntry *self); // 0x0044F920
static void CAssistance_Destructor(CAssistance *this); // 0x0045F1E0
static void CAssistance_NodeDestructor(CAssistanceNode *this); // 0x0045F240
static CAssistance *CAssistance_Constructor(CAssistance *self); // 0x0045F6C0
static uint8_t CAssistance_LoadRecordD(CAssistance *this, uint8_t *buf, int unused); // 0x0045F740
static uint8_t CAssistance_LoadRecordA(CAssistance *this, uint8_t *buf, int unused, uint32_t *serialOut); // 0x0045F8A0
static uint8_t *CAssistance_SaveRecordB(CSkillUseCtx *this); // 0x0045FA20
static int CAssistance_GetSerializedSizeB(CSkillUseCtx *this); // 0x0045FBA0
static uint8_t CAssistance_LoadRecordB(CSkillUseCtx *this, uint8_t *buf, int unused); // 0x0045FBB0
static CAssistanceNode *CAssistanceNode_Constructor(CAssistanceNode *self); // 0x0045FD20
static uint8_t CAssistance_LoadRecordC(CAssistanceNode *this, uint8_t *buf, int unused); // 0x0045FDB0
static void CAssistance_QueueDestructorWrapper(CAssistance *this); // 0x00469530
static uint8_t *CAssistanceQueue_Submit(CAssistance *this, uint8_t requestType); // 0x0049DBD0
static int CAssistanceQueue_GetSerializedSize(CAssistance *this); // 0x0049DD90
static void CHelpQueue_RemoveNode(CHelpQueue *q, CHelpRequestNode *target);
static void GM_ParseSetArgs(const char *arg, int *type, int *skillId, int *value, int *hasValue, char *strArg, size_t strArgSize);
static void GM_ApplySet(CItem *target, int type, int skillId, int value, int hasValue, const char *strArg, char *outMsg, int outMsgSize);
static void GM_TargetSet(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetKill(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetRemove(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetTele(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetMTele(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetMKill(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetMRemove(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetInfo(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetScripts(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetResources(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetAIState(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetFreeze(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetUnfreeze(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetMute(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetUnmute(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetInvis(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetVis(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetInvuln(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetVuln(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetResurrect(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetFillSpellbook(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetBank(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetSpawnNPC(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetLock(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetUnlock(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetItemHP(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetDurtest(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetNpcInv(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetLockItem(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetAttach(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
static void GM_TargetSetVar(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z);
/*
* Custom - g_GoLocations
*
* Named Felucca destinations recognised by the .go GM command.
*/
static const struct {
const char *name;
int16_t x, y, z;
} g_GoLocations[] = {
// Towns
{ "britain", 1475, 1645, 20 },
{ "bucs", 2736, 2166, 0 },
{ "cove", 2285, 1209, 0 },
{ "jhelom", 1414, 3816, 0 },
{ "magincia", 3730, 2161, 20 },
{ "minoc", 2526, 583, 0 },
{ "moonglow", 4442, 1122, 5 },
{ "nujelm", 3668, 1116, 0 },
{ "ocllo", 3650, 2516, 0 },
{ "serpent", 3023, 3417, 15 },
{ "skara", 601, 2171, 0 },
{ "trinsic", 1927, 2779, 0 },
{ "vesper", 2882, 788, 0 },
{ "wind", 1362, 896, 0 },
{ "yew", 535, 992, 0 },
{ "delucia", 5228, 3978, 37 },
{ "papua", 5730, 3208, -4 },
// Notable sub-locations
{ "british-castle", 1323, 1624, 55 },
{ "blackthorn", 1533, 1415, 56 },
{ "jhelom-pit", 1398, 3742, -21 },
{ "bucs-tunnels", 2667, 2069, -20 },
{ "cove-orcfort", 2171, 1332, 0 },
{ "yew-orcfort", 633, 1499, 0 },
{ "yew-prison", 354, 836, 20 },
{ "greenacres", 5445, 1153, 0 },
// Shrines
{ "chaos", 1456, 854, 0 },
{ "compassion", 1856, 872, -1 },
{ "honesty", 4217, 564, 36 },
{ "honor", 1730, 3528, 3 },
{ "humility", 4276, 3699, 0 },
{ "justice", 1301, 639, 16 },
{ "sacrifice", 3355, 299, 9 },
{ "spirituality", 1589, 2485, 5 },
{ "valor", 2496, 3932, 0 },
// Dungeon entrances
{ "covetous", 2499, 919, 0 },
{ "deceit", 4111, 432, 5 },
{ "despise", 1298, 1080, 0 },
{ "destard", 1176, 2637, 0 },
{ "hythloth", 4721, 3822, 0 },
{ "shame", 514, 1561, 0 },
{ "wrong", 2043, 238, 10 },
{ "terathan", 5451, 3143, -60 },
{ "fire", 5760, 2908, 15 },
{ "fire-brit", 2923, 3407, 8 },
{ "ice", 5210, 2322, 30 },
{ "ice-brit", 1999, 81, 4 },
{ "orccave", 1019, 1431, 0 },
{ "khaldun", 6009, 3775, 19 },
{ "solen", 729, 1451, 0 },
// Covetous levels
{ "covetous1", 5456, 1863, 0 },
{ "covetous2", 5614, 1997, 0 },
{ "covetous3", 5579, 1924, 0 },
{ "covetous-lake", 5467, 1805, 7 },
{ "covetous-torture", 5552, 1807, 0 },
// Deceit levels
{ "deceit1", 5188, 638, 0 },
{ "deceit2", 5305, 533, 2 },
{ "deceit3", 5137, 650, 5 },
{ "deceit4", 5306, 652, 2 },
// Despise levels
{ "despise1", 5501, 570, 59 },
{ "despise2", 5519, 673, 20 },
{ "despise3", 5407, 859, 45 },
// Destard levels
{ "destard1", 5243, 1006, 0 },
{ "destard2", 5143, 801, 4 },
{ "destard3", 5137, 986, 5 },
// Hythloth levels
{ "hythloth1", 5905, 20, 46 },
{ "hythloth2", 5976, 169, 0 },
{ "hythloth3", 6083, 145, -20 },
{ "hythloth4", 6059, 89, 24 },
// Shame levels
{ "shame1", 5395, 126, 0 },
{ "shame2", 5515, 11, 5 },
{ "shame3", 5514, 148, 25 },
{ "shame4", 5875, 20, -5 },
// Wrong levels
{ "wrong1", 5825, 630, 0 },
{ "wrong2", 5690, 569, 25 },
{ "wrong3", 5703, 639, 0 },
// Terathan Keep levels
{ "terathan1", 5342, 1601, 0 },
{ "terathan-champ", 5205, 1585, 0 },
{ "terathan-star", 5139, 1767, 0 },
// Fire levels
{ "fire1", 5790, 1416, 40 },
{ "fire2", 5702, 1316, 1 },
// Ice levels
{ "ice1", 5875, 150, 15 },
{ "ice-ratman", 5834, 327, 18 },
{ "ice-demon", 5700, 305, 0 },
// Orc Cave levels
{ "orccave1", 5137, 2014, 0 },
{ "orccave2", 5332, 1376, 0 },
{ "orccave3", 5272, 2036, 0 },
{ NULL, 0, 0, 0 },
};
/*
* Custom target callbacks for GM commands.
* TargetCB signature: (CPlayer *, uint8_t type, uint32_t serial,
* uint16_t x, uint16_t y, uint16_t z)
*/
// Custom - template id pending for the next .spawn target
static int g_PendingSpawnTemplate = -1;
// Custom - fame for the next .spawn target
static int16_t g_PendingSpawnFame = 0;
// Custom - karma for the next .spawn target
static int16_t g_PendingSpawnKarma = 0;
// Custom - new name pending for the next .rename target
static char g_PendingRenameBuf[30];
// Custom - hue pending for the next .hue target
static int g_PendingHue = -1;
// Custom - multi type id pending for the next .multi target
static int g_PendingMultiType = -1;
// Custom - text pending for the next .say target
static char g_PendingSayBuf[256];
// Custom - NPC serial pending for the next .walknpc target
static uint32_t g_PendingWalkNPC = 0;
// Custom - NPC serial pending for the next .attacknpc target
static uint32_t g_PendingAttackNPC = 0;
// Custom - HP value pending for the next .itemhp target (-1 = query only)
static int g_PendingItemHP = -1;
// Custom - script name pending for the next .attach target
static char g_PendingAttachScript[64];
// Custom - objvar name/value pending for the next .setvar target
static struct {
char name[64];
int value;
} g_PendingSetVar;
// Custom - aiState value pending for the next .aistate target
static int g_PendingAIState;
/*
* Target kinds for GM_TargetSet (.set <target> <value>).
*/
enum {
SET_STR,
SET_DEX,
SET_INT,
SET_HITS,
SET_MANA,
SET_STAM,
SET_SKILL,
SET_ALL_SKILLS,
SET_FAME,
SET_KARMA,
SET_MAXHP,
SET_MAXMANA,
SET_MAXSTAM,
SET_STRMOD,
SET_DEXMOD,
SET_INTMOD,
SET_NOTORIETY,
SET_HUNGER,
SET_STOMACH,
SET_SKILLMOD,
SET_ATTITUDE,
SET_CHANGEFAME,
SET_CHANGEKARMA,
SET_NAC,
SET_NWC,
SET_BODY,
SET_PHUE,
SET_HOMEX,
SET_HOMEY,
};
/*
* Staging slot filled by the .set command parser and consumed when the
* targeting cursor picks a mobile.
*/
// Custom - .set command staging slot consumed by GM_TargetSet
static struct {
int type;
int skillId;
int value;
int hasValue;
char strArg[64];
} g_PendingSet;
/*
* Custom - GM_ParseSetArgs
*
* Parse a .set / .settarget argument string into a stat/skill type plus
* value. On return *type < 0 means the argument did not match any known
* stat or skill (caller falls back to list/help). strArg receives the
* raw value portion, used by SET_NWC dice strings.
*/
static void
GM_ParseSetArgs(const char *arg, int *type, int *skillId, int *value, int *hasValue, char *strArg, size_t strArgSize)
{
const char *p = arg;
while (*p && *p != ' ')
p++;
int nlen = (int)(p - arg);
while (*p == ' ')
p++;
*value = 0;
*hasValue = 0;
*type = -1;
*skillId = -1;
strArg[0] = '\0';
if (*p != '\0') {
int vlen = (int)strlen(p);
if (vlen >= (int)strArgSize)
vlen = (int)strArgSize - 1;
memcpy(strArg, p, vlen);
strArg[vlen] = '\0';
if (sscanf(p, "%d", value) == 1)
*hasValue = 1;
}
if (nlen == 3 && strncasecmp(arg, "str", 3) == 0)
*type = SET_STR;
else if (nlen == 3 && strncasecmp(arg, "dex", 3) == 0)
*type = SET_DEX;
else if (nlen == 3 && strncasecmp(arg, "int", 3) == 0)
*type = SET_INT;
else if (nlen == 4 && strncasecmp(arg, "hits", 4) == 0)
*type = SET_HITS;
else if (nlen == 4 && strncasecmp(arg, "mana", 4) == 0)
*type = SET_MANA;
else if (nlen == 4 && strncasecmp(arg, "stam", 4) == 0)
*type = SET_STAM;
else if (nlen == 4 && strncasecmp(arg, "fame", 4) == 0)
*type = SET_FAME;
else if (nlen == 5 && strncasecmp(arg, "karma", 5) == 0)
*type = SET_KARMA;
else if (nlen == 3 && strncasecmp(arg, "all", 3) == 0)
*type = SET_ALL_SKILLS;
else if ((nlen == 5 && strncasecmp(arg, "maxhp", 5) == 0) || (nlen == 7 && strncasecmp(arg, "maxhits", 7) == 0))
*type = SET_MAXHP;
else if (nlen == 7 && strncasecmp(arg, "maxmana", 7) == 0)
*type = SET_MAXMANA;
else if ((nlen == 7 && strncasecmp(arg, "maxstam", 7) == 0) || (nlen == 10 && strncasecmp(arg, "maxfatigue", 10) == 0))
*type = SET_MAXSTAM;
else if ((nlen == 2 && strncasecmp(arg, "sm", 2) == 0) || (nlen == 6 && strncasecmp(arg, "strmod", 6) == 0) || (nlen == 11 && strncasecmp(arg, "strengthmod", 11) == 0))
*type = SET_STRMOD;
else if ((nlen == 2 && strncasecmp(arg, "dm", 2) == 0) || (nlen == 6 && strncasecmp(arg, "dexmod", 6) == 0) || (nlen == 12 && strncasecmp(arg, "dexteritymod", 12) == 0))
*type = SET_DEXMOD;
else if ((nlen == 2 && strncasecmp(arg, "im", 2) == 0) || (nlen == 6 && strncasecmp(arg, "intmod", 6) == 0) || (nlen == 15 && strncasecmp(arg, "intelligencemod", 15) == 0))
*type = SET_INTMOD;
else if ((nlen == 4 && strncasecmp(arg, "noto", 4) == 0) || (nlen == 9 && strncasecmp(arg, "notoriety", 9) == 0))
*type = SET_NOTORIETY;
else if (nlen == 6 && strncasecmp(arg, "hunger", 6) == 0)
*type = SET_HUNGER;
else if (nlen == 7 && strncasecmp(arg, "stomach", 7) == 0)
*type = SET_STOMACH;
else if (nlen == 8 && strncasecmp(arg, "attitude", 8) == 0)
*type = SET_ATTITUDE;
else if ((nlen == 5 && strncasecmp(arg, "cfame", 5) == 0) || (nlen == 10 && strncasecmp(arg, "changefame", 10) == 0))
*type = SET_CHANGEFAME;
else if ((nlen == 6 && strncasecmp(arg, "ckarma", 6) == 0) || (nlen == 11 && strncasecmp(arg, "changekarma", 11) == 0))
*type = SET_CHANGEKARMA;
else if ((nlen == 3 && strncasecmp(arg, "nac", 3) == 0) || (nlen == 9 && strncasecmp(arg, "naturalac", 9) == 0))
*type = SET_NAC;
else if ((nlen == 3 && strncasecmp(arg, "nwc", 3) == 0) || (nlen == 9 && strncasecmp(arg, "naturalwc", 9) == 0))
*type = SET_NWC;
else if (nlen == 4 && strncasecmp(arg, "body", 4) == 0)
*type = SET_BODY;
else if (nlen == 4 && strncasecmp(arg, "phue", 4) == 0)
*type = SET_PHUE;
else if (nlen == 6 && strncasecmp(arg, "home_x", 6) == 0)
*type = SET_HOMEX;
else if (nlen == 6 && strncasecmp(arg, "home_y", 6) == 0)
*type = SET_HOMEY;
else if (nlen == 8 && strncasecmp(arg, "skillmod", 8) == 0) {
int sid, sv;
if (sscanf(p, "%d %d", &sid, &sv) == 2 && sid >= 0 && sid < MAX_SKILLS) {
*type = SET_SKILLMOD;
*skillId = sid;
*value = sv;
*hasValue = 1;
} else {
char sn[80];
const char *lsp = strrchr(p, ' ');
if (lsp != NULL && sscanf(lsp + 1, "%d", &sv) == 1 && (lsp - p) < (int)sizeof(sn)) {
memcpy(sn, p, lsp - p);
sn[lsp - p] = '\0';
int i;
for (i = 0; i < MAX_SKILLS; i++) {
const char *nm = CSkillManager_GetSkillName(&g_SkillManager, (int8_t)i);
if (nm && strncasecmp(nm, sn, strlen(sn)) == 0 && (nm[strlen(sn)] == '\0' || nm[strlen(sn)] == '\r' || nm[strlen(sn)] == ' ')) {
*type = SET_SKILLMOD;
*skillId = i;
*value = sv;
*hasValue = 1;
break;
}
}
}
}
} else {
int sid, sv;
if (sscanf(arg, "%d %d", &sid, &sv) == 2 && sid >= 0 && sid < MAX_SKILLS && sv >= 0 && sv <= 1000) {
*type = SET_SKILL;
*skillId = sid;
*value = sv;
*hasValue = 1;
} else {
char sn[80];
const char *lsp = strrchr(arg, ' ');
if (lsp != NULL && sscanf(lsp + 1, "%d", &sv) == 1 && sv >= 0 && sv <= 1000 && (lsp - arg) < (int)sizeof(sn)) {
memcpy(sn, arg, lsp - arg);
sn[lsp - arg] = '\0';
int i;
for (i = 0; i < MAX_SKILLS; i++) {
const char *nm = CSkillManager_GetSkillName(&g_SkillManager, (int8_t)i);
if (nm && strncasecmp(nm, sn, strlen(sn)) == 0 && (nm[strlen(sn)] == '\0' || nm[strlen(sn)] == '\r' || nm[strlen(sn)] == ' ')) {
*type = SET_SKILL;
*skillId = i;
*value = sv;
*hasValue = 1;
break;
}
}
}
}
}
}
// Apply a stat or skill change to a mobile. Returns message in outMsg.
static void
GM_ApplySet(CItem *target, int type, int skillId, int value, int hasValue, const char *strArg, char *outMsg, int outMsgSize)
{
CMobile *mob = (CMobile *)target;
switch (type) {
case SET_STR:
if (hasValue && value >= 1 && value <= 200) {
mob->baseStr = (uint16_t)value;
CMobile_SetMaxHP(mob, value);
CMobile_SetHP(mob, value);
snprintf(outMsg, outMsgSize, "STR set to %d, HP=%d/%d", value, value, value);
} else if (!hasValue) {
snprintf(outMsg, outMsgSize, "STR=%d", mob->baseStr);
} else {
snprintf(outMsg, outMsgSize, "Value must be 1-200");
}
return;
case SET_DEX:
if (hasValue && value >= 1 && value <= 200) {
mob->baseDex = (uint16_t)value;
CMobile_SetMaxStamina(mob, value);
CMobile_SetStamina(mob, value);
snprintf(outMsg, outMsgSize, "DEX set to %d, Stam=%d/%d", value, value, value);
} else if (!hasValue) {
snprintf(outMsg, outMsgSize, "DEX=%d", mob->baseDex);
} else {
snprintf(outMsg, outMsgSize, "Value must be 1-200");
}
return;
case SET_INT:
if (hasValue && value >= 1 && value <= 200) {
mob->baseInt = (uint16_t)value;
CMobile_SetMaxMana(mob, value);
CMobile_SetMana(mob, value);
snprintf(outMsg, outMsgSize, "INT set to %d, Mana=%d/%d", value, value, value);
} else if (!hasValue) {
snprintf(outMsg, outMsgSize, "INT=%d", mob->baseInt);
} else {
snprintf(outMsg, outMsgSize, "Value must be 1-200");
}
return;
case SET_HITS:
if (hasValue && value >= 0 && value <= (int)mob->maxHp) {
CMobile_SetHP(mob, value);
snprintf(outMsg, outMsgSize, "HP set to %d/%d", value, mob->maxHp);
} else if (!hasValue) {
CMobile_SetHP(mob, (int)mob->maxHp);
snprintf(outMsg, outMsgSize, "HP refilled to %d/%d", mob->maxHp, mob->maxHp);
} else {
snprintf(outMsg, outMsgSize, "Value must be 0-%d", mob->maxHp);
}
return;
case SET_MANA:
if (hasValue && value >= 0 && value <= (int)mob->maxMana) {
CMobile_SetMana(mob, value);
snprintf(outMsg, outMsgSize, "Mana set to %d/%d", value, mob->maxMana);
} else if (!hasValue) {
CMobile_SetMana(mob, (int)mob->maxMana);
snprintf(outMsg, outMsgSize, "Mana refilled to %d/%d", mob->maxMana, mob->maxMana);
} else {
snprintf(outMsg, outMsgSize, "Value must be 0-%d", mob->maxMana);
}
return;
case SET_STAM:
if (hasValue && value >= 0 && value <= (int)mob->maxStamina) {
CMobile_SetStamina(mob, value);
snprintf(outMsg, outMsgSize, "Stam set to %d/%d", value, mob->maxStamina);
} else if (!hasValue) {
CMobile_SetStamina(mob, (int)mob->maxStamina);
snprintf(outMsg, outMsgSize, "Stam refilled to %d/%d", mob->maxStamina, mob->maxStamina);
} else {
snprintf(outMsg, outMsgSize, "Value must be 0-%d", mob->maxStamina);
}
return;
case SET_FAME:
if (hasValue && value >= 0 && value <= 20000) {
CMobile_SetFame(mob, (int16_t)value);
snprintf(outMsg, outMsgSize, "Fame set to %d", value);
} else if (!hasValue) {
snprintf(outMsg, outMsgSize, "Fame=%d", mob->fame);
} else {
snprintf(outMsg, outMsgSize, "Value must be 0-20000");
}
return;
case SET_KARMA:
if (hasValue && value >= -20000 && value <= 20000) {
CMobile_SetKarma(mob, (int16_t)value);
snprintf(outMsg, outMsgSize, "Karma set to %d", value);
} else if (!hasValue) {
snprintf(outMsg, outMsgSize, "Karma=%d", mob->karma);
} else {
snprintf(outMsg, outMsgSize, "Value must be -20000 to 20000");
}
return;
case SET_SKILL:
if (hasValue && value >= 0 && value <= 1000 && skillId >= 0 && skillId < MAX_SKILLS) {
CMobile_SetSkill(mob, (int8_t)skillId, (uint16_t)value);
CSkillManager_SendSkillUpdate(mob, (int8_t)skillId);
snprintf(outMsg, outMsgSize, "Skill %d set to %d", skillId, value);
} else {
snprintf(outMsg, outMsgSize, "Invalid skill/value");
}
return;
case SET_ALL_SKILLS:
if (hasValue && value >= 0 && value <= 1000) {
int i;
int numSkills = CSkillManager_GetMaxSkills(&g_SkillManager);
for (i = 0; i < numSkills; i++)
CMobile_SetSkill(mob, (int8_t)i, (uint16_t)value);
CSkillManager_SendSkillList(&g_SkillManager, (CItem *)mob);
snprintf(outMsg, outMsgSize, "All skills set to %d", value);
} else {
snprintf(outMsg, outMsgSize, "Usage: .settarget all VALUE (0-1000)");
}
return;
case SET_MAXHP:
if (!hasValue) {
snprintf(outMsg, outMsgSize, "MaxHP=%d", mob->maxHp);
return;
}
((void (*)(void *, int))VT_FN(target, VT_SET_MAX_HP))(mob, value);
snprintf(outMsg, outMsgSize, "MaxHP set to %d", value);
return;
case SET_MAXMANA:
if (!hasValue) {
snprintf(outMsg, outMsgSize, "MaxMana=%d", mob->maxMana);
return;
}
((void (*)(void *, int))VT_FN(target, VT_SET_MAX_MANA))(mob, value);
snprintf(outMsg, outMsgSize, "MaxMana set to %d", value);
return;
case SET_MAXSTAM:
if (!hasValue) {
snprintf(outMsg, outMsgSize, "MaxStam=%d", mob->maxStamina);
return;
}
((void (*)(void *, int))VT_FN(target, VT_SET_MAX_STAMINA))(mob, value);
snprintf(outMsg, outMsgSize, "MaxStam set to %d", value);
return;
case SET_STRMOD:
if (!hasValue) {
snprintf(outMsg, outMsgSize, "StrMod=%d", (int)mob->strBonus);
return;
}
((void (*)(void *, int, int16_t))VT_FN(target, VT_SET_STAT_BONUS))(mob, 0, (int16_t)value);
snprintf(outMsg, outMsgSize, "StrMod set to %d", value);
return;
case SET_DEXMOD:
if (!hasValue) {
snprintf(outMsg, outMsgSize, "DexMod=%d", (int)mob->dexBonus);
return;
}
((void (*)(void *, int, int16_t))VT_FN(target, VT_SET_STAT_BONUS))(mob, 1, (int16_t)value);
snprintf(outMsg, outMsgSize, "DexMod set to %d", value);
return;
case SET_INTMOD:
if (!hasValue) {
snprintf(outMsg, outMsgSize, "IntMod=%d", (int)mob->intBonus);
return;
}
((void (*)(void *, int, int16_t))VT_FN(target, VT_SET_STAT_BONUS))(mob, 2, (int16_t)value);
snprintf(outMsg, outMsgSize, "IntMod set to %d", value);
return;
case SET_NOTORIETY:
if (!hasValue) {
snprintf(outMsg, outMsgSize, "Noto=%d", mob->notoriety);
return;
}
((void (*)(void *, int))VT_FN(target, VT_SET_NOTORIETY))(mob, value);
snprintf(outMsg, outMsgSize, "Noto set to %d", value);
return;
case SET_HUNGER:
if (!hasValue) {
snprintf(outMsg, outMsgSize, "Hunger=%d", mob->hunger);
return;
}
mob->hunger = (uint8_t)value;
snprintf(outMsg, outMsgSize, "Hunger set to %d", (int)(uint8_t)value);
return;
case SET_STOMACH:
if (!hasValue) {
snprintf(outMsg, outMsgSize, "Stomach=%d", mob->stomach);
return;
}
mob->stomach = (uint8_t)value;
snprintf(outMsg, outMsgSize, "Stomach set to %d", (int)(uint8_t)value);
return;
case SET_SKILLMOD:
if (hasValue && skillId >= 0 && skillId < MAX_SKILLS) {
CMobile_SetSkillBonus(mob, (int8_t)skillId, (int16_t)value);
CSkillManager_SendSkillUpdate(mob, (int8_t)skillId);
snprintf(outMsg, outMsgSize, "SkillMod %d set to %d", skillId, value);
} else {
snprintf(outMsg, outMsgSize, "Usage: .set skillmod <id|name> <value>");
}
return;
case SET_ATTITUDE:
if (!VT_IsNPC(target)) {
snprintf(outMsg, outMsgSize, "Not an NPC");
return;
}
if (!hasValue) {
CNPC *npc = (CNPC *)target;
snprintf(outMsg, outMsgSize, "Attitude=%d", npc->mobile.attackMode);
return;
}
((CNPC *)target)->mobile.attackMode = (uint8_t)value;
snprintf(outMsg, outMsgSize, "Attitude set to %d", (int)(uint8_t)value);
return;
case SET_CHANGEFAME:
if (!hasValue) {
snprintf(outMsg, outMsgSize, "Usage: .set cfame <delta>");
return;
}
CMobile_ChangeFame(mob, value);
snprintf(outMsg, outMsgSize, "Fame changed by %d, now %d", value, mob->fame);
return;
case SET_CHANGEKARMA:
if (!hasValue) {
snprintf(outMsg, outMsgSize, "Usage: .set ckarma <delta>");
return;
}
CMobile_ChangeKarma(mob, value);
snprintf(outMsg, outMsgSize, "Karma changed by %d, now %d", value, mob->karma);
return;
case SET_NAC:
if (!hasValue) {
snprintf(outMsg, outMsgSize, "Usage: .set nac <value>");
return;
}
CMobile_SetBonusAC(mob, value);
snprintf(outMsg, outMsgSize, "Natural AC set to %d", value);
return;
case SET_NWC:
if (strArg == NULL || strArg[0] == '\0') {
snprintf(outMsg, outMsgSize, "Usage: .set nwc <NdM+K>");
return;
}
{
CWeaponDice dice;
CDiceRoll_InitParse(&dice, strArg);
CMobile_SetArmorRating(mob, &dice);
snprintf(outMsg, outMsgSize, "Natural WC set to %s", strArg);
}
return;
case SET_BODY:
if (!hasValue) {
snprintf(outMsg, outMsgSize, "Body=0x%04X", target->resourceEntity.entity.bodyType);
return;
}
CEntity_SetBodyType(target, (uint16_t)value);
if (!target->resourceEntity.entity.removedFromWorld) {
((void (*)(void *))VT_FN(target, VT_HIDE))(target);
((void (*)(void *))VT_FN(target, VT_RETURN_TO_TRACKED))(target);
}
snprintf(outMsg, outMsgSize, "Body set to 0x%04X", (uint16_t)value);
return;
case SET_PHUE:
if (!hasValue) {
snprintf(outMsg, outMsgSize, "Hue=0x%04X", target->resourceEntity.entity.color);
return;
}
target->resourceEntity.entity.color = (uint16_t)(value | 0x8000);
if (!target->resourceEntity.entity.removedFromWorld) {
((void (*)(void *))VT_FN(target, VT_HIDE))(target);
((void (*)(void *))VT_FN(target, VT_RETURN_TO_TRACKED))(target);
}
snprintf(outMsg, outMsgSize, "Partial hue set to 0x%04X", (uint16_t)(value | 0x8000));
return;
case SET_HOMEX:
if (!VT_IsNPC(target)) {
snprintf(outMsg, outMsgSize, "Not an NPC");
return;
}
if (!hasValue) {
snprintf(outMsg, outMsgSize, "home_x=%d", (int)(int16_t)((CNPC *)target)->homeLoc.x);
return;
}
{
CNPC *npc = (CNPC *)target;
npc->homeLoc.x = (uint16_t)value;
if ((int16_t)npc->homeLoc.z == -1)
npc->homeLoc.z = target->resourceEntity.entity.location.z;
snprintf(outMsg, outMsgSize, "home_x set to %d", value);
}
return;
case SET_HOMEY:
if (!VT_IsNPC(target)) {
snprintf(outMsg, outMsgSize, "Not an NPC");
return;
}
if (!hasValue) {
snprintf(outMsg, outMsgSize, "home_y=%d", (int)(int16_t)((CNPC *)target)->homeLoc.y);
return;
}
{
CNPC *npc = (CNPC *)target;
npc->homeLoc.y = (uint16_t)value;
if ((int16_t)npc->homeLoc.z == -1)
npc->homeLoc.z = target->resourceEntity.entity.location.z;
snprintf(outMsg, outMsgSize, "home_y set to %d", value);
}
return;
}
snprintf(outMsg, outMsgSize, "Unknown set type");
}
static void
GM_TargetSet(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z)
{
USED(type);
USED(x);
USED(y);
USED(z);
player->targetCallback = NULL;
if (serial == 0) {
CPlayer_SystemMessage(player, "Set cancelled");
return;
}
int itemLevel = (g_PendingSet.type == SET_BODY || g_PendingSet.type == SET_PHUE);
CItem *target = CWorld_FindBySerial(g_World, serial);
if (target == NULL) {
CPlayer_SystemMessage(player, "Target not found");
return;
}
if (!itemLevel && !VT_IsMobile(target)) {
CPlayer_SystemMessage(player, "Not a mobile");
return;
}
char msg[80];
GM_ApplySet(target, g_PendingSet.type, g_PendingSet.skillId, g_PendingSet.value, g_PendingSet.hasValue, g_PendingSet.strArg, msg, sizeof(msg));
char fullMsg[128];
const char *name = ((char *(*)(void *))VT_FN(target, VT_GET_NAME))(target);
snprintf(fullMsg, sizeof(fullMsg), "[0x%08X %s] %s", serial, name ? name : "?", msg);
CPlayer_SystemMessage(player, fullMsg);
}
static void
GM_TargetKill(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z)
{
USED(type);
USED(x);
USED(y);
USED(z);
player->targetCallback = NULL;
if (serial == 0) {
CPlayer_SystemMessage(player, "Kill cancelled");
return;
}
CItem *target = CWorld_FindBySerial(g_World, serial);
if (target == NULL || !VT_IsMobile(target)) {
CPlayer_SystemMessage(player, "Not a mobile");
return;
}
if (VT_IsDead(target)) {
CPlayer_SystemMessage(player, "Already dead");
return;
}
// Mirror combat death (combat.c:1415-1422 / 0x004B... DamageHelper):
// OnDeathWrap creates the corpse and runs death events but does not
// remove the mobile; the caller deletes it for non-players.
((void (*)(void *, void *, int))VT_FN(target, VT_ON_DEATH_WRAP))(target, player, 1);
if (!VT_IsPlayer(target))
((void (*)(void *))VT_FN(target, VT_DELETE))(target);
char msg[80];
snprintf(msg, sizeof(msg), "Killed 0x%08X", serial);
CPlayer_SystemMessage(player, msg);
}
static void
GM_TargetRemove(CPlayer *player, uint8_t type, uint32_t serial, uint16_t x, uint16_t y, uint16_t z)
{
USED(type);
USED(x);
USED(y);
USED(z);
player->targetCallback = NULL;
if (serial == 0) {
CPlayer_SystemMessage(player, "Remove cancelled");
return;
}
CItem *target = CWorld_FindBySerial(g_World, serial);
if (target == NULL) {
CPlayer_SystemMessage(player, "Entity not found");
return;
}
// Don't allow removing players
if (VT_IsPlayer(target)) {
CPlayer_SystemMessage(player, "Cannot remove a player");
return;
}
char msg[80];
snprintf(msg, sizeof(msg), "Removed 0x%08X", serial);
CWorld_DeleteEntity(g_World, target);
CPlayer_SystemMessage(player, msg);
}
CHelpQueue g_HelpQueue;
/*
* 0x0044DEC0 - CHelpQueue::ClearInit (inline)
*
* Empties the help list and zeros the counselor count.
*/
static __attribute__((unused)) CHelpQueue *
CHelpQueue_ClearInit(CHelpQueue *this)
{
void *dummy;
StdHelpList_Init((StdPtrList *)this, &dummy);
this->counselorCount = 0;
return this;
}
/*
* 0x0044DEE6 - CounselorDeleteChar
*
* Demotes a counselor character: hides the entity, clears counselor
* and dead pflags, restores the body type from sex, nulls notoriety,
* drops the counType script, detaches trackers, and equips a backpack
* so the world keeps a decay-eligible container. Returns 1 on success,
* 0 if not a counselor or the delete check fails.
*/
static int __attribute__((unused))
CounselorDeleteChar(CItem *self, CPlayer *entity)
{
CItem *container;
USED(self);
if (!CPlayer_IsCounselor(entity))
return 0;
if (!CItem_DeleteCheck2((CItem *)entity))
return 0;
((void (*)(void *))VT_FN((CItem *)entity, VT_HIDE))((CItem *)entity);
entity->pflags &= ~(uint32_t)PlayerIsCounselor;
entity->pflags &= ~(uint32_t)PlayerIsDead;
CEntity_SetBodyType((CItem *)entity, (uint16_t)(entity->mobile.sex + 0x190));
// Binary bug: writes to color at +0x08 - likely intended
// location.x at +0x0A. Reproduced exactly.
entity->mobile.container.item.resourceEntity.entity.color = 0x83EA;
((void (*)(void *, int))VT_FN((CItem *)entity, VT_SET_NOTORIETY))(entity, 0);
if (CResourceEntity_HasTag((CItem *)entity, "counType", 0))
CResourceEntity_DetachScript((CItem *)entity, "counType");
((int (*)(void *))VT_FN((CItem *)entity, VT_RETURN_TO_TRACKED))((CItem *)entity);
container = CWorld_CreateContainerItem(g_World, 0xE75);
if (container != NULL) {
if (CItem_TryEquipOnMobile(container, (CItem *)entity) == 1) {
CItem_Setup(container, 1, CEntity_GetLocation(&entity->mobile.container.item.resourceEntity.entity), 0, 1);
if (!ValidateInWorld(container))
container = NULL;
if (container != NULL)
CItem_DecayProcess(container);
} else {
if (container != NULL)
((void (*)(void *))VT_FN(container, VT_DELETE))(container);
container = NULL;
}
}
return 1;
}
/*
* 0x0044E1FE - CHelpQueue::FindBySerial
*
* Returns the queue entry with the given serial, or NULL if none.
*/
CHelpRequestNode *
CHelpQueue_FindBySerial(CHelpQueue *q, uint32_t serial)
{
CHelpRequestNode *node;
for (node = q->head; node != NULL; node = node->next) {
if (node->serial == serial)
return node;
}
return NULL;
}
/*
* 0x0044E272 - CHelpQueue::Add
*
* Adds a help request with level 'n' (new). Returns 0 if a request
* for the same serial is already queued.
*/
int
CHelpQueue_Add(CHelpQueue *q, uint32_t serial, const char *name, uint8_t level, const char *message)
{
if (CHelpQueue_FindBySerial(q, serial) != NULL)
return 0;
CHelpQueue_AddEntry(q, serial, 'n', (char)level, name, message);
return 1;
}
/*
* 0x0044E2D1 - CHelpQueue::AddEntry
*
* Appends a CHelpRequestEntry with the given fields. Returns 0 if
* a request for the same serial is already queued.