-
Notifications
You must be signed in to change notification settings - Fork 1
/
ChatCommander.lua
1195 lines (1063 loc) · 47 KB
/
ChatCommander.lua
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
-- ChatCommander
-- by Hexarobi
local SCRIPT_VERSION = "0.19.1"
---
--- Auto Updater
---
local auto_update_config = {
source_url="https://raw.githubusercontent.com/hexarobi/stand-lua-chatcommander/main/ChatCommander.lua",
script_relpath=SCRIPT_RELPATH,
project_url="https://github.com/hexarobi/stand-lua-chatcommander",
branch="main",
dependencies={
"lib/chat_commander/config.lua",
"lib/chat_commander/constants.lua",
"lib/chat_commander/item_browser.lua",
"lib/chat_commander/user_database.lua",
"lib/chat_commander/utils.lua",
"lib/chat_commander/vehicle_utils.lua",
"lib/file_database.lua",
-- ChatCommands
"lib/ChatCommands/other/event.lua",
"lib/ChatCommands/other/kick.lua",
"lib/ChatCommands/other/newlobby.lua",
"lib/ChatCommands/other/ping.lua",
"lib/ChatCommands/other/roulette.lua",
"lib/ChatCommands/other/blackjack.lua",
-- Player
"lib/ChatCommands/player/allguns.lua",
"lib/ChatCommands/player/ammo.lua",
"lib/ChatCommands/player/autoheal.lua",
"lib/ChatCommands/player/bail.lua",
"lib/ChatCommands/player/casino.lua",
"lib/ChatCommands/player/ceopay.lua",
"lib/ChatCommands/player/cleanup.lua",
"lib/ChatCommands/player/collectibles.lua",
"lib/ChatCommands/player/escape.lua",
"lib/ChatCommands/player/levelup.lua",
"lib/ChatCommands/player/parachute.lua",
"lib/ChatCommands/player/stuntjump.lua",
"lib/ChatCommands/player/teleport.lua",
"lib/ChatCommands/player/unstick.lua",
"lib/ChatCommands/player/vip.lua",
"lib/ChatCommands/player/wanted.lua",
-- Vehicles
"lib/ChatCommands/vehicle/copy.lua",
"lib/ChatCommands/vehicle/deletevehicle.lua",
"lib/ChatCommands/vehicle/fast.lua",
"lib/ChatCommands/vehicle/fav.lua",
"lib/ChatCommands/vehicle/gift.lua",
"lib/ChatCommands/vehicle/headlights.lua",
"lib/ChatCommands/vehicle/horn.lua",
"lib/ChatCommands/vehicle/livery.lua",
"lib/ChatCommands/vehicle/mods.lua",
"lib/ChatCommands/vehicle/modsmax.lua",
"lib/ChatCommands/vehicle/neonlights.lua",
"lib/ChatCommands/vehicle/paint.lua",
"lib/ChatCommands/vehicle/plate.lua",
"lib/ChatCommands/vehicle/platetype.lua",
"lib/ChatCommands/vehicle/repair.lua",
"lib/ChatCommands/vehicle/shuffle.lua",
"lib/ChatCommands/vehicle/spawn.lua",
"lib/ChatCommands/vehicle/tires.lua",
"lib/ChatCommands/vehicle/tiresmoke.lua",
"lib/ChatCommands/vehicle/tune.lua",
"lib/ChatCommands/vehicle/wash.lua",
"lib/ChatCommands/vehicle/wheelcolor.lua",
"lib/ChatCommands/vehicle/wheels.lua",
"lib/ChatCommands/vehicle/windowtint.lua",
},
}
-- If loading from Stand repository, then rely on it for updates and skip auto-updater
local is_from_repository = false
util.ensure_package_is_installed('lua/auto-updater')
local auto_updater = require('auto-updater')
if auto_updater == true and not is_from_repository then
auto_updater.run_auto_update(auto_update_config)
end
---
--- Chat Commander Vars
---
local chat_commander = {
chat_commands = {},
}
-- Short name
local cc = chat_commander
local menus = {}
local state = {}
local preferences = {
blessed_players={},
passthrough_commands={},
}
---
--- Dependencies
---
util.require_natives("3095a")
local constants = require("chat_commander/constants")
local utils = require("chat_commander/utils")
local vehicle_utils = require("chat_commander/vehicle_utils")
local config = require("chat_commander/config")
local item_browser = require("chat_commander/item_browser")
local user_db = require("chat_commander/user_database")
util.ensure_package_is_installed('lua/inspect')
local inspect = require("inspect")
-- Constructor lib is required for some commands, so install it from repo if its not already
util.ensure_package_is_installed('lua/Constructor')
local debug_log = utils.debug_log
local function error_msg(msg)
util.toast("Error: "..msg, TOAST_ALL)
end
---
--- Preferences
---
local PREFS_FOLDER = filesystem.resources_dir().."ChatCommander"
filesystem.mkdirs(PREFS_FOLDER)
local PREFS_FILE = PREFS_FOLDER.."/preferences.json"
local function clean_prefs(real_preferences)
local cleaned_prefs = {}
for key, value in real_preferences do
if type(value) == "table" then
if key ~= "menus" then
cleaned_prefs[key] = clean_prefs(value)
end
elseif type(value) == "string" or type(value) == "number" or type(value) == "boolean" then
cleaned_prefs[key] = value
else
debug_log("Skipped saving preference "..key.." due to invalid type")
end
end
return cleaned_prefs
end
local function apply_default_preferences(prefs)
if prefs.blessed_players == nil then prefs.blessed_players = {} end
if prefs.passthrough_commands == nil then prefs.passthrough_commands = {} end
end
local function save_prefs()
local file = io.open(PREFS_FILE, "wb")
if file == nil then util.toast("Error opening file for writing: "..PREFS_FILE, TOAST_ALL) return end
local cleaned_prefs = clean_prefs(preferences)
local encoded_prefs = soup.json.encode(cleaned_prefs)
if not encoded_prefs or encoded_prefs == "" then
util.toast("Failed to encode preferences")
else
file:write(encoded_prefs)
end
file:close()
end
local function load_prefs()
local file = io.open(PREFS_FILE)
if file then
local prefs_raw = file:read()
file:close()
if prefs_raw == nil then prefs_raw = "{}" end
preferences = soup.json.decode(prefs_raw)
--status, preferences = pcall(soup.json.decode, prefs_raw)
--if not status and type(preferences) == "string" then
-- preferences = DEFAULT_PREFERENCES
--end
--util.toast("Loaded prefs file "..inspect(preferences), TOAST_ALL)
else
save_prefs()
--util.toast("Created new prefs file "..inspect(PREFS_FILE), TOAST_ALL)
end
apply_default_preferences(preferences)
config.blessed_players = preferences.blessed_players
return preferences
end
load_prefs()
local function add_blessed_player(player_name)
if player_name == "" then return end
util.toast("Adding blessed player "..inspect(player_name), TOAST_ALL)
for _, player in preferences.blessed_players do
if player == player_name then
util.toast("Player already blessed")
return
end
end
table.insert(preferences.blessed_players, player_name)
end
local function save_user_command(pid, command)
local user_data = user_db.load_user(pid)
if user_data.command_counters[command] == nil then user_data.command_counters[command] = 0 end
user_data.command_counters[command] = user_data.command_counters[command] + 1
user_db.save_user(pid, user_data)
end
---
--- ChatCommands File Loader
---
cc.count_table = function(tbl)
local count = 0
for _, item in tbl do
count = count + 1
end
return count
end
cc.count_chat_commands = function()
return cc.count_table(cc.chat_commands)
end
cc.add_chat_command = function(command)
cc.expand_chat_command_defaults(command)
if command.command and cc.chat_commands[command.command] ~= nil then
util.toast("Error loading chat command: "..command.path.."/"..command.filename..": Command name is already taken")
else
cc.chat_commands[command.command] = command
--debug_log("Added command "..command.command.." #"..cc.get_num_chat_commands())
end
return command
end
cc.refresh_commands_from_files = function(directory, path)
if path == nil then path = "" end
for _, filepath in ipairs(filesystem.list_files(directory)) do
if filesystem.is_dir(filepath) then
local _2, dirname = string.match(filepath, "(.-)([^\\/]-%.?)$")
cc.refresh_commands_from_files(filepath, path.."/"..dirname)
else
local _3, filename, ext = string.match(filepath, "(.-)([^\\/]-%.?)[.]([^%.\\/]*)$")
if ext == "lua" or ext == "pluto" then
local command = require(config.chat_command_scripts_dir..path.."/"..filename)
--debug_log("Loading command "..config.chat_command_scripts_dir..path.."/"..filename..": "..inspect(command))
cc.expand_chat_command_defaults(command, filename, path)
cc.add_chat_command(command)
end
end
end
end
---
--- Chat Command Defaults
---
cc.expand_chat_command_defaults = function(chat_command, filename, path)
if chat_command.command == nil then
util.log("Cannot expand chat command without a command")
return
end
if chat_command.filename == nil then chat_command.filename = filename or chat_command.command end
if chat_command.path == nil then chat_command.path = path or "unknown" end
if chat_command.name == nil then chat_command.name = chat_command.filename end
if chat_command.menus == nil then chat_command.menus = {} end
chat_command.allowed_commands = { chat_command.command }
if chat_command.additional_commands ~= nil then
for _, allowed_command in chat_command.additional_commands do
table.insert(chat_command.allowed_commands, allowed_command)
end
end
-- If authorized_for isn't set at all, then default all to on
if chat_command.authorized_for == nil then
chat_command.authorized_for = {
me = true,
friends = true,
everyone = true,
blessed = true,
}
end
-- If authorized_for is set, but certain keys are not, then default missing keys to off
if chat_command.authorized_for.me == nil then chat_command.authorized_for.me = false end
if chat_command.authorized_for.friends == nil then chat_command.authorized_for.friends = false end
if chat_command.authorized_for.everyone == nil then chat_command.authorized_for.everyone = false end
if chat_command.authorized_for.blessed == nil then chat_command.authorized_for.blessed = false end
end
---
--- User Command Log
---
local user_command_log = {}
local function build_new_user_log(commands_log)
local new_user_log = {}
local expired_time = util.current_time_millis() - config.user_command_time
for _, log_item in pairs(commands_log) do
if log_item.time > expired_time then
table.insert(new_user_log, log_item)
end
end
return new_user_log
end
cc.log_user_command = function(pid, commands)
local new_log_item = {
time=util.current_time_millis(),
commands=commands
}
local rockstar_id = players.get_rockstar_id(pid)
if user_command_log[rockstar_id] == nil then user_command_log[rockstar_id] = {} end
local new_user_log = build_new_user_log(user_command_log[rockstar_id])
table.insert(new_user_log, new_log_item)
user_command_log[rockstar_id] = new_user_log
--debug_log("Tracked command for "..players.get_name(pid).." "..#new_user_log)
end
cc.is_user_allowed_to_issue_chat_command = function(pid, commands, chat_command)
local rockstar_id = players.get_rockstar_id(pid)
if user_command_log[rockstar_id] == nil then user_command_log[rockstar_id] = {} end
local new_user_log = build_new_user_log(user_command_log[rockstar_id])
if #new_user_log > (config.user_max_commands_per_time) and
not (pid == players.user() and config.is_player_allowed_to_bypass_commands_limit) then
utils.help_message(pid, "Please slow down your commands.")
return false
end
if not utils.is_player_authorized(pid) then
debug_log("User not authorized "..pid)
return false
end
if not utils.is_player_authorized_for_chat_command(pid, chat_command) then
debug_log("User not authorized for command "..pid.." "..chat_command.command)
return false
end
return true
end
---
--- Disable Built-In Chat Commands
---
local builtin_chat_commands_paths = {
"Online>Chat>Commands>For Strangers>Enabled",
"Online>Chat>Commands>For Team Chat>Enabled",
"Online>Chat>Commands>For Crew Members>Enabled",
"Online>Chat>Commands>For Friends>Enabled",
"Online>Chat>Commands>Enabled For Me",
}
local function disable_builtin_chat_commands()
if config.disable_builtin_chat_commands ~= true then return end
for _, builtin_chat_commands_path in pairs(builtin_chat_commands_paths) do
local command_ref = menu.ref_by_path(builtin_chat_commands_path)
if command_ref.value then
util.toast("Disabling built-in chat command option: "..builtin_chat_commands_path, TOAST_ALL)
command_ref.value = false
end
end
end
---
--- Lobby Finder
---
local function is_lobby_empty()
local players_list = players.list()
local num_players = #players_list
return num_players < config.min_num_players
end
local function should_find_new_lobby()
if not NETWORK.NETWORK_IS_SESSION_STARTED() then
return true
end
if util.current_time_millis() < state.lobby_created_at + config.fresh_lobby_delay then
return false
end
return is_lobby_empty()
end
local function find_new_lobby()
state.lobby_created_at = util.current_time_millis()
util.toast("Finding new lobby...", TOAST_ALL)
-- Enter key to dismiss any game alerts
PAD.SET_CONTROL_VALUE_NEXT_FRAME(2, 201, 1)
menu.trigger_commands(constants.lobby_mode_commands[config.lobby_mode_index][2])
end
state.lobby_created_at = util.current_time_millis()
local function enter_casino()
if NETWORK.NETWORK_IS_SESSION_STARTED() then
menu.trigger_commands("casinotp " .. players.get_name(players.user()))
end
end
---
--- AFK Mode
---
local function force_mc()
local org_type = players.get_org_type(players.user())
if org_type == -1 then
menu.trigger_commands("mcstart")
elseif org_type == 0 then
menu.trigger_commands("ceotomc")
end
end
local function force_org()
local org_type = players.get_org_type(players.user())
if org_type == -1 then
menu.trigger_commands("ceostart")
elseif org_type == 1 then
menu.trigger_commands("mctoceo")
end
end
local function force_roulette_area()
if not utils.is_player_within_dimensions(players.user(), {min={x=1130,y=240,z=-55},max={x=1150,y=270,z=-45}}) then
ENTITY.SET_ENTITY_COORDS(players.user_ped(), 1138.828, 256.55817, -51.035732)
end
end
local function force_rig_roulette()
local rig_roulette_menu = menu.ref_by_path("Online>Quick Progress>Casino>Roulette Outcome")
if menu.is_ref_valid(rig_roulette_menu) then
if rig_roulette_menu.value ~= 1 then
rig_roulette_menu.value = 1
end
else
util.toast("Failed to get command ref to rig roulette", TOAST_ALL)
end
end
local function force_rig_blackjack()
local rig_blackjack_menu = menu.ref_by_path("Online>Quick Progress>Casino>Always Win Blackjack")
if menu.is_ref_valid(rig_blackjack_menu) then
if not rig_blackjack_menu.value then
rig_blackjack_menu.value = true
end
else
util.toast("Failed to get command ref to rig blackjack", TOAST_ALL)
end
end
local function afk_casino_tick()
if not config.afk_in_casino then return end
if not utils.is_player_in_casino(players.user()) then
enter_casino()
else
force_roulette_area()
force_rig_roulette()
force_rig_blackjack()
--util.request_script_host("casinoroulette")
end
end
local function afk_mode_tick()
if config.afk_mode then
if state.next_afk_tick_time == nil or util.current_time_millis() > state.next_afk_tick_time then
state.next_afk_tick_time = util.current_time_millis() + config.afk_tick_handler_delay
force_mc()
if should_find_new_lobby() then
find_new_lobby()
else
afk_casino_tick()
end
end
end
return true
end
---
--- Announcements
---
local announcements = {
{
name="Basic Commands",
messages={
"Chat commands are now enabled for you! Spawn any vehicle with !name (Ex: !deluxo !op2 !raiju) "..
"Lose cops with !bail Heal with !autoheal Teleport with !tp Get RP with !levelup "..
"Get all weapons with !allguns Get more help with !help"
},
},
{
name="Casino Money",
messages={"For anyone that wants money, casino roulette is now rigged to always land on 1. Max bet and win 330k per spin. Blackjack is also rigged. For VIP access say !vip For more details say !roulette or !blackjack"},
validator=function()
return config.afk_mode and utils.is_player_in_casino(players.user())
end
},
--{
-- name="How to Gift",
-- messages={
-- "To keep spawned cars, start with a basic 10 car garage (!tp giftgarage) and fill it with any free car from phone, return your personal vehicle to garage, then use !gift",
-- }
--}
}
local function announce(announcement)
if not announcement.is_enabled then return end
if announcement.validator and type(announcement.validator) == "function" then
if not announcement.validator() then
--util.toast("Skipping invalid announcement: "..announcement.name)
return
end
end
if state.next_announcement_time ~= nil and (util.current_time_millis() < state.next_announcement_time) then
util.toast("Skipping flood delayed announcement: "..announcement.name)
return
end
state.next_announcement_time = util.current_time_millis() + config.announce_flood_delay
announcement.next_announcement_time = util.current_time_millis() + (config.announce_delay * 60000)
for _, message in pairs(announcement.messages) do
chat.send_message(utils.replace_command_character(message), false, true, true)
util.yield(config.announce_flood_delay)
end
end
local function announcement_tick()
if not config.is_auto_announcement_enabled then return end
if state.next_announcement_tick_time == nil or util.current_time_millis() > state.next_announcement_tick_time then
state.next_announcement_tick_time = util.current_time_millis() + config.announcement_tick_handler_delay
for _, announcement in pairs(announcements) do
if announcement.next_announcement_time == nil or util.current_time_millis() > announcement.next_announcement_time then
announce(announcement)
end
end
end
end
-- Init announcement delay
for _, announcement in pairs(announcements) do
announcement.next_announcement_time = util.current_time_millis() + (config.announce_delay * 60000)
end
---
--- Chat Handler
---
local function is_command_matched(commands, chat_command)
--debug_log("Checking for '"..tostring(commands[1]).."' in allowed commands: "..inspect(chat_command.allowed_commands))
if commands[1] == chat_command.command:lower() then
return true
end
if chat_command.allowed_commands then
for _, command_alias in pairs(chat_command.allowed_commands) do
if commands[1] == command_alias:lower() then
return true
end
end
end
return false
end
cc.find_chat_command = function(raw_command)
for _, chat_command in cc.chat_commands do
if chat_command.command == raw_command:lower() then
return chat_command
end
end
end
local function execute_chat_command(pid, commands, chat_command)
if cc.is_user_allowed_to_issue_chat_command(pid, commands, chat_command) then
--debug_log("Executing chat command function "..chat_command.name)
save_user_command(pid, commands[1])
return chat_command.execute(pid, commands, chat_command)
end
end
chat.on_message(function(pid, reserved, message_text, is_team_chat, networked, is_auto)
if is_auto then return end
if utils.str_starts_with(message_text, utils.get_chat_control_character()) then
--debug_log("Heard command: "..message_text)
local commands = utils.strsplit(message_text:lower():sub(2))
--debug_log("Command Keyword: "..tostring(commands[1]))
cc.log_user_command(pid, commands)
--debug_log("Checking against chat commands: "..inspect(cc.chat_commands))]
for _, chat_command in cc.chat_commands do
--debug_log("Checking chat command function "..chat_command.name)
cc.attach_execute_to_passthrough_command(chat_command)
if chat_command.is_enabled ~= false and is_command_matched(commands, chat_command) and chat_command.execute then
execute_chat_command(pid, commands, chat_command)
return
end
end
-- Default command if no others apply
if config.default_chat_command then
table.insert(commands, 1, config.default_chat_command.command)
if execute_chat_command(pid, commands, config.default_chat_command) then
return
end
end
if config.reply_to_unknown_commands then
utils.help_message(pid, "Invalid command. Try !help")
end
end
end)
---
--- Load Chat Commands From Files
---
cc.refresh_commands_from_files(filesystem.scripts_dir()..config.chat_command_scripts_dir)
debug_log("Loaded "..cc.count_chat_commands().." chat commands")
if config.default_chat_command_name then
config.default_chat_command = cc.find_chat_command(config.default_chat_command_name)
end
--debug_log("Default chat commands: "..inspect(config.default_chat_command))
---
--- PassThrough Commands
---
--local passthrough_commands = {
-- {
-- command="sprunk",
-- help="Spawns a sprunkified vehicle and some cans",
-- outbound_command_requires_player_name=true,
-- outbound_command="sprunk",
-- },
-- {
-- command="sprunkify",
-- help="Paints car green and more",
-- outbound_command_requires_player_name=true,
-- outbound_command="sprunkify",
-- },
-- {
-- command="sprunkrain",
-- help="Spawns some sprunk cans",
-- outbound_command_requires_player_name=true,
-- outbound_command="sprunkrain",
-- },
-- {
-- command="trivia",
-- help="Starts a game of trivia",
-- outbound_command="trivia",
-- },
-- {
-- command="casinotp",
-- additional_commands={"casino"},
-- outbound_command="casinotp",
-- outbound_command_requires_player_name=true,
-- },
-- {
-- command="collectibles",
-- additional_commands={"givecollectibles"},
-- outbound_command="givecollectibles",
-- outbound_command_requires_player_name=true,
-- },
--}
---
--- PassThrough Commands Handler
---
cc.add_extended_command = function(extended_command)
if extended_command.group == nil then
extended_command.group = "other"
end
extended_command.override_action_command = "ccextend"..extended_command.command -- Prefix pass through commands for uniqueness to avoid loop
cc.expand_chat_command_defaults(extended_command, extended_command.command, "ccextend")
local command = cc.add_chat_command(extended_command)
return command
end
cc.add_passthrough_command = function(passthrough_command)
if type(passthrough_command) ~= "table" then
passthrough_command = {command=passthrough_command}
end
passthrough_command.group = "passthrough"
return cc.add_extended_command(passthrough_command)
end
cc.attach_execute_to_passthrough_command = function(passthrough_command)
if passthrough_command.outbound_command ~= nil and passthrough_command.execute == nil then
passthrough_command.execute = function(pid, commands)
if passthrough_command.outbound_command_message then
utils.help_message(pid, passthrough_command.outbound_command_message)
end
local command_string = (passthrough_command.outbound_command or passthrough_command.command)
if pid ~= players.user() or passthrough_command.outbound_command_requires_player_name then
command_string = command_string .. " " .. players.get_name(pid)
end
if commands and commands[2] ~= nil then
command_string = command_string .. " " .. commands[2]
end
debug_log("Triggering passthrough command: "..command_string)
menu.trigger_commands(command_string)
end
end
end
cc.refresh_passthrough_commands = function()
for _, passthrough_command in preferences.passthrough_commands do
cc.add_passthrough_command(passthrough_command)
end
end
---
--- Constructor Spawnable Constructs Passthrough Commands
---
local CONSTRUCTS_DIR = filesystem.stand_dir() .. 'Constructs\\'
local SPAWNABLE_DIR = CONSTRUCTS_DIR.."spawnable"
local function load_spawnable_names_from_dir(directory)
local spawnable_names = {}
for _, filepath in ipairs(filesystem.list_files(directory)) do
if not filesystem.is_dir(filepath) then
local index, filename, ext = string.match(filepath, "(.-)([^\\/]-%.?)[.]([^%.\\/]*)$")
table.insert(spawnable_names, filename)
end
end
return spawnable_names
end
local function load_all_spawnable_names_from_dir(directory)
if not filesystem.exists(directory) then return {} end
local spawnable_names = load_spawnable_names_from_dir(directory)
for _, filepath in ipairs(filesystem.list_files(directory)) do
if filesystem.is_dir(filepath) then
for index, construct_plan_file in pairs(load_all_spawnable_names_from_dir(filepath)) do
table.insert(spawnable_names, construct_plan_file)
end
end
end
return spawnable_names
end
cc.add_construct_passthrough_command = function(passthrough_command)
passthrough_command.group="constructs"
return cc.add_passthrough_command(passthrough_command)
end
cc.refresh_construct_passthrough_commands = function()
for _, spawnable_name in pairs(load_all_spawnable_names_from_dir(SPAWNABLE_DIR)) do
cc.add_extended_command(
{
command=spawnable_name,
help="Spawn a "..spawnable_name,
group="constructs",
outbound_command=spawnable_name,
outbound_command_requires_player_name=true,
}
)
end
end
---
--- Help
---
cc.add_chat_command({
command="help",
additional_commands={"commands"},
group="other",
help={
"Welcome! Please don't grief others. For help with a specific command (or group) say !help <command>",
--"!spawn any vehicle by its name Ex: !deluxo !op2",
--"To keep cars, first fill a 10-car garage up with free cars, then use !gift",
--"More commands: !unstick !tpme !autoheal !bail !allguns !tp !repair !cleanup !paint !mods !wheels !shuffle !tune !fast",
},
execute=function(pid, commands, this_chat_command)
local groups = {}
local group_commands = {}
if type(commands) == "table" then
for _, chat_command in cc.chat_commands do
if commands[2] == chat_command.command then
utils.help_message(pid, chat_command.help)
return
elseif commands[2] == chat_command.group then
table.insert(group_commands, chat_command.command)
end
if not utils.is_in(chat_command.group, groups) then
table.insert(groups, chat_command.group)
end
end
end
if cc.count_table(group_commands) > 0 then
utils.help_message(pid, commands[2]:upper().." commands: "..table.concat(group_commands, " "))
return
end
-- Help's help message
utils.help_message(pid, this_chat_command.help)
utils.help_message(pid, "Command Groups: "..table.concat(groups, " "))
end,
})
---
--- Chat Command Menu Functions
---
local function get_unique_menu_id()
--- Used for generating unique menu input command names
if state.menu_counter == nil then state.menu_counter = 0 end
state.menu_counter = state.menu_counter + 1
return state.menu_counter
end
local function get_menu_action_help(chat_command_options)
if chat_command_options.help == nil then
return ""
end
if (type(chat_command_options.help) == "table") then
return chat_command_options.help[1]
end
return chat_command_options.help
end
local function add_chat_command_options_to_menu(root_menu, chat_command)
root_menu:action("Execute Command", {chat_command.override_action_command or chat_command.name}, "Immediately trigger this command for yourself. Ignores all restrictions.", function(click_type, pid)
if chat_command.execute == nil then
util.toast("No executable function found for chat command `"..chat_command.name.."`")
else
util.toast("Triggering chat command `"..chat_command.name.."`")
return chat_command.execute(pid, {chat_command.name}, chat_command)
end
end)
root_menu:action("Execute Command Help", {}, "Immediately trigger the help option for this command.", function(click_type, pid)
if chat_command.help == nil then
util.toast("No help found for chat command `"..chat_command.name.."`")
else
util.toast("Triggering help for chat command `"..chat_command.name.."`")
return utils.help_message(pid, chat_command.help)
end
end)
if chat_command.is_enabled == nil then chat_command.is_enabled = true end
root_menu:toggle("Enabled", {}, "Is this command currently active and usable by other players", function(toggle)
chat_command.is_enabled = toggle
end, chat_command.is_enabled)
chat_command.menus.authorized_for_menu = root_menu:list("Special Authorization For", {}, "To use this command a user must have general authorization, and be in at least one specially authorized group.")
chat_command.menus.authorized_for_menu:toggle("Me", {}, "Yourself", function(toggle)
chat_command.authorized_for.me = toggle
end, chat_command.authorized_for.me)
chat_command.menus.authorized_for_menu:toggle("Friends", {}, "People on your friends list", function(toggle)
chat_command.authorized_for.friends = toggle
end, chat_command.authorized_for.friends)
chat_command.menus.authorized_for_menu:toggle("Everyone", {}, "Everyone in the lobby", function(toggle)
chat_command.authorized_for.everyone = toggle
end, chat_command.authorized_for.everyone)
chat_command.menus.authorized_for_menu:toggle("Blessed", {}, "Players on your Blessed Players list", function(toggle)
chat_command.authorized_for.blessed = toggle
end, chat_command.authorized_for.blessed)
end
local function add_chat_command_to_menu(root_menu, chat_command)
if not menu.is_ref_valid(root_menu) then error("Error adding chat command to menu: Invalid root menu reference") end
chat_command.menus.root = root_menu:list(chat_command.name, {}, get_menu_action_help(chat_command))
chat_command.menus.root:divider(chat_command.name)
add_chat_command_options_to_menu(chat_command.menus.root, chat_command)
if chat_command.config_menu ~= nil then
chat_command.menus.root:divider("Config")
chat_command.config_menu(chat_command.menus.root)
end
return chat_command.menus.root
end
local function sort_items_by_name(items)
table.sort(items, function(a, b)
if a.name:lower() ~= b.name:lower() then
return a.name:lower() < b.name:lower()
end
end)
for _, item in items do
if item.items ~= nil then
sort_items_by_name(item.items)
end
end
end
local function build_chat_command_items()
local chat_commands_by_group = {}
for _, chat_command in cc.chat_commands do
if chat_command.group == nil then chat_command.group = "ungrouped" end
if chat_command.group ~= "passthrough" then
if chat_commands_by_group[chat_command.group] == nil then
chat_commands_by_group[chat_command.group] = {
name=chat_command.group,
items={},
}
end
table.insert(chat_commands_by_group[chat_command.group].items, chat_command)
end
end
--debug_log("chat_commands_by_group "..inspect(chat_commands_by_group))
local chat_command_items = {}
for _, chat_command_group in chat_commands_by_group do
table.insert(chat_command_items, chat_command_group)
end
sort_items_by_name(chat_command_items)
--debug_log("Sorted chat command items "..inspect(chat_command_items))
return chat_command_items
end
cc.build_chat_commands_menu = function()
menus.chat_commands = item_browser.browse_item(
menu.my_root(),
{
name="Chat Commands",
description="Browsable list of all chat commands you have installed",
items=build_chat_command_items(),
},
add_chat_command_to_menu
)
end
---
--- Startup
---
cc.refresh_passthrough_commands()
cc.refresh_construct_passthrough_commands()
---
--- Menu
---
menus.root = menu.my_root()
---
--- Chat Commands Menu
---
cc.build_chat_commands_menu()
---
--- Passthrough Menu
---
menus.passthrough_commands = menu.my_root():list("Passthrough Commands", {}, "Allow other stand commands to be triggered via chat commands")
menus.add_passthrough_command = menus.passthrough_commands:text_input("Add Command", {"ccpassthruadd"}, "Add a new passthrough chat command. This can then be configured to trigger another existing Stand command.", function(value)
local passthrough_command = cc.add_passthrough_command(value)
cc.add_passthrough_command_menu(passthrough_command)
table.insert(preferences.passthrough_commands, passthrough_command)
save_prefs()
menus.add_passthrough_command.value = ""
passthrough_command.menus.outbound_command:focus()
end, "")
cc.add_passthrough_command_menu = function(passthrough_command)
local menu_id = get_unique_menu_id()
passthrough_command.menus.passthrough_menu = menus.passthrough_commands:list(passthrough_command.command or "unknown", {}, "")
passthrough_command.menus.passthrough_menu:text_input("Inbound Command", { "ccpassthruinbound"..menu_id}, "The chat command that triggers this action", function(value)
passthrough_command.command = value
save_prefs()
end, passthrough_command.command or "")
passthrough_command.menus.outbound_command = passthrough_command.menus.passthrough_menu:text_input("Outbound Command", { "ccpassthruoutbound"..menu_id}, "The stand command that should be triggered by this action", function(value)
passthrough_command.outbound_command = value
save_prefs()
end, passthrough_command.outbound_command or "")
add_chat_command_options_to_menu(passthrough_command.menus.passthrough_menu, passthrough_command)
passthrough_command.menus.passthrough_menu:text_input("Help Text", { "ccpassthruhelp"..menu_id}, "The help text for this action", function(value)
passthrough_command.help = value
save_prefs()
end, passthrough_command.help or "")
--passthrough_command.menu:text_input("Group", {"ccpassthrugroup"..menu_id}, "The group for this command. Default: other", function(value)
-- passthrough_command.group = value
-- save_prefs()
--end, passthrough_command.group or "other")
passthrough_command.menus.passthrough_menu:toggle("Requires Player Name", {}, "Some Stand commands are player-specific. Check this box to automatically include the name of the player that issued the command.", function(value)
passthrough_command.outbound_command_requires_player_name = value
save_prefs()
end, passthrough_command.outbound_command_requires_player_name)
passthrough_command.menus.passthrough_menu:action("Delete", {}, "Delete this passthrough command", function()
cc.delete_passthrough_command(passthrough_command)
save_prefs()
menus.add_passthrough_command:focus()
end)
end
cc.delete_passthrough_command = function(deleted_command)
for key, passthrough_command in preferences.passthrough_commands do
if passthrough_command.command == deleted_command.command then
debug_log("Deleting passthrough command "..deleted_command.command)
preferences.passthrough_commands[key] = nil
cc.chat_commands[passthrough_command.command] = nil
if passthrough_command.menus.passthrough_menu and menu.is_ref_valid(passthrough_command.menus.passthrough_menu) then
menu.delete(passthrough_command.menus.passthrough_menu)
end
if passthrough_command.menus.root and menu.is_ref_valid(passthrough_command.menus.root) then
menu.delete(passthrough_command.menus.root)