Skip to content

Commit 43bbcae

Browse files
author
karel26
committed
Merge branch 'development'
2 parents b786dea + a6c1b64 commit 43bbcae

File tree

20 files changed

+234
-239
lines changed

20 files changed

+234
-239
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ My Fancy Server: # Your server name, as displayed in the server l
419419
ping_admin_on_crash: true # Ping DCS Admin role in discord, when the server crashed. Default: true
420420
autoscan: false # Enable autoscan for new missions (and auto-add them to the mission list). Default: false
421421
autoadd: true # Enable auto-adding of uploaded missions (default: true)
422+
validate_missions: true # Check, if your missions can be loaded or not (missing maps, etc). Default: true.
422423
ignore_dirs: # Optional: ignore directories from mission upload / mission add (already ignored are .dcssb, Scripts and Saves)
423424
- archive
424425
autorole: Fancy Players # Optional: give people this role, if they are online on this server (overwrites autorole[online] in bot.yaml!).

core/data/impl/serverimpl.py

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,8 @@ def set_status(self, status: Union[Status, str]):
239239
elif self._status in [Status.UNREGISTERED, Status.LOADING] and new_status in [Status.RUNNING, Status.PAUSED]:
240240
# only check the mission list, if we started that server
241241
if self._status == Status.LOADING:
242-
asyncio.create_task(self._load_mission_list())
242+
if self.locals.get('validate_missions', True):
243+
asyncio.create_task(self._load_mission_list())
243244
asyncio.create_task(self.init_extensions())
244245
asyncio.create_task(self._startup_extensions(status))
245246
elif self._status in [Status.RUNNING, Status.PAUSED, Status.SHUTTING_DOWN] and new_status in [Status.STOPPED, Status.SHUTDOWN]:
@@ -432,7 +433,7 @@ def do_startup(self):
432433
_mission = mission
433434
# check if the orig file has been updated
434435
orig = _mission + '.orig'
435-
if os.path.exists(orig) and os.path.getmtime(orig) > os.path.getmtime(mission):
436+
if os.path.exists(orig) and os.path.exists(mission) and os.path.getmtime(orig) > os.path.getmtime(mission):
436437
shutil.copy2(orig, _mission)
437438
missions.append(_mission)
438439
elif os.path.exists(mission):
@@ -617,7 +618,7 @@ async def shutdown(self, force: bool = False) -> None:
617618
await self.do_shutdown()
618619
# wait 30/60s for the process to terminate
619620
for i in range(1, 60 if self.node.locals.get('slow_system', False) else 30):
620-
if not self.process.is_running():
621+
if not self.process or not self.process.is_running():
621622
break
622623
await asyncio.sleep(1)
623624
await self._terminate()
@@ -634,7 +635,7 @@ async def is_running(self) -> bool:
634635

635636
async def _terminate(self) -> None:
636637
try:
637-
if not self.process.is_running():
638+
if not self.process or not self.process.is_running():
638639
return
639640
self.process.terminate()
640641
# wait 30/60s for the process to terminate
@@ -681,37 +682,39 @@ async def apply_mission_changes(self, filename: Optional[str] = None) -> str:
681682
if not filename:
682683
self.log.warning("No mission found. Is your mission list empty?")
683684
return filename
685+
684686
new_filename = utils.get_orig_file(filename)
685-
# process all mission modifications
686-
dirty = False
687-
for ext in self.extensions.values():
688-
if type(ext).beforeMissionLoad != Extension.beforeMissionLoad:
689-
new_filename, _dirty = await ext.beforeMissionLoad(new_filename)
690-
if _dirty:
691-
self.log.info(f' => {ext.name} applied on {new_filename}.')
692-
dirty |= _dirty
693-
# we did not change anything in the mission
694-
if not dirty:
687+
try:
688+
# process all mission modifications
689+
dirty = False
690+
for ext in self.extensions.values():
691+
if type(ext).beforeMissionLoad != Extension.beforeMissionLoad:
692+
new_filename, _dirty = await ext.beforeMissionLoad(new_filename)
693+
if _dirty:
694+
self.log.info(f' => {ext.name} applied on {new_filename}.')
695+
dirty |= _dirty
696+
# we did not change anything in the mission
697+
if not dirty:
698+
return filename
699+
# check if the original mission can be written
700+
if filename != new_filename:
701+
missions: list[str] = self.settings['missionList']
702+
try:
703+
index = missions.index(filename) + 1
704+
await self.replaceMission(index, new_filename)
705+
except ValueError:
706+
# we should not be here, but just in case
707+
if new_filename not in missions:
708+
await self.addMission(new_filename)
709+
return new_filename
710+
except Exception as ex:
711+
if isinstance(ex, UnsupportedMizFileException):
712+
self.log.error(ex)
713+
else:
714+
self.log.exception(ex)
715+
if filename != new_filename and os.path.exists(new_filename):
716+
os.remove(new_filename)
695717
return filename
696-
# check if the original mission can be written
697-
if filename != new_filename:
698-
missions: list[str] = self.settings['missionList']
699-
try:
700-
index = missions.index(filename) + 1
701-
await self.replaceMission(index, new_filename)
702-
except ValueError:
703-
# we should not be here, but just in case
704-
if new_filename not in missions:
705-
await self.addMission(new_filename)
706-
return new_filename
707-
except Exception as ex:
708-
if isinstance(ex, UnsupportedMizFileException):
709-
self.log.error(ex)
710-
else:
711-
self.log.exception(ex)
712-
if filename != new_filename and os.path.exists(new_filename):
713-
os.remove(new_filename)
714-
return filename
715718
finally:
716719
# enable autoscan
717720
if self.locals.get('autoscan', False):

core/utils/dcs.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import filecmp
12
import luadata
23
import math
34
import os
@@ -110,7 +111,10 @@ def desanitize(self, _filename: str = None) -> None:
110111

111112

112113
def is_desanitized(node: Node) -> bool:
114+
alt_filename = os.path.join(node.config_dir, 'MissionScripting.lua')
113115
filename = os.path.join(node.installation, 'Scripts', 'MissionScripting.lua')
116+
if os.path.exists(alt_filename):
117+
return filecmp.cmp(filename, alt_filename, shallow=False)
114118
with open(filename, mode='r', encoding='utf-8') as infile:
115119
for line in infile.readlines():
116120
if line.lstrip().startswith('--'):
@@ -162,6 +166,8 @@ def create_writable_mission(filename: str) -> str:
162166

163167

164168
def get_orig_file(filename: str, *, create_file: bool = True) -> Optional[str]:
169+
if filename.endswith('.orig'):
170+
return filename
165171
if '.dcssb' in filename:
166172
mission_file = os.path.join(os.path.dirname(filename).replace('.dcssb', ''),
167173
os.path.basename(filename))

core/utils/helper.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import random
2525
import math
2626

27+
from collections.abc import Mapping
2728
from copy import deepcopy
2829
from croniter import croniter
2930
from datetime import datetime, timedelta
@@ -68,6 +69,7 @@
6869
"SettingsDict",
6970
"RemoteSettingsDict",
7071
"tree_delete",
72+
"deep_merge",
7173
"hash_password",
7274
"evaluate",
7375
"for_each",
@@ -781,6 +783,18 @@ def tree_delete(d: dict, key: str, debug: Optional[bool] = False):
781783
curr_element.pop(int(keys[-1]))
782784

783785

786+
def deep_merge(dict1, dict2):
787+
result = dict(dict1) # Create a shallow copy of dict1
788+
for key, value in dict2.items():
789+
if key in result and isinstance(result[key], Mapping) and isinstance(value, Mapping):
790+
# Recursively merge dictionaries
791+
result[key] = deep_merge(result[key], value)
792+
else:
793+
# Overwrite or add the new key-value pair
794+
result[key] = value
795+
return result
796+
797+
784798
def hash_password(password: str) -> str:
785799
# Generate an 11 character alphanumeric string
786800
key = ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(11))

extensions/realweather/extension.py

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ def version(self) -> Optional[str]:
3939
return utils.get_windows_version(os.path.join(os.path.expandvars(self.config['installation']),
4040
'realweather.exe'))
4141

42+
def load_config(self) -> Optional[dict]:
43+
try:
44+
with open(self.config_path, mode='rb') as infile:
45+
return tomli.load(infile)
46+
except tomli.TOMLDecodeError as ex:
47+
raise RealWeatherException(f"Error while reading {self.config_path}: {ex}")
48+
4249
def get_config(self, filename: str) -> dict:
4350
if 'terrains' in self.config:
4451
miz = MizFile(filename)
@@ -62,7 +69,7 @@ def get_icao_code(filename: str) -> Optional[str]:
6269
else:
6370
return None
6471

65-
async def generate_config_1_0(self, input_mission: str, output_mission: str, cwd: str):
72+
async def generate_config_1_0(self, input_mission: str, output_mission: str, override: Optional[dict] = None):
6673
try:
6774
with open(self.config_path, mode='r', encoding='utf-8') as infile:
6875
cfg = json.load(infile)
@@ -88,10 +95,10 @@ async def generate_config_1_0(self, input_mission: str, output_mission: str, cwd
8895
}
8996
}
9097
self.config['metar'] = {"icao": icao}
91-
with open(os.path.join(cwd, 'config.json'), mode='w', encoding='utf-8') as outfile:
92-
json.dump(cfg, outfile, indent=2)
98+
self.locals = utils.deep_merge(cfg, override or {})
99+
await self.write_config()
93100

94-
async def generate_config_2_0(self, input_mission: str, output_mission: str, cwd: str):
101+
async def generate_config_2_0(self, input_mission: str, output_mission: str, override: Optional[dict] = None):
95102
tmpfd, tmpname = tempfile.mkstemp()
96103
os.close(tmpfd)
97104
try:
@@ -129,18 +136,26 @@ async def generate_config_2_0(self, input_mission: str, output_mission: str, cwd
129136
"icao": icao
130137
}
131138
}
132-
with open(os.path.join(cwd, 'config.toml'), mode='wb') as outfile:
133-
tomli_w.dump(cfg, outfile)
139+
self.locals = utils.deep_merge(cfg, override or {})
140+
await self.write_config()
134141

135-
async def beforeMissionLoad(self, filename: str) -> tuple[str, bool]:
136-
tmpfd, tmpname = tempfile.mkstemp()
137-
os.close(tmpfd)
142+
async def write_config(self):
138143
cwd = await self.server.get_missions_dir()
144+
if self.version.split('.')[0] == '1':
145+
with open(os.path.join(cwd, 'config.json'), mode='w', encoding='utf-8') as outfile:
146+
json.dump(self.locals, outfile, indent=2)
147+
else:
148+
with open(os.path.join(cwd, 'config.toml'), mode='wb') as outfile:
149+
tomli_w.dump(self.locals, outfile)
139150

151+
async def generate_config(self, filename: str, tmpname: str, config: Optional[dict] = None):
140152
if self.version.split('.')[0] == '1':
141-
await self.generate_config_1_0(filename, tmpname, cwd)
153+
await self.generate_config_1_0(filename, tmpname, config)
142154
else:
143-
await self.generate_config_2_0(filename, tmpname, cwd)
155+
await self.generate_config_2_0(filename, tmpname, config)
156+
157+
async def run_realweather(self, filename: str, tmpname: str) -> tuple[str, bool]:
158+
cwd = await self.server.get_missions_dir()
144159
rw_home = os.path.expandvars(self.config['installation'])
145160

146161
def run_subprocess():
@@ -151,7 +166,7 @@ def run_subprocess():
151166
self.log.error(stderr.decode('utf-8'))
152167
output = stdout.decode('utf-8')
153168
metar = next((x for x in output.split('\n') if 'METAR:' in x), "")
154-
remarks = self.config.get('realweather', {}).get('mission', {}).get('brief', {}).get('remarks', 'RMK Generated by DCS Real Weather')
169+
remarks = self.locals.get('realweather', {}).get('mission', {}).get('brief', {}).get('remarks', '')
155170
matches = re.search(rf"(?<=METAR: )(.*)(?= {remarks})", metar)
156171
if matches:
157172
self.metar = matches.group(0)
@@ -171,6 +186,18 @@ def run_subprocess():
171186
os.remove(tmpname)
172187
return new_filename, True
173188

189+
async def beforeMissionLoad(self, filename: str) -> tuple[str, bool]:
190+
tmpfd, tmpname = tempfile.mkstemp()
191+
os.close(tmpfd)
192+
await self.generate_config(filename, tmpname)
193+
return await self.run_realweather(filename, tmpname)
194+
195+
async def apply_realweather(self, filename: str, config: dict) -> str:
196+
tmpfd, tmpname = tempfile.mkstemp()
197+
os.close(tmpfd)
198+
await self.generate_config(utils.get_orig_file(filename), tmpname, config)
199+
return (await self.run_realweather(filename, tmpname))[0]
200+
174201
async def render(self, param: Optional[dict] = None) -> dict:
175202
if self.version.split('.')[0] == '1':
176203
icao = self.config.get('metar', {}).get('icao')

extensions/skyeye/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ MyNode:
2626
extensions:
2727
SkyEye:
2828
debug: true # Replicate the SkyEye console log into the DCSSB log
29-
config: '%USERPROFILE%\Saved Games\DCS.release_server\Config\SkyEye.yaml' # your SkyEye config file.
29+
config: '%USERPROFILE%\Saved Games\DCS.release_server\Config\SkyEye.yaml' # your SkyEye config file (default path)
3030
affinity: 14,15 # Set the core affinity for SkyEye (recommended!)
3131
coalition: blue # Which coalition should SkyEye be active on
3232
any-other-skyeye-config: xxx # See the SkyEye documentation.

extensions/skyeye/schemas/skyeye_schema.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ schema;instance_skyeye_schema:
88
type: map
99
mapping:
1010
enabled: {type: bool}
11+
affinity: {type: any}
1112
debug: {type: bool}
12-
config: {type: str, required: true}
13-
coalition: {type: str, required: true, enum: ['blue', 'red']}
13+
config: {type: str}
14+
coalition: {type: str, enum: ['blue', 'red']}
1415
recognizer: {type: str, enum: ['openai-whisper-local', 'openai-whisper-api']}
1516
whisper-model: {type: str}
1617
openai-api-key: {type: str}

plugins/funkman/README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Plugin "FunkMan"
2-
This plugin adds support for the Moose Funkman protocol. You need to install [FunkMan](https://github.com/funkyfranky/FunkMan) for it to work.<br/>
3-
And no, you don't need to run two bots, I just use some modules from FunkMan, just because they are nice :).
2+
This plugin adds support for the Moose Funkman protocol. You need to install [FunkMan](https://github.com/funkyfranky/FunkMan) for it to work.
3+
4+
> [!IMPORTANT]
5+
> You do not need to run the FunkMan bot!
6+
> I just use some modules from FunkMan, so all you need is to tell the bot where it finds the files.
47
58
FunkMan supports the following Moose modules:
69
* AIRBOSS
@@ -40,17 +43,16 @@ DEFAULT:
4043
4144
If you want to use different channels for your different servers, you can add a section for each server:
4245
```yaml
43-
DCS.release_server:
46+
DEFAULT:
4447
install: ../FunkMan
48+
IMAGEPATH: ../FunkMan/funkpics/
49+
DCS.release_server:
4550
CHANNELID_MAIN: 1122334455667788
4651
CHANNELID_RANGE: 8877665544332211
4752
CHANNELID_AIRBOSS: 1188227733664455
48-
IMAGEPATH: ../FunkMan/funkpics/
4953
DCS.release_server02:
5054
CHANNELID_MAIN: 1234567812345678
5155
CHANNELID_RANGE: 8765432187654321
52-
CHANNELID_AIRBOSS: 1112223334445555
53-
IMAGEPATH: ../FunkMan/funkpics/
5456
```
5557
5658
### Rangeboards for Strafing and Bombing

plugins/gamemaster/lua/callbacks.lua

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,4 @@ function gamemaster.onPlayerTryChangeSlot(playerID, side, slotID)
3737
end
3838
end
3939

40-
function gamemaster.onPlayerChangeSlot(id)
41-
local side = net.get_player_info(id, 'side')
42-
local slot = net.get_player_info(id, 'slot')
43-
local _, _slot, _ = utils.getMulticrewAllParameters(id)
44-
45-
-- workaround for non-working onPlayerTryChangeSlot calls on dynamic spawns
46-
if _slot > 1000000 and gamemaster.onPlayerTryChangeSlot(id, side, slot) == false then
47-
net.force_player_slot(id, side, 1)
48-
end
49-
end
50-
5140
DCS.setUserCallbacks(gamemaster)

plugins/mission/commands.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ async def info(self, interaction: Interaction, server: app_commands.Transform[Se
164164
@mission.command(description=_('Manage the active mission'))
165165
@app_commands.guild_only()
166166
@utils.app_has_role('DCS Admin')
167-
async def manage(self, interaction: Interaction, server: app_commands.Transform[Server, utils.ServerTransformer]):
167+
async def manage(self, interaction: Interaction, server: app_commands.Transform[Server, utils.ServerTransformer(
168+
status=[Status.RUNNING, Status.PAUSED, Status.STOPPED])]):
168169
view = ServerView(server)
169170
embed = await view.render(interaction)
170171
# noinspection PyUnresolvedReferences

0 commit comments

Comments
 (0)