Skip to content

Commit 3f37023

Browse files
committed
Merge branch 'development'
2 parents 3e30a1a + d8e6e29 commit 3f37023

File tree

16 files changed

+299
-110
lines changed

16 files changed

+299
-110
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ autorole: # Automatically give roles to pe
530530
online: Online # Give people that are online on any of your servers the "Online" role.
531531
no_dcs_autoban: false # If true, people banned on your Discord will not be banned on your servers (default: false)
532532
message_ban: User has been banned on Discord. # Default reason to show people that try to join your DCS servers when they are banned on Discord.
533-
message_autodelete: 300 # Most of the Discord messages are private messages. If not, this is the timeout after which they vanish. Default is 300 (5 mins).
533+
message_autodelete: 300 # Optional: Most of the Discord messages are private messages. If not, this is the timeout after which they vanish. Default is 300 (5 mins).
534534
channels:
535535
admin: 1122334455667788 # Optional: Central admin channel (see below).
536536
audit: 88776655443322 # Central audit channel to send audit events to (default: none)

Scripts/net/DCSServerBot/DCSServerBot.lua

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ if base.dcsbot ~= nil then
1010
return
1111
end
1212

13+
local MAX_CHUNK = 65000 -- safe UDP payload size
14+
local HEADER_SEP = '|' -- separator in the header
15+
local HEADER_FMT = '%s'..HEADER_SEP..'%d'..HEADER_SEP..'%d'..HEADER_SEP..'%d'..HEADER_SEP
16+
1317
-- load the configuration
1418
dofile(lfs.writedir() .. 'Scripts/net/DCSServerBot/DCSServerBotConfig.lua')
1519
local config = require('DCSServerBotConfig')
@@ -34,15 +38,34 @@ end
3438

3539
dcsbot.sendBotTable = dcsbot.sendBotTable or function (tbl, channel)
3640
tbl.server_name = cfg.name
37-
tbl.channel = tostring(channel or "-1")
38-
socket.try(dcsbot.UDPSendSocket:sendto(net.lua2json(tbl), config.BOT_HOST, config.BOT_PORT))
41+
tbl.channel = tostring(channel or "-1")
42+
43+
local msg = net.lua2json(tbl)
44+
45+
if #msg <= MAX_CHUNK then
46+
socket.try(dcsbot.UDPSendSocket:sendto(msg, config.BOT_HOST, config.BOT_PORT))
47+
return
48+
end
49+
50+
local msg_id = tostring(math.floor(socket.gettime() * 1e6))
51+
local total_parts = math.ceil(#msg / MAX_CHUNK)
52+
53+
for part = 1, total_parts do
54+
local start_idx = (part-1) * MAX_CHUNK + 1
55+
local end_idx = math.min(start_idx + MAX_CHUNK - 1, #msg)
56+
local payload = msg:sub(start_idx, end_idx)
57+
58+
local header = string.format(HEADER_FMT, msg_id, config.DCS_PORT, total_parts, part)
59+
local packet = header .. payload
60+
socket.try(dcsbot.UDPSendSocket:sendto(packet, config.BOT_HOST, config.BOT_PORT))
61+
end
3962
end
4063

41-
dcsbot.enableExtension = dcsbot.enableExtension or function (extension, config)
64+
dcsbot.enableExtension = dcsbot.enableExtension or function (extension, cfg)
4265
local msg = {
4366
command = 'enableExtension',
4467
extension = extension,
45-
config = config
68+
config = cfg
4669
}
4770
dcsbot.sendBotTable(msg, "-1")
4871
end

Scripts/net/DCSServerBot/DCSServerBotUtils.lua

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ local tostring = base.tostring
1313
local Sim = base.Sim
1414
local type = base.type
1515
local os = base.os
16+
local math = base.math
1617

1718
local lfs = require('lfs')
1819
local TableUtils = require('TableUtils')
@@ -28,13 +29,36 @@ UDPSendSocket = socket.udp()
2829
-- this is the DCS server name
2930
server_name = nil
3031

32+
local MAX_CHUNK = 65000 -- safe UDP payload size
33+
local HEADER_SEP = '|' -- separator in the header
34+
local HEADER_FMT = '%s'..HEADER_SEP..'%d'..HEADER_SEP..'%d'..HEADER_SEP..'%d'..HEADER_SEP
35+
3136
function sendBotTable(tbl, channel)
32-
if server_name == nil then
33-
server_name = loadSettingsRaw().name
34-
end
35-
tbl.server_name = server_name
36-
tbl.channel = tostring(channel or "-1")
37-
socket.try(UDPSendSocket:sendto(net.lua2json(tbl), config.BOT_HOST, config.BOT_PORT))
37+
if server_name == nil then
38+
server_name = loadSettingsRaw().name
39+
end
40+
tbl.server_name = server_name
41+
tbl.channel = tostring(channel or "-1")
42+
43+
local msg = net.lua2json(tbl)
44+
45+
if #msg <= MAX_CHUNK then
46+
socket.try(UDPSendSocket:sendto(msg, config.BOT_HOST, config.BOT_PORT))
47+
return
48+
end
49+
50+
local msg_id = tostring(math.floor(socket.gettime() * 1e6))
51+
local total_parts = math.ceil(#msg / MAX_CHUNK)
52+
53+
for part = 1, total_parts do
54+
local start_idx = (part-1) * MAX_CHUNK + 1
55+
local end_idx = math.min(start_idx + MAX_CHUNK - 1, #msg)
56+
local payload = msg:sub(start_idx, end_idx)
57+
58+
local header = string.format(HEADER_FMT, msg_id, config.DCS_PORT, total_parts, part)
59+
local packet = header .. payload
60+
socket.try(UDPSendSocket:sendto(packet, config.BOT_HOST, config.BOT_PORT))
61+
end
3862
end
3963

4064
function loadSettingsRaw()
@@ -56,7 +80,7 @@ function mergeGuiSettings(new_settings)
5680
end
5781

5882
function saveSettings(settings)
59-
mergedSettings = mergeGuiSettings(settings)
83+
local mergedSettings = mergeGuiSettings(settings)
6084
if mergedSettings.name ~= server_name then
6185
server_name = mergedSettings.name
6286
end
@@ -106,7 +130,7 @@ function getMulticrewAllParameters(PlayerId)
106130

107131
if (not tonumber(_player_slot)) then
108132
-- If this is multiseat slot parse master slot and look for seat number
109-
_t_start, _t_end = string.find(_player_slot, '_%d+')
133+
local _t_start, _t_end = string.find(_player_slot, '_%d+')
110134

111135
if _t_start then
112136
-- This is co-player
@@ -165,7 +189,7 @@ function isWithinInterval(last_event, interval)
165189
end
166190

167191
function loadScript(scriptPath)
168-
command = 'dofile(\\"' .. lfs.writedir():gsub('\\', '/') .. 'Scripts/net/DCSServerBot/' .. scriptPath .. '\\")'
192+
local command = 'dofile(\\"' .. lfs.writedir():gsub('\\', '/') .. 'Scripts/net/DCSServerBot/' .. scriptPath .. '\\")'
169193
net.dostring_in('mission', 'a_do_script("' .. command .. '")')
170194
end
171195

@@ -181,12 +205,12 @@ end
181205
function isDynamic(slotId)
182206
if not(string.find(slotId, 'red') or string.find(slotId, 'blue')) then
183207
-- Player took model
184-
_master_slot = slotId
185-
_sub_slot = 0
208+
local _master_slot = slotId
209+
local _sub_slot = 0
186210

187211
if (not tonumber(slotId)) then
188212
-- If this is multiseat slot parse master slot and look for seat number
189-
_t_start, _t_end = string.find(slotId, '_%d+')
213+
local _t_start, _t_end = string.find(slotId, '_%d+')
190214

191215
if _t_start then
192216
-- This is co-player

migrate.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,9 +407,10 @@ def migrate_3(node: str):
407407
'owner': int(cfg['BOT']['OWNER']),
408408
'automatch': cfg['BOT'].getboolean('AUTOMATCH'),
409409
'autoban': cfg['BOT'].getboolean('AUTOBAN'),
410-
'message_ban': cfg['BOT']['MESSAGE_BAN'],
411-
'message_autodelete': int(cfg['BOT']['MESSAGE_AUTODELETE'])
410+
'message_ban': cfg['BOT']['MESSAGE_BAN']
412411
}
412+
if int(cfg['BOT']['MESSAGE_AUTODELETE']) > 0:
413+
bot['message_autodelete'] = int(cfg['BOT']['MESSAGE_AUTODELETE'])
413414
# take the first admin channel as the single one
414415
if single_admin:
415416
for server_name, instance in utils.findDCSInstances():

plugins/admin/commands.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -918,7 +918,8 @@ async def _startup(server: Server):
918918
await server.startup()
919919
server.maintenance = False
920920
except (TimeoutError, asyncio.TimeoutError):
921-
await interaction.followup.send(_("Timeout while starting server {}!").format(server.name))
921+
await interaction.followup.send(_("Timeout while starting server {}!").format(server.name),
922+
ephemeral=True)
922923

923924
async def _node_online(node_name: str):
924925
next_startup = 0
@@ -929,7 +930,7 @@ async def _node_online(node_name: str):
929930
next_startup += startup_delay
930931
else:
931932
server.maintenance = False
932-
await interaction.followup.send(_("Node {} is now online.").format(node_name))
933+
await interaction.followup.send(_("Node {} is now online.").format(node_name), ephemeral=ephemeral)
933934
await self.bot.audit(f"took node {node_name} online.", user=interaction.user)
934935

935936
ephemeral = utils.get_ephemeral(interaction)

plugins/mission/commands.py

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,23 +1065,24 @@ async def persistence(self, interaction: discord.Interaction,
10651065
@airbase.command(name="info", description=_('Information about a specific airbase'))
10661066
@utils.app_has_role('DCS')
10671067
@app_commands.guild_only()
1068+
@app_commands.rename(_server='server')
10681069
@app_commands.rename(idx=_('airbase'))
10691070
@app_commands.describe(idx=_('Airbase for warehouse information'))
10701071
@app_commands.autocomplete(idx=utils.airbase_autocomplete)
10711072
async def airbase_info(self, interaction: discord.Interaction,
1072-
server: app_commands.Transform[Server, utils.ServerTransformer(
1073+
_server: app_commands.Transform[Server, utils.ServerTransformer(
10731074
status=[Status.RUNNING, Status.PAUSED])],
10741075
idx: int):
1075-
if server.status not in [Status.RUNNING, Status.PAUSED]:
1076+
if _server.status not in [Status.RUNNING, Status.PAUSED]:
10761077
# noinspection PyUnresolvedReferences
10771078
await interaction.response.send_message(_("Server {} is not running.").format(server.display_name),
10781079
ephemeral=True)
10791080
return
10801081

10811082
# noinspection PyUnresolvedReferences
1082-
await interaction.response.defer()
1083-
airbase = server.current_mission.airbases[idx]
1084-
data = await server.send_to_dcs_sync({
1083+
await interaction.response.defer(ephemeral=utils.get_ephemeral(interaction))
1084+
airbase = _server.current_mission.airbases[idx]
1085+
data = await _server.send_to_dcs_sync({
10851086
"command": "getAirbase",
10861087
"name": airbase['name']
10871088
})
@@ -1093,10 +1094,10 @@ async def airbase_info(self, interaction: discord.Interaction,
10931094
report = Report(self.bot, self.plugin_name, 'airbase.json')
10941095
env = await report.render(coalition=colors[data['coalition']], airbase=airbase, data=data)
10951096
if utils.check_roles(set(self.bot.roles['DCS Admin'] + self.bot.roles['GameMaster']), interaction.user):
1096-
view = AirbaseView(server, airbase, data)
1097+
view = AirbaseView(_server, airbase, data)
10971098
else:
10981099
view = discord.utils.MISSING
1099-
msg = await interaction.followup.send(embed=env.embed, view=view, ephemeral=utils.get_ephemeral(interaction))
1100+
msg = await interaction.followup.send(embed=env.embed, view=view)
11001101
try:
11011102
await view.wait()
11021103
finally:
@@ -1108,29 +1109,30 @@ async def airbase_info(self, interaction: discord.Interaction,
11081109
@airbase.command(description=_('Automatic Terminal Information Service (ATIS)'))
11091110
@utils.app_has_role('DCS')
11101111
@app_commands.guild_only()
1112+
@app_commands.rename(_server='server')
11111113
@app_commands.rename(idx=_('airbase'))
11121114
@app_commands.describe(idx=_('Airbase for ATIS information'))
11131115
@app_commands.autocomplete(idx=utils.airbase_autocomplete)
11141116
async def atis(self, interaction: discord.Interaction,
1115-
server: app_commands.Transform[Server, utils.ServerTransformer(
1117+
_server: app_commands.Transform[Server, utils.ServerTransformer(
11161118
status=[Status.RUNNING, Status.PAUSED])],
11171119
idx: int):
1118-
if server.status not in [Status.RUNNING, Status.PAUSED]:
1120+
if _server.status not in [Status.RUNNING, Status.PAUSED]:
11191121
# noinspection PyUnresolvedReferences
11201122
await interaction.response.send_message(_("Server {} is not running.").format(server.display_name),
11211123
ephemeral=True)
11221124
return
11231125
# noinspection PyUnresolvedReferences
11241126
await interaction.response.defer()
1125-
airbase = server.current_mission.airbases[idx]
1126-
data = await server.send_to_dcs_sync({
1127+
airbase = _server.current_mission.airbases[idx]
1128+
data = await _server.send_to_dcs_sync({
11271129
"command": "getWeatherInfo",
11281130
"x": airbase['position']['x'],
11291131
"y": airbase['position']['y'],
11301132
"z": airbase['position']['z']
11311133
})
11321134
report = Report(self.bot, self.plugin_name, 'atis.json')
1133-
env = await report.render(airbase=airbase, data=data, server=server)
1135+
env = await report.render(airbase=airbase, data=data, server=_server)
11341136
msg = await interaction.original_response()
11351137
await msg.edit(embed=env.embed, delete_after=self.bot.locals.get('message_autodelete'))
11361138

@@ -1151,7 +1153,7 @@ async def capture(self, interaction: discord.Interaction,
11511153
return
11521154

11531155
# noinspection PyUnresolvedReferences
1154-
await interaction.response.defer()
1156+
await interaction.response.defer(ephemeral=utils.get_ephemeral(interaction))
11551157
airbase = server.current_mission.airbases[idx]
11561158
await server.send_to_dcs_sync({
11571159
"command": "captureAirbase",
@@ -1160,7 +1162,7 @@ async def capture(self, interaction: discord.Interaction,
11601162
})
11611163
await interaction.followup.send(
11621164
_("Airbase \"{}\": Coalition changed to **{}**.\n:warning: Auto-capturing is now **disabled**!").format(
1163-
airbase['name'], coalition.lower()), ephemeral=utils.get_ephemeral(interaction))
1165+
airbase['name'], coalition.lower()))
11641166

11651167
@staticmethod
11661168
async def manage_items(server: Server, airbase: dict, category: str, item: str | list[int],
@@ -1214,16 +1216,17 @@ async def manage_warehouse(server: Server, airbase: dict, value: int | None = No
12141216
@airbase.command(description=_('Manage warehouses'))
12151217
@utils.app_has_role('DCS')
12161218
@app_commands.guild_only()
1219+
@app_commands.rename(_server='server')
12171220
@app_commands.rename(idx=_('airbase'))
12181221
@app_commands.describe(idx=_('Airbase for warehouse information'))
12191222
@app_commands.autocomplete(idx=utils.airbase_autocomplete)
12201223
@app_commands.autocomplete(category=wh_category_autocomplete)
12211224
@app_commands.autocomplete(item=wh_item_autocomplete)
12221225
async def warehouse(self, interaction: discord.Interaction,
1223-
server: app_commands.Transform[Server, utils.ServerTransformer(
1226+
_server: app_commands.Transform[Server, utils.ServerTransformer(
12241227
status=[Status.RUNNING, Status.PAUSED])],
12251228
idx: int, category: str | None = None, item: str | None = None, value: int | None = None):
1226-
if server.status not in [Status.RUNNING, Status.PAUSED]:
1229+
if _server.status not in [Status.RUNNING, Status.PAUSED]:
12271230
# noinspection PyUnresolvedReferences
12281231
await interaction.response.send_message(_("Server {} is not running.").format(server.display_name),
12291232
ephemeral=True)
@@ -1234,15 +1237,15 @@ async def warehouse(self, interaction: discord.Interaction,
12341237
raise PermissionError(_("You cannot change warehouse items."))
12351238

12361239
# noinspection PyUnresolvedReferences
1237-
await interaction.response.defer()
1238-
airbase = server.current_mission.airbases[idx]
1240+
await interaction.response.defer(ephemeral=True)
1241+
airbase = _server.current_mission.airbases[idx]
12391242

12401243
embed = discord.Embed(title=_("Warehouse information for {}").format(airbase['name']),
12411244
color=discord.Color.blue())
12421245

12431246
if category and item:
12441247
_item = list(map(int, item.split('.'))) if isinstance(item, str) else item
1245-
data = await Mission.manage_items(server, airbase, category, _item, value)
1248+
data = await Mission.manage_items(_server, airbase, category, _item, value)
12461249
if data['value'] == 1000000:
12471250
display = _("unlimited")
12481251
elif category == 'liquids':
@@ -1251,11 +1254,11 @@ async def warehouse(self, interaction: discord.Interaction,
12511254
display = _("{} pcs").format(data['value'])
12521255

12531256
item_name = next(
1254-
(x['name'] for x in server.resources.get(category) if str(x['wstype']) == item),
1257+
(x['name'] for x in _server.resources.get(category) if str(x['wstype']) == item),
12551258
'n/a'
12561259
)
12571260
embed.add_field(name=_("Inventory for {}").format(item_name), value="```" + display + "```")
1258-
await interaction.followup.send(embed=embed, ephemeral=utils.get_ephemeral(interaction))
1261+
await interaction.followup.send(embed=embed)
12591262

12601263
else:
12611264
if value is not None:
@@ -1266,11 +1269,11 @@ async def warehouse(self, interaction: discord.Interaction,
12661269
return
12671270

12681271
if category is not None:
1269-
await Mission.manage_category(server, airbase, category, value)
1272+
await Mission.manage_category(_server, airbase, category, value)
12701273
else:
1271-
await Mission.manage_warehouse(server, airbase, value)
1274+
await Mission.manage_warehouse(_server, airbase, value)
12721275

1273-
data = await server.send_to_dcs_sync({
1276+
data = await _server.send_to_dcs_sync({
12741277
"command": "getAirbase",
12751278
"name": airbase['name']
12761279
})
@@ -2490,19 +2493,21 @@ async def upload_warehouse_data(channel: discord.TextChannel, server: Server, sh
24902493
for airport in airports:
24912494
await channel.send(_("Loading warehouse for airport {} ...").format(airport))
24922495
for key, values in sheets.items():
2493-
await channel.send(_("Uploading {} information to your warehouse ...").format(SHEET_TITLES[key]))
2496+
await channel.send(_("> uploading {} information ...").format(SHEET_TITLES[key].lower()))
2497+
tasks = []
24942498
for k, v in values.items():
24952499
if key != 'liquids':
24962500
cmd = "setWarehouseItem"
24972501
else:
24982502
cmd = "setWarehouseLiquid"
2499-
await server.send_to_dcs_sync({
2503+
tasks.append(server.send_to_dcs_sync({
25002504
"command": cmd,
25012505
"name": airport,
25022506
"item": k,
25032507
"value": v
2504-
}, timeout=60)
2505-
await channel.send(_("Warehouse updated."))
2508+
}, timeout=60))
2509+
await asyncio.gather(*tasks)
2510+
await channel.send(_("Warehouse at {} updated.").format(airport))
25062511

25072512
async def handle_warehouse_uploads(self, message: discord.Message):
25082513
if not utils.check_roles(set(self.bot.roles['DCS Admin'] + self.bot.roles['GameMaster']), message.author):
@@ -2559,7 +2564,7 @@ async def handle_warehouse_uploads(self, message: discord.Message):
25592564
if not await utils.yn_question(
25602565
ctx,
25612566
question=_("Do you want to load a new warehouse configuration?"),
2562-
message=_("This will replace the warehouse configuration for {}").format(','.join(airports))
2567+
message=_("This will replace the warehouse configuration for:\n{}").format(','.join(airports))
25632568
):
25642569
await message.channel.send(_("Aborted."))
25652570
return

0 commit comments

Comments
 (0)