Rigs of Rods 2026.01
Soft-body Physics Simulation
Loading...
Searching...
No Matches
RigDef_Parser.cpp
Go to the documentation of this file.
1/*
2 This source file is part of Rigs of Rods
3 Copyright 2005-2012 Pierre-Michel Ricordel
4 Copyright 2007-2012 Thomas Fischer
5 Copyright 2013-2021 Petr Ohlidal
6
7 For more information, see http://www.rigsofrods.org/
8
9 Rigs of Rods is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License version 3, as
11 published by the Free Software Foundation.
12
13 Rigs of Rods is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with Rigs of Rods. If not, see <http://www.gnu.org/licenses/>.
20*/
21
25
26#include "RigDef_Parser.h"
27
28#include "Actor.h"
29#include "Application.h"
30#include "DashBoardManager.h"
31#include "SimConstants.h"
32#include "CacheSystem.h"
33#include "Console.h"
34#include "RigDef_File.h"
35#include "RigDef_Regexes.h"
36#include "Utils.h"
37
38#include <OgreException.h>
39#include <OgreString.h>
40#include <OgreStringVector.h>
41#include <OgreStringConverter.h>
42
43#include <algorithm>
44
45using namespace RoR;
46
47namespace RigDef
48{
49
50inline bool IsWhitespace(char c)
51{
52 return (c == ' ') || (c == '\t');
53}
54
55inline bool IsSeparator(char c)
56{
57 return IsWhitespace(c) || (c == ':') || (c == '|') || (c == ',');
58}
59
60inline bool StrEqualsNocase(std::string const & s1, std::string const & s2)
61{
62 if (s1.size() != s2.size()) { return false; }
63 for (size_t i = 0; i < s1.size(); ++i)
64 {
65 if (tolower(s1[i]) != tolower(s2[i])) { return false; }
66 }
67 return true;
68}
69
71{
72 // Push defaults
73 m_ror_default_inertia = std::shared_ptr<Inertia>(new Inertia);
74 m_ror_node_defaults = std::shared_ptr<NodeDefaults>(new NodeDefaults);
75}
76
78{
79 // First line in file (except blanks or comments) is the actor name
80 if (m_definition->name == "" && m_current_line != "")
81 {
82 m_definition->name = m_current_line; // Already trimmed
83 return;
84 }
85
86 // Split line to tokens
89 {
90 this->TokenizeCurrentLine();
91 }
92
93 // Detect keyword on current line
95 // Quick check - keyword always starts with ASCII letter
96 char c = tolower(m_current_line[0]); // Note: line comes in trimmed
97 if (c >= 'a' && c <= 'z')
98 {
100 }
102 switch (keyword)
103 {
104 // No keyword - Continue below to process current block.
105 case Keyword::INVALID:
106 break; // << NOT RETURN.
107
108 // Directives without arguments: just record, do not change current block.
115 case Keyword::RESCUER:
116 case Keyword::ROLLON:
118 this->ProcessGlobalDirective(keyword);
119 return;
121 this->ProcessChangeModuleLine(keyword);
122 return;
123
124 // Directives with arguments: process immediately, do not change current block.
127 return;
129 this->ParseAntiLockBrakes();
130 return;
131 case Keyword::AUTHOR:
132 this->ParseAuthor();
133 return;
136 return;
138 this->ParseCruiseControl();
139 return;
142 return;
145 return;
147 this->ParseExtCamera();
148 return;
151 return;
153 this->ParseFileinfo();
154 return;
157 return;
158 case Keyword::FORSET:
159 this->ParseDirectiveForset();
160 return;
161 case Keyword::FORVERT:
162 this->ParseDirectiveForvert();
163 return;
164 case Keyword::GUID:
165 this->ParseGuid();
166 return;
169 return;
170 case Keyword::SECTION:
171 this->ParseDirectiveSection();
172 return;
175 return;
178 return;
181 return;
184 return;
187 return;
190 return;
193 return;
196 return;
198 this->ParseSpeedLimiter();
199 return;
200 case Keyword::SUBMESH:
201 this->ParseDirectiveSubmesh();
202 return;
205 return;
207 this->ParseTractionControl();
208 return;
209
210 // Keywords which end current block:
213 case Keyword::END:
215 return;
216
217 // Ignored keywords (obsolete):
218 case Keyword::ENVMAP:
222 return;
223
224 // Keywords which start new block:
225 default:
226 this->BeginBlock(keyword);
227 return;
228 }
229
230 // Block extent tracking - assumes single 'nodes[2]' and 'beams' block in file.
231 if ((keyword == Keyword::NODES || keyword == Keyword::NODES2) && m_current_module->_hint_nodes12_start_linenumber == -1)
232 {
233 m_current_module->_hint_nodes12_start_linenumber = (int)m_current_line_number;
234 }
235 else if (keyword == Keyword::BEAMS && m_current_module->_hint_beams_start_linenumber == -1)
236 {
237 m_current_module->_hint_beams_start_linenumber = (int)m_current_line_number;
238 }
239 else if (m_current_module->_hint_nodes12_end_linenumber == -1)
240 {
241 m_current_module->_hint_nodes12_start_linenumber = (int)m_current_line_number - 1;
242 }
243 else if (m_current_module->_hint_beams_end_linenumber == -1)
244 {
245 m_current_module->_hint_beams_start_linenumber = (int)m_current_line_number - 1;
246 }
247
248 // Parse current block, if any
250 switch (m_current_block)
251 {
252 case Keyword::AIRBRAKES: this->ParseAirbrakes(); return;
253 case Keyword::ANIMATORS: this->ParseAnimator(); return;
254 case Keyword::ASSETPACKS: this->ParseAssetpacks(); return;
255 case Keyword::AXLES: this->ParseAxles(); return;
256 case Keyword::BEAMS: this->ParseBeams(); return;
257 case Keyword::BRAKES: this->ParseBrakes(); return;
258 case Keyword::CAMERAS: this->ParseCameras(); return;
259 case Keyword::CAB: this->ParseCab(); return;
260 case Keyword::CAMERARAIL: this->ParseCameraRails(); return;
261 case Keyword::CINECAM: this->ParseCinecam(); return;
263 case Keyword::COMMANDS2: this->ParseCommandsUnified(); return;
264 case Keyword::COLLISIONBOXES: this->ParseCollisionBox(); return;
265 case Keyword::CONTACTERS: this->ParseContacter(); return;
267 case Keyword::DESCRIPTION: this->ParseDescription(); return;
268 case Keyword::ENGINE: this->ParseEngine(); return;
269 case Keyword::ENGOPTION: this->ParseEngoption(); return;
270 case Keyword::ENGTURBO: this->ParseEngturbo(); return;
271 case Keyword::EXHAUSTS: this->ParseExhaust(); return;
272 case Keyword::FIXES: this->ParseFixes(); return;
273 case Keyword::FLARES:
274 case Keyword::FLARES2: this->ParseFlaresUnified(); return;
275 case Keyword::FLARES3: this->ParseFlares3(); return;
277 case Keyword::FLEXBODIES: this->ParseFlexbody(); return;
278 case Keyword::FLEXBODYWHEELS: this->ParseFlexBodyWheel(); return;
279 case Keyword::FUSEDRAG: this->ParseFusedrag(); return;
280 case Keyword::GLOBALS: this->ParseGlobals(); return;
281 case Keyword::GUISETTINGS: this->ParseGuiSettings(); return;
282 case Keyword::HELP: this->ParseHelp(); return;
283 case Keyword::HOOKS: this->ParseHook(); return;
284 case Keyword::HYDROS: this->ParseHydros(); return;
285 case Keyword::INTERAXLES: this->ParseInterAxles(); return;
286 case Keyword::LOCKGROUPS: this->ParseLockgroups(); return;
289 case Keyword::MESHWHEELS: this->ParseMeshWheel(); return;
290 case Keyword::MESHWHEELS2: this->ParseMeshWheel2(); return;
291 case Keyword::MINIMASS: this->ParseMinimass(); return;
292 case Keyword::NODES:
293 case Keyword::NODES2: this->ParseNodesUnified(); return;
294 case Keyword::PARTICLES: this->ParseParticles(); return;
295 case Keyword::PISTONPROPS: this->ParsePistonprops(); return;
296 case Keyword::PROPS: this->ParseProps(); return;
297 case Keyword::RAILGROUPS: this->ParseRailGroups(); return;
298 case Keyword::ROPABLES: this->ParseRopables(); return;
299 case Keyword::ROPES: this->ParseRopes(); return;
301 case Keyword::ROTATORS2: this->ParseRotatorsUnified(); return;
302 case Keyword::SCREWPROPS: this->ParseScrewprops(); return;
303 case Keyword::SCRIPTS: this->ParseScripts(); return;
304 case Keyword::SHOCKS: this->ParseShock(); return;
305 case Keyword::SHOCKS2: this->ParseShock2(); return;
306 case Keyword::SHOCKS3: this->ParseShock3(); return;
307 case Keyword::SLIDENODES: this->ParseSlidenodes(); return;
308 case Keyword::SOUNDSOURCES: this->ParseSoundsources(); return;
309 case Keyword::SOUNDSOURCES2: this->ParseSoundsources2(); return;
310 case Keyword::TEXCOORDS: this->ParseTexcoords(); return;
311 case Keyword::TIES: this->ParseTies(); return;
312 case Keyword::TORQUECURVE: this->ParseTorqueCurve(); return;
313 case Keyword::TRANSFERCASE: this->ParseTransferCase(); return;
314 case Keyword::TRIGGERS: this->ParseTriggers(); return;
315 case Keyword::TURBOJETS: this->ParseTurbojets(); return;
317 case Keyword::TURBOPROPS2: this->ParseTurbopropsUnified(); return;
318 case Keyword::VIDEOCAMERA: this->ParseVideoCamera(); return;
319 case Keyword::WHEELDETACHERS: this->ParseWheelDetachers(); return;
320 case Keyword::WHEELS: this->ParseWheel(); return;
321 case Keyword::WHEELS2: this->ParseWheel2(); return;
322 case Keyword::WINGS: this->ParseWing(); return;
323 default:;
324 };
325}
326
328{
329 if (min_args > m_num_args)
330 {
332 fmt::format("Not enough arguments (got {}, {} needed), skipping line", m_num_args, min_args));
333 return false;
334 }
335 return true;
336}
337
338// --------------------------------------------------------------------------
339// Parsing individual keywords
340// --------------------------------------------------------------------------
341
343{
344 if (!this->CheckNumArguments(16)) { return; }
345
346 Wing wing;
347
348 for (int i = 0; i < 8; i++) { wing.nodes[i] = this->GetArgNodeRef (i); }
349 for (int i = 8; i < 16; i++) { wing.tex_coords[i-8] = this->GetArgFloat (i); }
350
351 if (m_num_args > 16) { wing.control_surface = this->GetArgWingSurface (16); }
352 if (m_num_args > 17) { wing.chord_point = this->GetArgFloat (17); }
353 if (m_num_args > 18) { wing.min_deflection = this->GetArgFloat (18); }
354 if (m_num_args > 19) { wing.max_deflection = this->GetArgFloat (19); }
355 if (m_num_args > 20) { wing.airfoil = this->GetArgStr (20); }
356 if (m_num_args > 21) { wing.efficacy_coef = this->GetArgFloat (21); }
357
358 m_current_module->wings.push_back(wing);
360}
361
363{
364 if (! this->CheckNumArguments(2)) { return; } // 2 items: keyword, arg
365
367 cr.node_collision_range = this->GetArgFloat(1);
368
369 m_current_module->set_collision_range.push_back(cr);
371}
372
374{
375 if (!this->CheckNumArguments(17)) { return; }
376
377 Wheel2 wheel_2;
380
381 wheel_2.rim_radius = this->GetArgFloat ( 0);
382 wheel_2.tyre_radius = this->GetArgFloat ( 1);
383 wheel_2.width = this->GetArgFloat ( 2);
384 wheel_2.num_rays = this->GetArgInt ( 3);
385 wheel_2.nodes[0] = this->GetArgNodeRef ( 4);
386 wheel_2.nodes[1] = this->GetArgNodeRef ( 5);
387 wheel_2.rigidity_node = this->GetArgRigidityNode ( 6);
388 wheel_2.braking = this->GetArgBraking ( 7);
389 wheel_2.propulsion = this->GetArgPropulsion ( 8);
390 wheel_2.reference_arm_node = this->GetArgNodeRef ( 9);
391 wheel_2.mass = this->GetArgFloat (10);
392 wheel_2.rim_springiness = this->GetArgFloat (11);
393 wheel_2.rim_damping = this->GetArgFloat (12);
394 wheel_2.tyre_springiness = this->GetArgFloat (13);
395 wheel_2.tyre_damping = this->GetArgFloat (14);
396 wheel_2.face_material_name = this->GetArgStr (15);
397 wheel_2.band_material_name = this->GetArgStr (16);
398
400 {
402 }
403
404 m_current_module->wheels2.push_back(wheel_2);
406}
407
409{
410 if (! this->CheckNumArguments(14)) { return; }
411
412 Wheel wheel;
415
416 wheel.radius = this->GetArgFloat ( 0);
417 wheel.width = this->GetArgFloat ( 1);
418 wheel.num_rays = this->GetArgInt ( 2);
419 wheel.nodes[0] = this->GetArgNodeRef ( 3);
420 wheel.nodes[1] = this->GetArgNodeRef ( 4);
421 wheel.rigidity_node = this->GetArgRigidityNode ( 5);
422 wheel.braking = this->GetArgBraking ( 6);
423 wheel.propulsion = this->GetArgPropulsion ( 7);
424 wheel.reference_arm_node = this->GetArgNodeRef ( 8);
425 wheel.mass = this->GetArgFloat ( 9);
426 wheel.springiness = this->GetArgFloat (10);
427 wheel.damping = this->GetArgFloat (11);
428 wheel.face_material_name = this->GetArgStr (12);
429 wheel.band_material_name = this->GetArgStr (13);
430
432 {
434 }
435
436 m_current_module->wheels.push_back(wheel);
438}
439
441{
442 if (! this->CheckNumArguments(2)) { return; }
443
444 WheelDetacher wheeldetacher;
445
446 wheeldetacher.wheel_id = this->GetArgInt(0);
447 wheeldetacher.detacher_group = this->GetArgInt(1);
448
449 m_current_module->wheeldetachers.push_back(wheeldetacher);
451}
452
454{
455 Ogre::StringVector tokens = Ogre::StringUtil::split(m_current_line + 15, ","); // "TractionControl" = 15 characters
456 m_num_args = (int)tokens.size();
457 if (! this->CheckNumArguments(2)) { return; }
458
460 tc.regulation_force = this->ParseArgFloat(tokens[0].c_str());
461 tc.wheel_slip = this->ParseArgFloat(tokens[1].c_str());
462 if (tokens.size() > 2) { tc.fade_speed = this->ParseArgFloat(tokens[2].c_str()); }
463 if (tokens.size() > 3) { tc.pulse_per_sec = this->ParseArgFloat(tokens[3].c_str()); }
464
465 for (unsigned int i=4; i<tokens.size(); i++)
466 {
467 Ogre::StringVector args2 = Ogre::StringUtil::split(tokens[i], ":");
468 Ogre::StringUtil::trim(args2[0]);
469 Ogre::StringUtil::toLowerCase(args2[0]);
470
471 if (args2[0] == "mode" && args2.size() == 2)
472 {
473 Ogre::StringVector attrs = Ogre::StringUtil::split(args2[1], "&");
474 auto itor = attrs.begin();
475 auto endi = attrs.end();
476 for (; itor != endi; ++itor)
477 {
478 std::string attr = *itor;
479 Ogre::StringUtil::trim(attr);
480 Ogre::StringUtil::toLowerCase(attr);
481 if (strncmp(attr.c_str(), "nodash", 6) == 0) { tc.attr_no_dashboard = true; }
482 else if (strncmp(attr.c_str(), "notoggle", 8) == 0) { tc.attr_no_toggle = true; }
483 else if (strncmp(attr.c_str(), "on", 2) == 0) { tc.attr_is_on = true; }
484 else if (strncmp(attr.c_str(), "off", 3) == 0) { tc.attr_is_on = false; }
485 }
486 }
487 else
488 {
489 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "missing mode");
490 tc.attr_no_dashboard = false;
491 tc.attr_no_toggle = false;
492 tc.attr_is_on = true;
493 }
494 }
495
496 m_current_module->tractioncontrol.push_back(tc);
498}
499
501{
502 if (! this->CheckNumArguments(2)) { return; }
503
504 TransferCase tc;
505
506 tc.a1 = this->GetArgInt(0) - 1;
507 tc.a2 = this->GetArgInt(1) - 1;
508 if (m_num_args > 2) { tc.has_2wd = this->GetArgInt(2); }
509 if (m_num_args > 3) { tc.has_2wd_lo = this->GetArgInt(3); }
510 for (int i = 4; i < m_num_args; i++) { tc.gear_ratios.push_back(this->GetArgFloat(i)); }
511
512 m_current_module->transfercase.push_back(tc);
514}
515
517{
518 if (!this->CheckNumArguments(2)) { return; } // Items: keyword, arg
519
520 m_current_module->submesh_groundmodel.push_back(this->GetArgStr(1));
522}
523
525{
526 if (! this->CheckNumArguments(2)) { return; } // 2 items: keyword, arg
527
528 SpeedLimiter sl;
529 sl.is_enabled = true;
530 sl.max_speed = this->GetArgFloat(1);
531
532 m_current_module->speedlimiter.push_back(sl);
534}
535
537{
538 if (! this->CheckNumArguments(2)) { return; }
539
540 if (m_current_module->set_skeleton_settings.size() == 0)
541 {
542 m_current_module->set_skeleton_settings.push_back(SkeletonSettings());
543 }
544
545 SkeletonSettings& skel = m_current_module->set_skeleton_settings[0];
546 skel.visibility_range_meters = this->GetArgFloat(1);
547 if (m_num_args > 2) { skel.beam_thickness_meters = this->GetArgFloat(2); }
548
549 // Defaults
550 if (skel.visibility_range_meters < 0.f) { skel.visibility_range_meters = 150.f; }
552}
553
555{
556 if (!this->CheckNumArguments(2)) { return; }
557
558 float load_weight = this->GetArgFloat(1);
559 float friction = (m_num_args > 2) ? this->GetArgFloat(2) : -1;
560 float volume = (m_num_args > 3) ? this->GetArgFloat(3) : -1;
561 float surface = (m_num_args > 4) ? this->GetArgFloat(4) : -1;
562
563 m_user_node_defaults = std::shared_ptr<NodeDefaults>( new NodeDefaults(*m_user_node_defaults) );
564
565 m_user_node_defaults->load_weight = (load_weight < 0) ? m_ror_node_defaults->load_weight : load_weight;
566 m_user_node_defaults->friction = (friction < 0) ? m_ror_node_defaults->friction : friction;
567 m_user_node_defaults->volume = (volume < 0) ? m_ror_node_defaults->volume : volume;
568 m_user_node_defaults->surface = (surface < 0) ? m_ror_node_defaults->surface : surface;
569
570 if (m_num_args > 5) m_user_node_defaults->options = this->GetArgNodeOptions(5);
571}
572
574{
575 if (! this->CheckNumArguments(2)) { return; } // 2 items: keyword, arg
576
577 // Legacy behavior.
579}
580
582{
583 if (! this->CheckNumArguments(5)) { return; }
584
586 b->scale.springiness = this->GetArgFloat(1);
587
588 if (m_num_args > 2) { b->scale.damping_constant = this->GetArgFloat(2); }
590 if (m_num_args > 4) { b->scale.breaking_threshold_constant = this->GetArgFloat(4); }
591
592 m_user_beam_defaults = std::shared_ptr<BeamDefaults>(b);
593}
594
596{
597 if (! this->CheckNumArguments(2)) { return; } // 2 items: keyword, arg
598
600
601 // What's the state of "enable_advanced_deformation" feature at this point?
602 // Directive "enable_advanced_deformation" alters the effects of BeamDefaults
603 // Since the old parser worked on-the-fly, only BeamDefaults defined after the directive were affected
604
605 d._enable_advanced_deformation = m_definition->enable_advanced_deformation;
606
607 d._is_user_defined = true; //The "_enable_advanced_deformation" must only be aplied to user-defined values, not defaults.
608 d.springiness = this->GetArgFloat(1);
609
610 if (m_num_args > 2) { d.damping_constant = this->GetArgFloat(2); }
611 if (m_num_args > 3) { d.deformation_threshold = this->GetArgFloat(3); }
612 if (m_num_args > 4) { d.breaking_threshold = this->GetArgFloat(4); }
613 if (m_num_args > 5) { d.visual_beam_diameter = this->GetArgFloat(5); }
614 if (m_num_args > 6) { d.beam_material_name = this->GetArgStr (6); }
615 if (m_num_args > 7) { d.plastic_deform_coef = this->GetArgFloat(7); }
616
618
619 if (d.springiness < 0.f) { d.springiness = DEFAULT_SPRING; }
625
626 m_user_beam_defaults = std::shared_ptr<BeamDefaults>( new BeamDefaults(d) );
627 return;
628}
629
631{
632 if (! this->CheckNumArguments(2)) { return; } // 2 items: keyword, arg
633
634 if (m_current_module->props.size() > 0)
635 {
636 m_current_module->props[m_current_module->props.size() - 1].camera_settings.mode = this->GetArgInt(1);
637 }
638 else
639 {
640 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "This line must come after a prop!");
641 }
642}
643
645{
646 this->BeginBlock(Keyword::INVALID); // flush the current submesh
647 m_current_submesh = std::shared_ptr<Submesh>( new Submesh() );
648}
649
651{
653 {
654 m_current_submesh->backmesh = true;
655 }
656 else
657 {
658 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "must come after 'submesh'");
659 }
660}
661
662void Parser::ProcessGlobalDirective(Keyword keyword) // Directives that should only appear in root module
663{
664 switch (keyword)
665 {
666 case Keyword::DISABLEDEFAULTSOUNDS: m_definition->disable_default_sounds = true; return;
667 case Keyword::ENABLE_ADVANCED_DEFORMATION: m_definition->enable_advanced_deformation = true; return;
668 case Keyword::FORWARDCOMMANDS: m_definition->forward_commands = true; return;
669 case Keyword::IMPORTCOMMANDS: m_definition->import_commands = true; return;
670 case Keyword::HIDEINCHOOSER: m_definition->hide_in_chooser = true; return;
671 case Keyword::LOCKGROUP_DEFAULT_NOLOCK: m_definition->lockgroup_default_nolock = true; return;
672 case Keyword::RESCUER: m_definition->rescuer = true; return;
673 case Keyword::ROLLON: m_definition->rollon = true; return;
674 case Keyword::SLIDENODE_CONNECT_INSTANTLY: m_definition->slide_nodes_connect_instantly = true; return;
675
676 default: return;
677 }
678}
679
681{
684
685 mesh_wheel.tyre_radius = this->GetArgFloat ( 0);
686 mesh_wheel.rim_radius = this->GetArgFloat ( 1);
687 mesh_wheel.width = this->GetArgFloat ( 2);
688 mesh_wheel.num_rays = this->GetArgInt ( 3);
689 mesh_wheel.nodes[0] = this->GetArgNodeRef ( 4);
690 mesh_wheel.nodes[1] = this->GetArgNodeRef ( 5);
691 mesh_wheel.rigidity_node = this->GetArgRigidityNode ( 6);
692 mesh_wheel.braking = this->GetArgBraking ( 7);
693 mesh_wheel.propulsion = this->GetArgPropulsion ( 8);
694 mesh_wheel.reference_arm_node = this->GetArgNodeRef ( 9);
695 mesh_wheel.mass = this->GetArgFloat (10);
696 mesh_wheel.spring = this->GetArgFloat (11);
697 mesh_wheel.damping = this->GetArgFloat (12);
698 mesh_wheel.side = this->GetArgWheelSide (13);
699 mesh_wheel.mesh_name = this->GetArgStr (14);
700 mesh_wheel.material_name = this->GetArgStr (15);
701}
702
704{
705 if (! this->CheckNumArguments(16)) { return; }
706
707 MeshWheel mesh_wheel;
708 this->_ParseBaseMeshWheel(mesh_wheel);
709
711 {
713 }
714
715 m_current_module->meshwheels.push_back(mesh_wheel);
717}
718
720{
721 if (! this->CheckNumArguments(16)) { return; }
722
723 MeshWheel2 mesh_wheel;
724 this->_ParseBaseMeshWheel(mesh_wheel);
725
727 {
729 }
730
731 m_current_module->meshwheels2.push_back(mesh_wheel);
733}
734
736{
737 if (! this->CheckNumArguments(1)) { return; }
738
739 Hook hook;
740 hook.node = this->GetArgNodeRef(0);
741
742 int i = 1;
743 while (i < m_num_args)
744 {
745 std::string attr = this->GetArgStr(i);
746 Ogre::StringUtil::trim(attr);
747 const bool has_value = (i < (m_num_args - 1));
748
749 // Values
750 if (has_value && (attr == "hookrange") ) { hook.option_hook_range = this->GetArgFloat(++i); }
751 else if (has_value && (attr == "speedcoef") ) { hook.option_speed_coef = this->GetArgFloat(++i); }
752 else if (has_value && (attr == "maxforce") ) { hook.option_max_force = this->GetArgFloat(++i); }
753 else if (has_value && (attr == "timer") ) { hook.option_timer = this->GetArgFloat(++i); }
754 else if (has_value && (attr == "hookgroup" || attr == "hgroup") ) { hook.option_hookgroup = this->GetArgInt (++i); }
755 else if (has_value && (attr == "lockgroup" || attr == "lgroup") ) { hook.option_lockgroup = this->GetArgInt (++i); }
756 else if (has_value && (attr == "shortlimit" || attr == "short_limit")) { hook.option_min_range_meters = this->GetArgFloat(++i); }
757 // Flags
758 else if ((attr == "selflock") ||(attr == "self-lock") ||(attr == "self_lock") ) { hook.flag_self_lock = true; }
759 else if ((attr == "autolock") ||(attr == "auto-lock") ||(attr == "auto_lock") ) { hook.flag_auto_lock = true; }
760 else if ((attr == "nodisable")||(attr == "no-disable")||(attr == "no_disable")) { hook.flag_no_disable = true; }
761 else if ((attr == "norope") ||(attr == "no-rope") ||(attr == "no_rope") ) { hook.flag_no_rope = true; }
762 else if ((attr == "visible") ||(attr == "vis") ) { hook.flag_visible = true; }
763 else
764 {
765 this->LogMessage(Console::CONSOLE_SYSTEM_WARNING, fmt::format("ignoring invalid option '{}'", attr));
766 }
767 i++;
768 }
769
770 m_current_module->hooks.push_back(hook);
772}
773
775{
776 Help h;
777 h.material = m_current_line; // already trimmed
778 m_current_module->help.push_back(h);
779}
780
782{
783 if (! this->CheckNumArguments(2)) { return; }
784
785 GuiSettings gs;
786 gs.key = this->GetArgStr(0);
787 gs.value = this->GetArgStr(1);
788
789 m_current_module->guisettings.push_back(gs);
791}
792
794{
795 if (! this->CheckNumArguments(2)) { return; }
796
797 Guid g;
798 g.guid = this->GetArgStr(1);
799
800 m_current_module->guid.push_back(g);
802}
803
805{
806 if (! this->CheckNumArguments(2)) { return; }
807
808 Globals globals;
809 globals.dry_mass = this->GetArgFloat(0);
810 globals.cargo_mass = this->GetArgFloat(1);
811
812 if (m_num_args > 2) { globals.material_name = this->GetArgStr(2); }
813
814 m_current_module->globals.push_back(globals);
816}
817
819{
820 if (! this->CheckNumArguments(3)) { return; }
821
822 Fusedrag fusedrag;
823 fusedrag.front_node = this->GetArgNodeRef(0);
824 fusedrag.rear_node = this->GetArgNodeRef(1);
825
826 if (this->GetArgStr(2) == "autocalc")
827 {
828 fusedrag.autocalc = true;
829
830 // Fusedrag autocalculation from truck size
831 if (m_num_args > 3) { fusedrag.area_coefficient = this->GetArgFloat(3); }
832 if (m_num_args > 4) { fusedrag.airfoil_name = this->GetArgStr (4); }
833 }
834 else
835 {
836 // Original calculation
837 fusedrag.approximate_width = this->GetArgFloat(2);
838
839 if (m_num_args > 3) { fusedrag.airfoil_name = this->GetArgStr(3); }
840 }
841
842 m_current_module->fusedrag.push_back(fusedrag);
844}
845
847{
848 if (! this->CheckNumArguments(2)) { return; } // 2 items: keyword, arg
849
850 if (m_current_module->flexbodies.size() > 0)
851 {
852 m_current_module->flexbodies[m_current_module->flexbodies.size() - 1].camera_settings.mode = this->GetArgInt(1);
853 }
854 else
855 {
856 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "This line must come after a flexbody!");
857 }
858}
859
861{
862 if (! this->CheckNumArguments(3)) { return; }
863
865 {
866 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "must come after 'submesh'");
867 return;
868 }
869
870 Cab cab;
871 cab.nodes[0] = this->GetArgNodeRef(0);
872 cab.nodes[1] = this->GetArgNodeRef(1);
873 cab.nodes[2] = this->GetArgNodeRef(2);
874 if (m_num_args > 3) cab.options = this->GetArgCabOptions(3);
875
876 m_current_submesh->cab_triangles.push_back(cab);
878}
879
881{
882 if (! this->CheckNumArguments(3)) { return; }
883
885 {
886 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "must come after 'submesh'");
887 return;
888 }
889
890 Texcoord texcoord;
891 texcoord.node = this->GetArgNodeRef(0);
892 texcoord.u = this->GetArgFloat (1);
893 texcoord.v = this->GetArgFloat (2);
894
895 m_current_submesh->texcoords.push_back(texcoord);
897}
898
900{
901 if (! this->CheckNumArguments(10)) { return; }
902
903 Flexbody flexbody;
904 flexbody.reference_node = this->GetArgNodeRef (0);
905 flexbody.x_axis_node = this->GetArgNodeRef (1);
906 flexbody.y_axis_node = this->GetArgNodeRef (2);
907 flexbody.offset.x = this->GetArgFloat (3);
908 flexbody.offset.y = this->GetArgFloat (4);
909 flexbody.offset.z = this->GetArgFloat (5);
910 flexbody.rotation.x = this->GetArgFloat (6);
911 flexbody.rotation.y = this->GetArgFloat (7);
912 flexbody.rotation.z = this->GetArgFloat (8);
913 flexbody.mesh_name = this->GetArgStr (9);
914
915 m_current_module->flexbodies.push_back(flexbody);
917}
918
920{
921 if (m_current_module->flexbodies.size() == 0)
922 {
923 this->LogMessage(Console::CONSOLE_SYSTEM_WARNING, "ignoring 'forset': no matching flexbody!");
924 return;
925 }
926
928}
929
931{
932 if (m_current_module->flexbodies.size() == 0)
933 {
934 this->LogMessage(Console::CONSOLE_SYSTEM_WARNING, "ignoring 'forvert': no matching flexbody!");
935 return;
936 }
937
938 if (!this->CheckNumArguments(4)) { return; }
939
940 Forvert forvert; // The temp object.
942 forvert.node_ref = this->GetArgNodeRef(1);
943 forvert.node_x = this->GetArgNodeRef(2);
944 forvert.node_y = this->GetArgNodeRef(3);
945
946 // Forver supports comma-separated vert ranges like "A-B, C, D-E"
947 // The code used with 'forset' is quirky, let's use a custom code.
948 const char* input_pos = std::strstr(m_current_line, "verts:");
949 if (!input_pos)
950 {
951 this->LogMessage(Console::CONSOLE_SYSTEM_WARNING, "ignoring 'forvert': missing 'verts:' portion.");
952 return;
953 }
954
955 std::string input = input_pos;
956 std::regex pattern(R"((\d+)\s*-\s*(\d+)|(\d+))");
957 std::smatch matches;
958
959 std::string::const_iterator searchStart(input.cbegin());
960 while (std::regex_search(searchStart, input.cend(), matches, pattern))
961 {
962 if (matches[1].matched && matches[2].matched)
963 {
964 // Range found - unroll it into the `Forvert` array.
965 int start = std::stoi(matches[1].str());
966 int end = std::stoi(matches[2].str());
967 for (int i = start; i <= end; ++i)
968 {
969 forvert.vert_index = i; // Update the temp object
970 m_current_module->flexbodies.back().forvert.push_back(forvert); // Copy the temp object
971 }
972 }
973 else if (matches[3].matched)
974 {
975 // Single number found
976 forvert.vert_index = std::stoi(matches[3].str()); // Update the temp object
977 m_current_module->flexbodies.back().forvert.push_back(forvert); // Copy the temp object
978 }
979 searchStart = matches.suffix().first;
980 }
981}
982
983void Parser::ProcessForsetLine(RigDef::Flexbody& def, const std::string& _line, int line_number /*= -1*/)
984{
985 // --------------------------------------------------------------------------------------------
986 // BEWARE OF QUIRKS in the following code (they must be preserved for backwards compatibility):
987 // - a space between the 'forset' keyword and arguments is optional.
988 // - garbage characters anywhere on the line will silently add node 0 to the set.
989 // - a separator at the end of line will silently add node 0 to the set.
990 // --------------------------------------------------------------------------------------------
991
992 //parsing set definition
993 std::string line = _line;
994 char* pos = &line[0] + 6; // 'forset' = 6 characters
995 while (*pos == ' ' || *pos == ':' || *pos == ',') { pos++; } // Skip any separators
996 char* end = pos;
997 char endwas = 'G';
998 while (endwas != 0)
999 {
1000 int val1, val2; // Node numbers
1001 end = pos;
1002 while (*end != '-' && *end != ',' && *end != 0) end++;
1003 endwas = *end;
1004 *end = 0;
1005 val1 = strtoul(pos, 0, 10);
1006 if (endwas == '-')
1007 {
1008 pos = end + 1;
1009 end = pos;
1010 while (*end != ',' && *end != 0) end++;
1011 endwas = *end;
1012 *end = 0;
1013 val2 = strtoul(pos, 0, 10);
1014 // Add interval [val1-val2]
1015 def.node_list_to_import.push_back(
1017 Node::Ref(std::to_string(val1), val1, Node::Ref::IMPORT_STATE_IS_VALID, line_number),
1018 Node::Ref(std::to_string(val2), val2, Node::Ref::IMPORT_STATE_IS_VALID, line_number)));
1019 }
1020 else
1021 {
1022 // Add interval [val1-val1]
1023 Node::Range range_a = Node::Range(Node::Ref(std::to_string(val1), val1, Node::Ref::IMPORT_STATE_IS_VALID, line_number));
1024 def.node_list_to_import.push_back(range_a);
1025 }
1026 pos = end + 1;
1027 }
1028}
1029
1031{
1032 const bool is_flares2 = (m_current_block == Keyword::FLARES2);
1033 if (! this->CheckNumArguments(is_flares2 ? 6 : 5)) { return; }
1034
1035 Flare2 flare2;
1036 int pos = 0;
1037 flare2.reference_node = this->GetArgNodeRef(pos++);
1038 flare2.node_axis_x = this->GetArgNodeRef(pos++);
1039 flare2.node_axis_y = this->GetArgNodeRef(pos++);
1040 flare2.offset.x = this->GetArgFloat (pos++);
1041 flare2.offset.y = this->GetArgFloat (pos++);
1042
1044 {
1045 flare2.offset.z = this->GetArgFloat(pos++);
1046 }
1047
1048 if (m_num_args > pos) { flare2.type = this->GetArgFlareType(pos++); }
1049
1050 if (m_num_args > pos)
1051 {
1052 switch (flare2.type)
1053 {
1054 case FlareType::USER: flare2.control_number = this->GetArgInt(pos); break;
1055 case FlareType::DASHBOARD: flare2.dashboard_link = this->GetArgStr(pos); break;
1056 default: break;
1057 }
1058 pos++;
1059 }
1060
1061 if (m_num_args > pos) { flare2.blink_delay_milis = this->GetArgInt (pos++); }
1062 if (m_num_args > pos) { flare2.size = this->GetArgFloat (pos++); }
1063 if (m_num_args > pos) { flare2.material_name = this->GetArgStr (pos++); }
1064
1065 m_current_module->flares2.push_back(flare2);
1067}
1068
1070{
1071 const bool is_flares2 = (m_current_block == Keyword::FLARES2);
1072 if (! this->CheckNumArguments(is_flares2 ? 6 : 5)) { return; }
1073
1074 Flare3 flare3;
1076
1077 flare3.reference_node = this->GetArgNodeRef(0);
1078 flare3.node_axis_x = this->GetArgNodeRef(1);
1079 flare3.node_axis_y = this->GetArgNodeRef(2);
1080 flare3.offset.x = this->GetArgFloat(3);
1081 flare3.offset.y = this->GetArgFloat(4);
1082 flare3.offset.z = this->GetArgFloat(5);
1083 if (m_num_args > 6) { flare3.type = this->GetArgFlareType(6); }
1084
1085 if (m_num_args > 7)
1086 {
1087 switch (flare3.type)
1088 {
1089 case FlareType::USER: flare3.control_number = this->GetArgInt(7); break;
1090 case FlareType::DASHBOARD: flare3.dashboard_link = this->GetArgStr(7); break;
1091 default: break;
1092 }
1093 }
1094
1095 if (m_num_args > 8) { flare3.blink_delay_milis = this->GetArgInt (8); }
1096 if (m_num_args > 9) { flare3.size = this->GetArgFloat (9); }
1097 if (m_num_args > 10) { flare3.material_name = this->GetArgStr (10); }
1098
1099 m_current_module->flares3.push_back(flare3);
1101}
1102
1104{
1105 m_current_module->fixes.push_back(this->GetArgNodeRef(0));
1106}
1107
1109{
1110 if (! this->CheckNumArguments(2)) { return; }
1111
1112 ExtCamera extcam;
1113 extcam.mode = this->GetArgExtCameraMode(1);
1114 if (m_num_args > 2) { extcam.node = this->GetArgNodeRef(2); }
1115
1116 m_current_module->extcamera.push_back(extcam);
1118}
1119
1121{
1122 if (! this->CheckNumArguments(2)) { return; }
1123
1124 Exhaust exhaust;
1125 exhaust.reference_node = this->GetArgNodeRef(0);
1126 exhaust.direction_node = this->GetArgNodeRef(1);
1127
1128 // Param [2] is unused
1129 if (m_num_args > 3) { exhaust.particle_name = this->GetArgStr(3); }
1130
1131 m_current_module->exhausts.push_back(exhaust);
1133}
1134
1136{
1137 if (! this->CheckNumArguments(2)) { return; }
1138
1140 ffv.version = this->GetArgInt(1);
1141
1142 m_current_module->fileformatversion.push_back(ffv);
1144}
1145
1147{
1148 if (!this->CheckNumArguments(2)) { return; } // 2 items: keyword, param
1149
1150 DefaultSkin data;
1151 data.skin_name = this->GetArgStr(1);
1152 std::replace(data.skin_name.begin(), data.skin_name.end(), '_', ' ');
1153
1154 m_current_module->default_skin.push_back(data);
1156}
1157
1159{
1160 if (! this->CheckNumArguments(2)) { return; } // 2 items: keyword, param
1161
1162 if (this->GetArgStr(1) == "end")
1163 {
1165 }
1166 else
1167 {
1169 }
1170}
1171
1173{
1174 if (! this->CheckNumArguments(3)) { return; } // keyword + 2 params
1175
1176 CruiseControl cruise_control;
1177 cruise_control.min_speed = this->GetArgFloat(1);
1178 cruise_control.autobrake = this->GetArgInt(2);
1179
1180 m_current_module->cruisecontrol.push_back(cruise_control);
1182}
1183
1185{
1186 m_current_module->description.push_back(m_current_line); // Already trimmed
1187}
1188
1190{
1191 Ogre::StringVector tokens = Ogre::StringUtil::split(m_current_line + 14, ","); // "add_animation " = 14 characters
1192 m_num_args = (int)tokens.size();
1193 if (! this->CheckNumArguments(4)) { return; }
1194
1195 if (m_current_module->props.size() == 0)
1196 {
1197 this->LogMessage(Console::CONSOLE_SYSTEM_WARNING, "'add_animation' must come after prop, ignoring...");
1198 return;
1199 }
1200
1201 Animation animation;
1202 animation.ratio = this->ParseArgFloat(tokens[0].c_str());
1203 animation.lower_limit = this->ParseArgFloat(tokens[1].c_str());
1204 animation.upper_limit = this->ParseArgFloat(tokens[2].c_str());
1205
1206 for (auto itor = tokens.begin() + 3; itor != tokens.end(); ++itor)
1207 {
1208 Ogre::StringVector entry = Ogre::StringUtil::split(*itor, ":");
1209 Ogre::StringUtil::trim(entry[0]);
1210 if (entry.size() > 1) Ogre::StringUtil::trim(entry[1]);
1211
1212 const int WARN_LEN = 500;
1213 char warn_msg[WARN_LEN] = "";
1214
1215 if (entry.size() == 1) // Single keyword
1216 {
1217 if (entry[0] == "autoanimate") { animation.mode |= Animation::MODE_AUTO_ANIMATE; }
1218 else if (entry[0] == "noflip") { animation.mode |= Animation::MODE_NO_FLIP; }
1219 else if (entry[0] == "bounce") { animation.mode |= Animation::MODE_BOUNCE; }
1220 else if (entry[0] == "eventlock") { animation.mode |= Animation::MODE_EVENT_LOCK; }
1221
1222 else { snprintf(warn_msg, WARN_LEN, "Invalid keyword: %s", entry[0].c_str()); }
1223 }
1224 else if (entry.size() == 2 && (entry[0] == "mode" || entry[0] == "event" || entry[0] == "source" || entry[0] == "link"))
1225 {
1226 Ogre::StringVector values = Ogre::StringUtil::split(entry[1], "|");
1227 if (entry[0] == "mode")
1228 {
1229 for (auto itor = values.begin(); itor != values.end(); ++itor)
1230 {
1231 std::string value = *itor;
1232 Ogre::StringUtil::trim(value);
1233
1234 if (value == "x-rotation") { animation.mode |= Animation::MODE_ROTATION_X; }
1235 else if (value == "y-rotation") { animation.mode |= Animation::MODE_ROTATION_Y; }
1236 else if (value == "z-rotation") { animation.mode |= Animation::MODE_ROTATION_Z; }
1237 else if (value == "x-offset" ) { animation.mode |= Animation::MODE_OFFSET_X; }
1238 else if (value == "y-offset" ) { animation.mode |= Animation::MODE_OFFSET_Y; }
1239 else if (value == "z-offset" ) { animation.mode |= Animation::MODE_OFFSET_Z; }
1240
1241 else { snprintf(warn_msg, WARN_LEN, "Invalid 'mode': %s, ignoring...", entry[1].c_str()); }
1242 }
1243 }
1244 else if (entry[0] == "event")
1245 {
1246 animation.event_name = entry[1];
1247 Ogre::StringUtil::trim(animation.event_name);
1248 Ogre::StringUtil::toUpperCase(animation.event_name);
1249 }
1250 else if (entry[0] == "link")
1251 {
1252 animation.dash_link_name = entry[1];
1253 Ogre::StringUtil::trim(animation.dash_link_name);
1254 }
1255 else if (entry[0] == "source")
1256 {
1257 for (auto itor = values.begin(); itor != values.end(); ++itor)
1258 {
1259 std::string value = *itor;
1260 Ogre::StringUtil::trim(value);
1261
1262 if (value == "airspeed") { animation.source |= Animation::SOURCE_AIRSPEED; }
1263 else if (value == "vvi") { animation.source |= Animation::SOURCE_VERTICAL_VELOCITY; }
1264 else if (value == "altimeter100k") { animation.source |= Animation::SOURCE_ALTIMETER_100K; }
1265 else if (value == "altimeter10k") { animation.source |= Animation::SOURCE_ALTIMETER_10K; }
1266 else if (value == "altimeter1k") { animation.source |= Animation::SOURCE_ALTIMETER_1K; }
1267 else if (value == "aoa") { animation.source |= Animation::SOURCE_ANGLE_OF_ATTACK; }
1268 else if (value == "flap") { animation.source |= Animation::SOURCE_FLAP; }
1269 else if (value == "airbrake") { animation.source |= Animation::SOURCE_AIR_BRAKE; }
1270 else if (value == "roll") { animation.source |= Animation::SOURCE_ROLL; }
1271 else if (value == "pitch") { animation.source |= Animation::SOURCE_PITCH; }
1272 else if (value == "brakes") { animation.source |= Animation::SOURCE_BRAKES; }
1273 else if (value == "accel") { animation.source |= Animation::SOURCE_ACCEL; }
1274 else if (value == "clutch") { animation.source |= Animation::SOURCE_CLUTCH; }
1275 else if (value == "speedo") { animation.source |= Animation::SOURCE_SPEEDO; }
1276 else if (value == "tacho") { animation.source |= Animation::SOURCE_TACHO; }
1277 else if (value == "turbo") { animation.source |= Animation::SOURCE_TURBO; }
1278 else if (value == "parking") { animation.source |= Animation::SOURCE_PARKING; }
1279 else if (value == "shifterman1") { animation.source |= Animation::SOURCE_SHIFT_LEFT_RIGHT; }
1280 else if (value == "shifterman2") { animation.source |= Animation::SOURCE_SHIFT_BACK_FORTH; }
1281 else if (value == "sequential") { animation.source |= Animation::SOURCE_SEQUENTIAL_SHIFT; }
1282 else if (value == "shifterlin") { animation.source |= Animation::SOURCE_SHIFTERLIN; }
1283 else if (value == "autoshifterlin"){ animation.source |= Animation::SOURCE_AUTOSHIFTERLIN; }
1284 else if (value == "torque") { animation.source |= Animation::SOURCE_TORQUE; }
1285 else if (value == "heading") { animation.source |= Animation::SOURCE_HEADING; }
1286 else if (value == "difflock") { animation.source |= Animation::SOURCE_DIFFLOCK; }
1287 else if (value == "rudderboat") { animation.source |= Animation::SOURCE_BOAT_RUDDER; }
1288 else if (value == "throttleboat") { animation.source |= Animation::SOURCE_BOAT_THROTTLE; }
1289 else if (value == "steeringwheel") { animation.source |= Animation::SOURCE_STEERING_WHEEL; }
1290 else if (value == "aileron") { animation.source |= Animation::SOURCE_AILERON; }
1291 else if (value == "elevator") { animation.source |= Animation::SOURCE_ELEVATOR; }
1292 else if (value == "rudderair") { animation.source |= Animation::SOURCE_AIR_RUDDER; }
1293 else if (value == "permanent") { animation.source |= Animation::SOURCE_PERMANENT; }
1294 else if (value == "event") { animation.source |= Animation::SOURCE_EVENT; }
1295 else if (value == "dashboard") { animation.source |= Animation::SOURCE_DASHBOARD; }
1296 else if (value == "signalstalk") { animation.source |= Animation::SOURCE_SIGNALSTALK; }
1297 else if (value == "gearreverse") { animation.source |= Animation::SOURCE_GEAR_REVERSE; }
1298 else if (value == "gearneutral") { animation.source |= Animation::SOURCE_GEAR_NEUTRAL; }
1299 else if (value == "ignition") { animation.source |= Animation::SOURCE_IGNITION; }
1300
1301 else
1302 {
1303 Animation::MotorSource motor_source;
1304 // aeroengines...
1305 if (entry[1].compare(0, 8, "throttle") == 0)
1306 {
1308 motor_source.motor = this->ParseArgUint(entry[1].substr(8));
1309 }
1310 else if (entry[1].compare(0, 3, "rpm") == 0)
1311 {
1313 motor_source.motor = this->ParseArgUint(entry[1].substr(3));
1314 }
1315 else if (entry[1].compare(0, 8, "aerotorq") == 0)
1316 {
1318 motor_source.motor = this->ParseArgUint(entry[1].substr(8));
1319 }
1320 else if (entry[1].compare(0, 7, "aeropit") == 0)
1321 {
1323 motor_source.motor = this->ParseArgUint(entry[1].substr(7));
1324 }
1325 else if (entry[1].compare(0, 10, "aerostatus") == 0)
1326 {
1328 motor_source.motor = this->ParseArgUint(entry[1].substr(10));
1329 }
1330 // gears... (hack)
1331 else if (entry[1].compare(0, 4, "gear") == 0)
1332 {
1334 motor_source.motor = this->ParseArgUint(entry[1].substr(4));
1335 }
1336 else
1337 {
1338 snprintf(warn_msg, WARN_LEN, "Invalid 'source': %s, ignoring...", entry[1].c_str());
1339 continue;
1340 }
1341 animation.motor_sources.push_back(motor_source);
1342 }
1343 }
1344 }
1345 else
1346 {
1347 snprintf(warn_msg, WARN_LEN, "Invalid keyword: %s, ignoring...", entry[0].c_str());
1348 }
1349 }
1350 else
1351 {
1352 snprintf(warn_msg, WARN_LEN, "Invalid item: %s, ignoring...", entry[0].c_str());
1353 }
1354
1355 if (warn_msg[0] != '\0')
1356 {
1358 fmt::format("Ignoring invalid token '{}' ({})", itor->c_str(), warn_msg));
1359 }
1360 }
1361
1362 m_current_module->props.back().animations.push_back(animation);
1363}
1364
1366{
1367 AntiLockBrakes alb;
1368 Ogre::StringVector tokens = Ogre::StringUtil::split(m_current_line + 15, ","); // "AntiLockBrakes " = 15 characters
1369 m_num_args = (int)tokens.size();
1370 if (! this->CheckNumArguments(2)) { return; }
1371
1372 alb.regulation_force = this->ParseArgFloat(tokens[0].c_str());
1373 alb.min_speed = this->ParseArgInt (tokens[1].c_str());
1374
1375 if (tokens.size() > 3) { alb.pulse_per_sec = this->ParseArgFloat(tokens[2].c_str()); }
1376
1377 for (unsigned int i=3; i<tokens.size(); i++)
1378 {
1379 Ogre::StringVector args2 = Ogre::StringUtil::split(tokens[i], ":");
1380 Ogre::StringUtil::trim(args2[0]);
1381 Ogre::StringUtil::toLowerCase(args2[0]);
1382 if (args2[0] == "mode" && args2.size() == 2)
1383 {
1384 Ogre::StringVector attrs = Ogre::StringUtil::split(args2[1], "&");
1385 auto itor = attrs.begin();
1386 auto endi = attrs.end();
1387 for (; itor != endi; ++itor)
1388 {
1389 std::string attr = *itor;
1390 Ogre::StringUtil::trim(attr);
1391 Ogre::StringUtil::toLowerCase(attr);
1392 if (strncmp(attr.c_str(), "nodash", 6) == 0) { alb.attr_no_dashboard = true; }
1393 else if (strncmp(attr.c_str(), "notoggle", 8) == 0) { alb.attr_no_toggle = true; }
1394 else if (strncmp(attr.c_str(), "on", 2) == 0) { alb.attr_is_on = true; }
1395 else if (strncmp(attr.c_str(), "off", 3) == 0) { alb.attr_is_on = false; }
1396 }
1397 }
1398 else
1399 {
1400 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "missing mode");
1401 alb.attr_no_dashboard = false;
1402 alb.attr_no_toggle = false;
1403 alb.attr_is_on = true;
1404 }
1405 }
1406
1407 m_current_module->antilockbrakes.push_back(alb);
1409}
1410
1412{
1413 if (! this->CheckNumArguments(1)) { return; }
1414
1415 Engoption engoption;
1416 engoption.inertia = this->GetArgFloat(0);
1417
1418 if (m_num_args > 1) { engoption.type = this->GetArgEngineType(1); }
1419 if (m_num_args > 2) { engoption.clutch_force = this->GetArgFloat(2); }
1420 if (m_num_args > 3) { engoption.shift_time = this->GetArgFloat(3); }
1421 if (m_num_args > 4) { engoption.clutch_time = this->GetArgFloat(4); }
1422 if (m_num_args > 5) { engoption.post_shift_time = this->GetArgFloat(5); }
1423 if (m_num_args > 6) { engoption.stall_rpm = this->GetArgFloat(6); }
1424 if (m_num_args > 7) { engoption.idle_rpm = this->GetArgFloat(7); }
1425 if (m_num_args > 8) { engoption.max_idle_mixture = this->GetArgFloat(8); }
1426 if (m_num_args > 9) { engoption.min_idle_mixture = this->GetArgFloat(9); }
1427 if (m_num_args > 10){ engoption.braking_torque = this->GetArgFloat(10);}
1428
1429 m_current_module->engoption.push_back(engoption);
1431}
1432
1434{
1435 if (! this->CheckNumArguments(4)) { return; }
1436
1437 Engturbo engturbo;
1438 engturbo.version = this->GetArgInt ( 0);
1439 engturbo.tinertiaFactor = this->GetArgFloat( 1);
1440 engturbo.nturbos = this->GetArgInt ( 2);
1441 engturbo.param1 = this->GetArgFloat( 3);
1442
1443 if (m_num_args > 4) { engturbo.param2 = this->GetArgFloat( 4); }
1444 if (m_num_args > 5) { engturbo.param3 = this->GetArgFloat( 5); }
1445 if (m_num_args > 6) { engturbo.param4 = this->GetArgFloat( 6); }
1446 if (m_num_args > 7) { engturbo.param5 = this->GetArgFloat( 7); }
1447 if (m_num_args > 8) { engturbo.param6 = this->GetArgFloat( 8); }
1448 if (m_num_args > 9) { engturbo.param7 = this->GetArgFloat( 9); }
1449 if (m_num_args > 10) { engturbo.param8 = this->GetArgFloat(10); }
1450 if (m_num_args > 11) { engturbo.param9 = this->GetArgFloat(11); }
1451 if (m_num_args > 12) { engturbo.param10 = this->GetArgFloat(12); }
1452 if (m_num_args > 13) { engturbo.param11 = this->GetArgFloat(13); }
1453
1454 if (engturbo.nturbos > 4)
1455 {
1456 this->LogMessage(Console::CONSOLE_SYSTEM_WARNING, "You cannot have more than 4 turbos. Fallback: using 4 instead.");
1457 engturbo.nturbos = 4;
1458 }
1459
1460 m_current_module->engturbo.push_back(engturbo);
1462}
1463
1465{
1466 if (! this->CheckNumArguments(6)) { return; }
1467
1468 Engine engine;
1469 engine.shift_down_rpm = this->GetArgFloat(0);
1470 engine.shift_up_rpm = this->GetArgFloat(1);
1471 engine.torque = this->GetArgFloat(2);
1472 engine.global_gear_ratio = this->GetArgFloat(3);
1473 engine.reverse_gear_ratio = this->GetArgFloat(4);
1474 engine.neutral_gear_ratio = this->GetArgFloat(5);
1475
1476 // Forward gears
1477 for (int i = 6; i < m_num_args; i++)
1478 {
1479 float ratio = this->GetArgFloat(i);
1480 if (ratio < 0.f)
1481 {
1482 break; // Optional terminator argument
1483 }
1484 engine.gear_ratios.push_back(ratio);
1485 }
1486
1487 if (engine.gear_ratios.size() == 0)
1488 {
1489 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "no forward gear");
1490 return;
1491 }
1492
1493 m_current_module->engine.push_back(engine);
1495}
1496
1498{
1499 if (! this->CheckNumArguments(1)) { return; }
1500
1501 m_current_module->contacters.push_back(this->GetArgNodeRef(0));
1503}
1504
1506{
1507 if (!this->CheckNumArguments(2)) { return; }
1508
1509 CustomDashboardInput dashVal;
1510 dashVal.name = this->GetArgStr(0);
1511 dashVal.data_type = this->GetArgDashboardInputDataType(1);
1512 if (dashVal.data_type == -1)
1513 {
1515 fmt::format("Dashboard value \"{}\" has an invalid data type. Ignoring it.", dashVal.name));
1516 return;
1517 }
1518
1519 m_current_module->customdashboardinputs.push_back(dashVal);
1521}
1522
1524{
1525 const bool is_commands2 = (m_current_block == Keyword::COMMANDS2);
1526 const int max_args = (is_commands2 ? 8 : 7);
1527 if (! this->CheckNumArguments(max_args)) { return; }
1528
1529 Command2 command2;
1533
1534 int pos = 0;
1535 command2.nodes[0] = this->GetArgNodeRef(pos++);
1536 command2.nodes[1] = this->GetArgNodeRef(pos++);
1537 command2.shorten_rate = this->GetArgFloat (pos++);
1538
1539 if (is_commands2)
1540 {
1541 command2.lengthen_rate = this->GetArgFloat(pos++);
1542 }
1543 else
1544 {
1545 command2.lengthen_rate = command2.shorten_rate;
1546 }
1547
1548 command2.max_contraction = this->GetArgFloat(pos++);
1549 command2.max_extension = this->GetArgFloat(pos++);
1550 command2.contract_key = this->GetArgInt (pos++);
1551 command2.extend_key = this->GetArgInt (pos++);
1552
1553 if (m_num_args <= max_args) // No more args?
1554 {
1555 m_current_module->commands2.push_back(command2);
1556 return;
1557 }
1558
1559 // Parse options
1560 std::string options_str = this->GetArgStr(pos++);
1561 char winner = 0;
1562 for (auto itor = options_str.begin(); itor != options_str.end(); ++itor)
1563 {
1564 const char c = *itor;
1565 if ((winner == 0) && (c == 'o' || c == 'p' || c == 'c')) { winner = c; }
1566
1567 if (c == 'n') {} // Filler, does nothing
1568 else if (c == 'i') { command2.option_i_invisible = true; }
1569 else if (c == 'r') { command2.option_r_rope = true; }
1570 else if (c == 'f') { command2.option_f_not_faster = true; }
1571 else if (c == 'c') { command2.option_c_auto_center = true; }
1572 else if (c == 'p') { command2.option_p_1press = true; }
1573 else if (c == 'o') { command2.option_o_1press_center = true; }
1574 else
1575 {
1577 fmt::format("ignoring unknown flag '{}'", c));
1578 }
1579 }
1580
1581 // Resolve option conflicts
1582 if (command2.option_c_auto_center && winner != 'c' && winner != 0)
1583 {
1584 this->LogMessage(Console::CONSOLE_SYSTEM_WARNING, "Command cannot be one-pressed and self centering at the same time, ignoring flag 'c'");
1585 command2.option_c_auto_center = false;
1586 }
1587 char ignored = '\0';
1588 if (command2.option_o_1press_center && winner != 'o' && winner != 0)
1589 {
1590 command2.option_o_1press_center = false;
1591 ignored = 'o';
1592 }
1593 else if (command2.option_p_1press && winner != 'p' && winner != 0)
1594 {
1595 command2.option_p_1press = false;
1596 ignored = 'p';
1597 }
1598
1599 // Report conflicts
1600 if (ignored != 0 && winner == 'c')
1601 {
1603 "Command cannot be one-pressed and self centering at the same time, ignoring flag '%c'");
1604 }
1605 else if (ignored != 0 && (winner == 'o' || winner == 'p'))
1606 {
1608 "Command already has a one-pressed c.mode, ignoring flag '%c'");
1609 }
1610
1611 if (m_num_args > pos) { command2.description = this->GetArgStr (pos++);}
1612
1613 if (m_num_args > pos) { ParseOptionalInertia(command2.inertia, pos); pos += 4; }
1614
1615 if (m_num_args > pos) { command2.affect_engine = this->GetArgFloat(pos++);}
1616 if (m_num_args > pos) { command2.needs_engine = this->GetArgBool (pos++);}
1617 if (m_num_args > pos) { command2.plays_sound = this->GetArgBool (pos++);}
1618
1619 m_current_module->commands2.push_back(command2);
1621}
1622
1624{
1625 CollisionBox collisionbox;
1626
1627 Ogre::StringVector tokens = Ogre::StringUtil::split(m_current_line, ",");
1628 Ogre::StringVector::iterator iter = tokens.begin();
1629 for ( ; iter != tokens.end(); iter++)
1630 {
1631 collisionbox.nodes.push_back( this->_ParseNodeRef(*iter) );
1632 }
1633
1634 m_current_module->collisionboxes.push_back(collisionbox);
1636}
1637
1639{
1640 if (! this->CheckNumArguments(11)) { return; }
1641
1642 Cinecam cinecam;
1645
1646 // Required arguments
1647 cinecam.position.x = this->GetArgFloat ( 0);
1648 cinecam.position.y = this->GetArgFloat ( 1);
1649 cinecam.position.z = this->GetArgFloat ( 2);
1650 cinecam.nodes[0] = this->GetArgNodeRef( 3);
1651 cinecam.nodes[1] = this->GetArgNodeRef( 4);
1652 cinecam.nodes[2] = this->GetArgNodeRef( 5);
1653 cinecam.nodes[3] = this->GetArgNodeRef( 6);
1654 cinecam.nodes[4] = this->GetArgNodeRef( 7);
1655 cinecam.nodes[5] = this->GetArgNodeRef( 8);
1656 cinecam.nodes[6] = this->GetArgNodeRef( 9);
1657 cinecam.nodes[7] = this->GetArgNodeRef(10);
1658
1659 // Optional arguments
1660 if (m_num_args > 11) { cinecam.spring = this->GetArgFloat(11); }
1661 if (m_num_args > 12) { cinecam.damping = this->GetArgFloat(12); }
1662
1663 if (m_num_args > 13)
1664 {
1665 float value = this->GetArgFloat(13);
1666 if (value > 0.f) // Invalid input (for example illegal trailing ";pseudo-comment") parses as 0
1667 cinecam.node_mass = value;
1668 }
1669
1671 {
1673 }
1674
1675 m_current_module->cinecam.push_back(cinecam);
1677}
1678
1680{
1681 m_current_camera_rail->nodes.push_back( this->GetArgNodeRef(0) );
1682}
1683
1685{
1686 if (!this->CheckNumArguments(1)) { return; }
1687
1688 Brakes brakes;
1689 brakes.default_braking_force = this->GetArgFloat(0);
1690 if (m_num_args > 1)
1691 {
1692 brakes.parking_brake_force = this->GetArgFloat(1);
1693 }
1694 m_current_module->brakes.push_back(brakes);
1696}
1697
1699{
1700 Axle axle;
1701
1702 Ogre::StringVector tokens = Ogre::StringUtil::split(m_current_line, ",");
1703 Ogre::StringVector::iterator iter = tokens.begin();
1704 for ( ; iter != tokens.end(); iter++)
1705 {
1706 std::smatch results;
1707 if (! std::regex_search(*iter, results, Regexes::SECTION_AXLES_PROPERTY))
1708 {
1709 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "Invalid property, ignoring whole line...");
1710 return;
1711 }
1712 // NOTE: Positions in 'results' array match E_CAPTURE*() positions (starting with 1) in the respective regex.
1713
1714 if (results[1].matched)
1715 {
1716 unsigned int wheel_index = PARSEINT(results[2]) - 1;
1717 axle.wheels[wheel_index][0] = _ParseNodeRef(results[3]);
1718 axle.wheels[wheel_index][1] = _ParseNodeRef(results[4]);
1719 }
1720 else if (results[5].matched)
1721 {
1722 this->_ParseDifferentialTypes(axle.options, results[6].str());
1723 }
1724 }
1725
1726 m_current_module->axles.push_back(axle);
1728}
1729
1731{
1732 auto args = Ogre::StringUtil::split(m_current_line, ",");
1733 if (args.size() < 2) { return; }
1734
1735 InterAxle interaxle;
1736
1737 interaxle.a1 = this->ParseArgInt(args[0].c_str()) - 1;
1738 interaxle.a2 = this->ParseArgInt(args[1].c_str()) - 1;
1739
1740 std::smatch results;
1741 if (! std::regex_search(args[2], results, Regexes::SECTION_AXLES_PROPERTY))
1742 {
1743 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "Invalid property, ignoring whole line...");
1744 return;
1745 }
1746 // NOTE: Positions in 'results' array match E_CAPTURE*() positions (starting with 1) in the respective regex.
1747
1748 if (results[5].matched)
1749 {
1750 this->_ParseDifferentialTypes(interaxle.options, results[6].str());
1751 }
1752
1753 m_current_module->interaxles.push_back(interaxle);
1755}
1756
1758{
1759 if (! this->CheckNumArguments(14)) { return; }
1760
1761 Airbrake airbrake;
1762 airbrake.reference_node = this->GetArgNodeRef( 0);
1763 airbrake.x_axis_node = this->GetArgNodeRef( 1);
1764 airbrake.y_axis_node = this->GetArgNodeRef( 2);
1765 airbrake.aditional_node = this->GetArgNodeRef( 3);
1766 airbrake.offset.x = this->GetArgFloat ( 4);
1767 airbrake.offset.y = this->GetArgFloat ( 5);
1768 airbrake.offset.z = this->GetArgFloat ( 6);
1769 airbrake.width = this->GetArgFloat ( 7);
1770 airbrake.height = this->GetArgFloat ( 8);
1771 airbrake.max_inclination_angle = this->GetArgFloat ( 9);
1772 airbrake.texcoord_x1 = this->GetArgFloat (10);
1773 airbrake.texcoord_y1 = this->GetArgFloat (11);
1774 airbrake.texcoord_x2 = this->GetArgFloat (12);
1775 airbrake.texcoord_y2 = this->GetArgFloat (13);
1776
1777 m_current_module->airbrakes.push_back(airbrake);
1779}
1780
1782{
1783 if (! this->CheckNumArguments(1)) { return; }
1784
1785 Assetpack assetpack;
1786
1787 assetpack.filename = this->GetArgStr(0);
1788
1789 m_current_module->assetpacks.push_back(assetpack);
1791}
1792
1794{
1795 if (! this->CheckNumArguments(19)) { return; }
1796
1797 VideoCamera videocamera;
1798
1799 videocamera.reference_node = this->GetArgNodeRef ( 0);
1800 videocamera.left_node = this->GetArgNodeRef ( 1);
1801 videocamera.bottom_node = this->GetArgNodeRef ( 2);
1802 videocamera.alt_reference_node = this->GetArgNullableNode( 3);
1803 videocamera.alt_orientation_node = this->GetArgNullableNode( 4);
1804 videocamera.offset.x = this->GetArgFloat ( 5);
1805 videocamera.offset.y = this->GetArgFloat ( 6);
1806 videocamera.offset.z = this->GetArgFloat ( 7);
1807 videocamera.rotation.x = this->GetArgFloat ( 8);
1808 videocamera.rotation.y = this->GetArgFloat ( 9);
1809 videocamera.rotation.z = this->GetArgFloat (10);
1810 videocamera.field_of_view = this->GetArgFloat (11);
1811 videocamera.texture_width = this->GetArgInt (12);
1812 videocamera.texture_height = this->GetArgInt (13);
1813 videocamera.min_clip_distance = this->GetArgFloat (14);
1814 videocamera.max_clip_distance = this->GetArgFloat (15);
1815 videocamera.camera_role = this->GetArgVideoCamRole(16);
1816 videocamera.camera_mode = this->GetArgInt (17);
1817 videocamera.material_name = this->GetArgStr (18);
1818
1819 if (m_num_args > 19) { videocamera.camera_name = this->GetArgStr(19); }
1820
1821 m_current_module->videocameras.push_back(videocamera);
1823}
1824
1826{
1827 if (! this->CheckNumArguments(3)) { return; }
1828
1829 Camera camera;
1830 camera.center_node = this->GetArgNodeRef(0);
1831 camera.back_node = this->GetArgNodeRef(1);
1832 camera.left_node = this->GetArgNodeRef(2);
1833
1834 m_current_module->cameras.push_back(camera);
1836}
1837
1839{
1840 bool is_turboprop_2 = m_current_block == Keyword::TURBOPROPS2;
1841
1842 if (! this->CheckNumArguments(is_turboprop_2 ? 9 : 8)) { return; }
1843
1844 Turboprop2 turboprop;
1845
1846 turboprop.reference_node = this->GetArgNodeRef(0);
1847 turboprop.axis_node = this->GetArgNodeRef(1);
1848 turboprop.blade_tip_nodes[0] = this->GetArgNodeRef(2);
1849 turboprop.blade_tip_nodes[1] = this->GetArgNodeRef(3);
1850 turboprop.blade_tip_nodes[2] = this->GetArgNullableNode(4);
1851 turboprop.blade_tip_nodes[3] = this->GetArgNullableNode(5);
1852
1853 int offset = 0;
1854
1855 if (is_turboprop_2)
1856 {
1857 turboprop.couple_node = this->GetArgNullableNode(6);
1858
1859 offset = 1;
1860 }
1861
1862 turboprop.turbine_power_kW = this->GetArgFloat (6 + offset);
1863 turboprop.airfoil = this->GetArgStr (7 + offset);
1864
1865 m_current_module->turboprops2.push_back(turboprop);
1867}
1868
1870{
1871 if (! this->CheckNumArguments(9)) { return; }
1872
1873 Turbojet turbojet;
1874 turbojet.front_node = this->GetArgNodeRef(0);
1875 turbojet.back_node = this->GetArgNodeRef(1);
1876 turbojet.side_node = this->GetArgNodeRef(2);
1877 turbojet.is_reversable = this->GetArgInt (3);
1878 turbojet.dry_thrust = this->GetArgFloat (4);
1879 turbojet.wet_thrust = this->GetArgFloat (5);
1880 turbojet.front_diameter = this->GetArgFloat (6);
1881 turbojet.back_diameter = this->GetArgFloat (7);
1882 turbojet.nozzle_length = this->GetArgFloat (8);
1883
1884 m_current_module->turbojets.push_back(turbojet);
1886}
1887
1889{
1890 if (! this->CheckNumArguments(6)) { return; }
1891
1892 Trigger trigger;
1895 trigger.nodes[0] = this->GetArgNodeRef(0);
1896 trigger.nodes[1] = this->GetArgNodeRef(1);
1897 trigger.contraction_trigger_limit = this->GetArgFloat (2);
1898 trigger.expansion_trigger_limit = this->GetArgFloat (3);
1899 trigger.shortbound_trigger_action = this->GetArgInt (4);
1900 trigger.longbound_trigger_action = this->GetArgInt (5);
1901 if (m_num_args > 6) trigger.options = this->GetArgTriggerOptions(6);
1902 if (m_num_args > 7) trigger.boundary_timer = this->GetArgFloat(7);
1903
1904 m_current_module->triggers.push_back(trigger);
1906}
1907
1909{
1910 if (m_current_module->torquecurve.size() == 0)
1911 {
1912 m_current_module->torquecurve.push_back(TorqueCurve());
1913 }
1914
1915 Ogre::StringVector args = Ogre::StringUtil::split(m_current_line, ",");
1916
1917 if (args.size() == 1u)
1918 {
1919 m_current_module->torquecurve[0].predefined_func_name = args[0];
1920 }
1921 else if (args.size() == 2u)
1922 {
1923 TorqueCurve::Sample sample;
1924 sample.power = this->ParseArgFloat(args[0].c_str());
1925 sample.torque_percent = this->ParseArgFloat(args[1].c_str());
1926 m_current_module->torquecurve[0].samples.push_back(sample);
1927 }
1928 else
1929 {
1930 // Consistent with 0.38's parser.
1931 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "too many arguments, skipping");
1932 }
1933}
1934
1936{
1937 if (! this->CheckNumArguments(5)) { return; }
1938
1939 Tie tie;
1942
1943 tie.root_node = this->GetArgNodeRef(0);
1944 tie.max_reach_length = this->GetArgFloat (1);
1945 tie.auto_shorten_rate = this->GetArgFloat (2);
1946 tie.min_length = this->GetArgFloat (3);
1947 tie.max_length = this->GetArgFloat (4);
1948
1949 if (m_num_args > 5)
1950 {
1951 for (char c: this->GetArgStr(5))
1952 {
1953 switch (c)
1954 {
1955 case (char)TieOption::n_DUMMY:
1956 case (char)TieOption::v_DUMMY:
1957 break;
1958
1959 case (char)TieOption::i_INVISIBLE:
1961 break;
1962
1963 case (char)TieOption::s_NO_SELF_LOCK:
1965 break;
1966
1967 default:
1969 fmt::format("ignoring invalid option '{}'", c));
1970 break;
1971 }
1972 }
1973 }
1974
1975 if (m_num_args > 6) { tie.max_stress = this->GetArgFloat (6); }
1976 if (m_num_args > 7) { tie.group = this->GetArgInt (7); }
1977
1978 m_current_module->ties.push_back(tie);
1980}
1981
1983{
1984 if (! this->CheckNumArguments(2)) { return; }
1985
1986 SoundSource soundsource;
1987 soundsource.node = this->GetArgNodeRef(0);
1988 soundsource.sound_script_name = this->GetArgStr(1);
1989
1990 m_current_module->soundsources.push_back(soundsource);
1992}
1993
1995{
1996 if (! this->CheckNumArguments(3)) { return; }
1997
1998 SoundSource2 soundsource2;
1999 soundsource2.node = this->GetArgNodeRef(0);
2000 soundsource2.mode = this->GetArgInt(1);
2001 soundsource2.sound_script_name = this->GetArgStr(2);
2002
2003 if (soundsource2.mode < -2)
2004 {
2006 fmt::format("invalid mode {}, falling back to default -2", soundsource2.mode));
2007 soundsource2.mode = -2;
2008 }
2009
2010 m_current_module->soundsources2.push_back(soundsource2);
2012}
2013
2015{
2016 Ogre::StringVector args = Ogre::StringUtil::split(m_current_line, ", ");
2017 m_num_args = (int)args.size();
2018 if (! this->CheckNumArguments(2)) { return; }
2019
2020 SlideNode slidenode;
2021 slidenode.slide_node = this->_ParseNodeRef(args[0]);
2022
2023 bool in_rail_node_list = true;
2024
2025 for (auto itor = args.begin() + 1; itor != args.end(); ++itor)
2026 {
2027 char c = toupper(itor->at(0));
2028 switch (c)
2029 {
2030 case 'S':
2031 slidenode.spring_rate = this->ParseArgFloat(itor->substr(1));
2032 slidenode._spring_rate_set = true;
2033 in_rail_node_list = false;
2034 break;
2035 case 'B':
2036 slidenode.break_force = this->ParseArgFloat(itor->substr(1));
2037 slidenode._break_force_set = true;
2038 in_rail_node_list = false;
2039 break;
2040 case 'T':
2041 slidenode.tolerance = this->ParseArgFloat(itor->substr(1));
2042 slidenode._tolerance_set = true;
2043 in_rail_node_list = false;
2044 break;
2045 case 'R':
2046 slidenode.attachment_rate = this->ParseArgFloat(itor->substr(1));
2047 slidenode._attachment_rate_set = true;
2048 in_rail_node_list = false;
2049 break;
2050 case 'G':
2051 slidenode.railgroup_id = this->ParseArgFloat(itor->substr(1));
2052 slidenode._railgroup_id_set = true;
2053 in_rail_node_list = false;
2054 break;
2055 case 'D':
2056 slidenode.max_attach_dist = this->ParseArgFloat(itor->substr(1));
2057 slidenode._max_attach_dist_set = true;
2058 in_rail_node_list = false;
2059 break;
2060 case 'C':
2061 switch (itor->at(1))
2062 {
2063 case 'a':
2065 break;
2066 case 'f':
2068 break;
2069 case 's':
2071 break;
2072 case 'n':
2074 break;
2075 default:
2077 fmt::format("Ignoring invalid option '{}'", itor->at(1)));
2078 break;
2079 }
2080 in_rail_node_list = false;
2081 break;
2082 default:
2083 if (in_rail_node_list)
2084 slidenode.rail_node_ranges.push_back( _ParseNodeRef(*itor));
2085 break;
2086 }
2087 }
2088
2089 m_current_module->slidenodes.push_back(slidenode);
2091}
2092
2094{
2095 if (! this->CheckNumArguments(15)) { return; }
2096
2097 Shock3 shock_3;
2100
2101 shock_3.nodes[0] = this->GetArgNodeRef( 0);
2102 shock_3.nodes[1] = this->GetArgNodeRef( 1);
2103 shock_3.spring_in = this->GetArgFloat ( 2);
2104 shock_3.damp_in = this->GetArgFloat ( 3);
2105 shock_3.damp_in_slow = this->GetArgFloat ( 4);
2106 shock_3.split_vel_in = this->GetArgFloat ( 5);
2107 shock_3.damp_in_fast = this->GetArgFloat ( 6);
2108 shock_3.spring_out = this->GetArgFloat ( 7);
2109 shock_3.damp_out = this->GetArgFloat ( 8);
2110 shock_3.damp_out_slow = this->GetArgFloat ( 9);
2111 shock_3.split_vel_out = this->GetArgFloat (10);
2112 shock_3.damp_out_fast = this->GetArgFloat (11);
2113 shock_3.short_bound = this->GetArgFloat (12);
2114 shock_3.long_bound = this->GetArgFloat (13);
2115 shock_3.precompression = this->GetArgFloat (14);
2116
2117 if (m_num_args > 15) shock_3.options = this->GetArgShock3Options(15);
2118
2119 m_current_module->shocks3.push_back(shock_3);
2121}
2122
2124{
2125 if (! this->CheckNumArguments(13)) { return; }
2126
2127 Shock2 shock_2;
2130
2131 shock_2.nodes[0] = this->GetArgNodeRef( 0);
2132 shock_2.nodes[1] = this->GetArgNodeRef( 1);
2133 shock_2.spring_in = this->GetArgFloat ( 2);
2134 shock_2.damp_in = this->GetArgFloat ( 3);
2135 shock_2.progress_factor_spring_in = this->GetArgFloat ( 4);
2136 shock_2.progress_factor_damp_in = this->GetArgFloat ( 5);
2137 shock_2.spring_out = this->GetArgFloat ( 6);
2138 shock_2.damp_out = this->GetArgFloat ( 7);
2139 shock_2.progress_factor_spring_out = this->GetArgFloat ( 8);
2140 shock_2.progress_factor_damp_out = this->GetArgFloat ( 9);
2141 shock_2.short_bound = this->GetArgFloat (10);
2142 shock_2.long_bound = this->GetArgFloat (11);
2143 shock_2.precompression = this->GetArgFloat (12);
2144
2145 if (m_num_args > 13) shock_2.options = this->GetArgShock2Options(13);
2146
2147 m_current_module->shocks2.push_back(shock_2);
2149}
2150
2152{
2153 if (! this->CheckNumArguments(7)) { return; }
2154
2155 Shock shock;
2158
2159 shock.nodes[0] = this->GetArgNodeRef(0);
2160 shock.nodes[1] = this->GetArgNodeRef(1);
2161 shock.spring_rate = this->GetArgFloat (2);
2162 shock.damping = this->GetArgFloat (3);
2163 shock.short_bound = this->GetArgFloat (4);
2164 shock.long_bound = this->GetArgFloat (5);
2165 shock.precompression = this->GetArgFloat (6);
2166 if (m_num_args > 7) shock.options = this->GetArgShockOptions(7);
2167
2168 m_current_module->shocks.push_back(shock);
2170}
2171
2172Node::Ref Parser::_ParseNodeRef(std::string const & node_id_str)
2173{
2175 {
2176 // Import of legacy fileformatversion
2177 int node_id_num = PARSEINT(node_id_str);
2178 if (node_id_num < 0)
2179 {
2180 node_id_num *= -1;
2181 }
2182 // Since fileformatversion is not known from the beginning of parsing, 2 states must be kept
2183 // at the same time: IMPORT_STATE and REGULAR_STATE. The outer logic must make the right pick.
2184 unsigned int flags = Node::Ref::IMPORT_STATE_IS_VALID | // Import state
2185 Node::Ref::REGULAR_STATE_IS_VALID | Node::Ref::REGULAR_STATE_IS_NAMED; // Regular state (fileformatversion >= 450)
2187 {
2188 flags |= Node::Ref::IMPORT_STATE_MUST_CHECK_NAMED_FIRST;
2189 }
2190 return Node::Ref(node_id_str, node_id_num, flags, m_current_line_number);
2191 }
2192 else
2193 {
2194 // fileformatversion >= 450, use named-only nodes
2195 return Node::Ref(node_id_str, 0, Node::Ref::REGULAR_STATE_IS_VALID | Node::Ref::REGULAR_STATE_IS_NAMED, m_current_line_number);
2196 }
2197}
2198
2200{
2201 if (! this->CheckNumArguments(2)) { return; } // Directive name + parameter
2202
2203 m_set_default_minimass = std::shared_ptr<DefaultMinimass>(new DefaultMinimass());
2204 m_set_default_minimass->min_mass_Kg = this->GetArgFloat(1);
2205}
2206
2208{
2209 // ;flare_groups_no_import
2210 // ; <flaretype> <controlnumber 1-10>
2211 if (!this->CheckNumArguments(1)) { return; }
2212
2214 fni.type = this->GetArgFlareType(0);
2215 if (m_num_args > 1)
2216 {
2217 fni.control_number = this->GetArgInt(1);
2218 if (fni.control_number < 1)
2219 {
2220 this->LogMessage(Console::CONSOLE_SYSTEM_WARNING, fmt::format("flaregroup_no_import: parameter <control_number> must be 1-10; got {}, clamping to 1", fni.control_number));
2221 }
2222 if (fni.control_number > 10)
2223 {
2224 this->LogMessage(Console::CONSOLE_SYSTEM_WARNING, fmt::format("flaregroup_no_import: parameter <control_number> must be 1-10; got {}, clamping to 10", fni.control_number));
2225 }
2226 }
2227
2228 LOG(fmt::format("[RoR|RigDef::Parser] parsed FlaregroupNoImport ({} {})", (char)fni.type, fni.control_number));
2229 m_current_module->flaregroups_no_import.push_back(fni);
2230}
2231
2233{
2234 if (! this->CheckNumArguments(2)) { return; }
2235
2236 float start_delay = this->GetArgFloat(1);
2237 float stop_delay = 0;
2238 if (m_num_args > 2) { stop_delay = this->GetArgFloat(2); }
2239
2240 if (start_delay < 0 || stop_delay < 0)
2241 {
2242 m_user_default_inertia = m_ror_default_inertia; // Reset and return
2243 return;
2244 }
2245
2246 // Create
2247 Inertia* i = new Inertia(*m_user_default_inertia.get());
2248 i->start_delay_factor = start_delay;
2249 i->stop_delay_factor = stop_delay;
2250
2251 if (m_num_args > 3) { i->start_function = this->GetArgStr(3); }
2252 if (m_num_args > 4) { i->stop_function = this->GetArgStr(4); }
2253
2254 m_user_default_inertia = std::shared_ptr<Inertia>(i);
2255}
2256
2258{
2259 if (! this->CheckNumArguments(4)) { return; }
2260
2261 Screwprop screwprop;
2262
2263 screwprop.prop_node = this->GetArgNodeRef(0);
2264 screwprop.back_node = this->GetArgNodeRef(1);
2265 screwprop.top_node = this->GetArgNodeRef(2);
2266 screwprop.power = this->GetArgFloat (3);
2267
2268 m_current_module->screwprops.push_back(screwprop);
2270}
2271
2273{
2274 if (!this->CheckNumArguments(1)) { return; }
2275
2276 Script script;
2277
2278 script.filename = this->GetArgStr(0);
2279
2280 m_current_module->scripts.push_back(script);
2282}
2283
2285{
2286 if (! this->CheckNumArguments(13)) { return; }
2287
2288 Rotator2 rotator;
2290
2291 rotator.axis_nodes[0] = this->GetArgNodeRef( 0);
2292 rotator.axis_nodes[1] = this->GetArgNodeRef( 1);
2293 rotator.base_plate_nodes[0] = this->GetArgNodeRef( 2);
2294 rotator.base_plate_nodes[1] = this->GetArgNodeRef( 3);
2295 rotator.base_plate_nodes[2] = this->GetArgNodeRef( 4);
2296 rotator.base_plate_nodes[3] = this->GetArgNodeRef( 5);
2297 rotator.rotating_plate_nodes[0] = this->GetArgNodeRef( 6);
2298 rotator.rotating_plate_nodes[1] = this->GetArgNodeRef( 7);
2299 rotator.rotating_plate_nodes[2] = this->GetArgNodeRef( 8);
2300 rotator.rotating_plate_nodes[3] = this->GetArgNodeRef( 9);
2301 rotator.rate = this->GetArgFloat (10);
2302 rotator.spin_left_key = this->GetArgInt (11);
2303 rotator.spin_right_key = this->GetArgInt (12);
2304
2305 int offset = 0;
2306
2308 {
2309 if (! this->CheckNumArguments(16)) { return; }
2310 if (m_num_args > 13) { rotator.rotating_force = this->GetArgFloat(13); }
2311 if (m_num_args > 14) { rotator.tolerance = this->GetArgFloat(14); }
2312 if (m_num_args > 15) { rotator.description = this->GetArgStr (15); }
2313
2314 offset = 3;
2315 }
2316
2317 this->ParseOptionalInertia(rotator.inertia, 13 + offset);
2318 if (m_num_args > 17 + offset) { rotator.engine_coupling = this->GetArgFloat(17 + offset); }
2319 if (m_num_args > 18 + offset) { rotator.needs_engine = this->GetArgBool (18 + offset); }
2320
2322 {
2323 m_current_module->rotators2.push_back(rotator);
2325 }
2326 else
2327 {
2328 m_current_module->rotators.push_back(rotator);
2330 }
2331}
2332
2334{
2335 if (! this->CheckNumArguments(2)) { return; }
2336
2337 Fileinfo fileinfo;
2338
2339 fileinfo.unique_id = this->GetArgStr(1);
2340 Ogre::StringUtil::trim(fileinfo.unique_id);
2341
2342 if (m_num_args > 2) { fileinfo.category_id = this->GetArgInt(2); }
2343 if (m_num_args > 3) { fileinfo.file_version = this->GetArgInt(3); }
2344
2345 m_current_module->fileinfo.push_back(fileinfo);
2346
2348}
2349
2351{
2352 if (! this->CheckNumArguments(2)) { return; }
2353
2354 Rope rope;
2357 rope.root_node = this->GetArgNodeRef(0);
2358 rope.end_node = this->GetArgNodeRef(1);
2359
2360 if (m_num_args > 2) { rope.invisible = (this->GetArgChar(2) == 'i'); }
2361
2362 m_current_module->ropes.push_back(rope);
2364}
2365
2367{
2368 if (! this->CheckNumArguments(1)) { return; }
2369
2370 Ropable ropable;
2371 ropable.node = this->GetArgNodeRef(0);
2372
2373 if (m_num_args > 1) { ropable.group = this->GetArgInt(1); }
2374 if (m_num_args > 2) { ropable.has_multilock = (this->GetArgInt(2) == 1); }
2375
2376 m_current_module->ropables.push_back(ropable);
2378}
2379
2381{
2382 Ogre::StringVector args = Ogre::StringUtil::split(m_current_line, ",");
2383 m_num_args = (int)args.size();
2384 if (! this->CheckNumArguments(3)) { return; }
2385
2386 RailGroup railgroup;
2387 railgroup.id = this->ParseArgInt(args[0].c_str());
2388
2389 for (auto itor = args.begin() + 1; itor != args.end(); itor++)
2390 {
2391 railgroup.node_list.push_back( this->_ParseNodeRef(*itor));
2392 }
2393
2394 m_current_module->railgroups.push_back(railgroup);
2396}
2397
2399{
2400 if (! this->CheckNumArguments(10)) { return; }
2401
2402 Prop prop;
2403 prop.reference_node = this->GetArgNodeRef(0);
2404 prop.x_axis_node = this->GetArgNodeRef(1);
2405 prop.y_axis_node = this->GetArgNodeRef(2);
2406 prop.offset.x = this->GetArgFloat (3);
2407 prop.offset.y = this->GetArgFloat (4);
2408 prop.offset.z = this->GetArgFloat (5);
2409 prop.rotation.x = this->GetArgFloat (6);
2410 prop.rotation.y = this->GetArgFloat (7);
2411 prop.rotation.z = this->GetArgFloat (8);
2412 prop.mesh_name = this->GetArgStr(9);
2414
2415 if ((prop.special == SpecialProp::BEACON) && (m_num_args >= 14))
2416 {
2418 Ogre::StringUtil::trim(prop.special_prop_beacon.flare_material_name);
2419
2420 prop.special_prop_beacon.color = Ogre::ColourValue(
2421 this->GetArgFloat(11), this->GetArgFloat(12), this->GetArgFloat(13));
2422 }
2423 else if (prop.special == SpecialProp::DASHBOARD_LEFT ||
2425 {
2426 if (m_num_args > 10) prop.special_prop_dashboard.mesh_name = this->GetArgStr(10);
2427 if (m_num_args > 13)
2428 {
2429 prop.special_prop_dashboard.offset = Ogre::Vector3(this->GetArgFloat(11), this->GetArgFloat(12), this->GetArgFloat(13));
2431 }
2433 }
2434
2435 m_current_module->props.push_back(prop);
2437}
2438
2440{
2441 if (!this->CheckNumArguments(10)) { return; }
2442
2443 Pistonprop pistonprop;
2444 pistonprop.reference_node = this->GetArgNodeRef (0);
2445 pistonprop.axis_node = this->GetArgNodeRef (1);
2446 pistonprop.blade_tip_nodes[0] = this->GetArgNodeRef (2);
2447 pistonprop.blade_tip_nodes[1] = this->GetArgNodeRef (3);
2448 pistonprop.blade_tip_nodes[2] = this->GetArgNullableNode(4);
2449 pistonprop.blade_tip_nodes[3] = this->GetArgNullableNode(5);
2450 pistonprop.couple_node = this->GetArgNullableNode(6);
2451 pistonprop.turbine_power_kW = this->GetArgFloat (7);
2452 pistonprop.pitch = this->GetArgFloat (8);
2453 pistonprop.airfoil = this->GetArgStr (9);
2454
2455 m_current_module->pistonprops.push_back(pistonprop);
2457}
2458
2460{
2461 if (!this->CheckNumArguments(3)) { return; }
2462
2463 Particle particle;
2464 particle.emitter_node = this->GetArgNodeRef(0);
2465 particle.reference_node = this->GetArgNodeRef(1);
2466 particle.particle_system_name = this->GetArgStr (2);
2467
2468 m_current_module->particles.push_back(particle);
2470}
2471
2472// Static
2473void Parser::_TrimTrailingComments(std::string const & line_in, std::string & line_out)
2474{
2475 // Trim trailing comment
2476 // We need to handle a case of lines as [keyword 1, 2, 3 ;;///// Comment!]
2477 int comment_start = static_cast<int>(line_in.find_first_of(";"));
2478 if (comment_start != Ogre::String::npos)
2479 {
2480 line_out = line_in.substr(0, comment_start);
2481 return;
2482 }
2483 // The [//Comment] is harder - the '/' character may also be present in DESCRIPTION arguments!
2484 comment_start = static_cast<int>(line_in.find_last_of("/"));
2485 if (comment_start != Ogre::String::npos)
2486 {
2487 while (comment_start >= 0)
2488 {
2489 char c = line_in[comment_start - 1];
2490 if (c != '/' && c != ' ' && c != '\t')
2491 {
2492 break; // Start of comment found
2493 }
2494 --comment_start;
2495 }
2496 line_out = line_in.substr(0, comment_start);
2497 return;
2498 }
2499 // No comment found
2500 line_out = line_in;
2501}
2502
2504{
2505 if (! this->CheckNumArguments(4)) { return; }
2506
2507 Node node;
2512
2514 {
2515 std::string node_name = this->GetArgStr(0);
2516 node.id.setStr(node_name);
2518 {
2520 }
2521 m_any_named_node_defined = true; // For import logic
2522 }
2523 else
2524 {
2525 const unsigned int node_num = this->GetArgUint(0);
2526 node.id.SetNum(node_num);
2528 {
2530 }
2531 }
2532
2533 node.position.x = this->GetArgFloat(1);
2534 node.position.y = this->GetArgFloat(2);
2535 node.position.z = this->GetArgFloat(3);
2536 if (m_num_args > 4)
2537 {
2538 node.options = this->GetArgNodeOptions(4);
2539 }
2540 if (m_num_args > 5)
2541 {
2543 {
2544 node.load_weight_override = this->GetArgFloat(5);
2545 node._has_load_weight_override = true;
2546 }
2547 else
2548 {
2550 "Node has load-weight-override value specified, but option 'l' is not present. Ignoring value...");
2551 }
2552 }
2553
2554 m_current_module->nodes.push_back(node);
2556}
2557
2559{
2560 if (! this->CheckNumArguments(1)) { return; }
2561
2562 Minimass mm;
2563 mm.global_min_mass_Kg = this->GetArgFloat(0);
2564 if (m_num_args > 1) { mm.option = this->GetArgMinimassOption(1); }
2565
2566 m_current_module->minimass.push_back(mm);
2568}
2569
2571{
2572 if (! this->CheckNumArguments(16)) { return; }
2573
2574 FlexBodyWheel flexbody_wheel;
2575 flexbody_wheel.node_defaults = m_user_node_defaults;
2576 flexbody_wheel.beam_defaults = m_user_beam_defaults;
2577
2578 flexbody_wheel.tyre_radius = this->GetArgFloat ( 0);
2579 flexbody_wheel.rim_radius = this->GetArgFloat ( 1);
2580 flexbody_wheel.width = this->GetArgFloat ( 2);
2581 flexbody_wheel.num_rays = this->GetArgInt ( 3);
2582 flexbody_wheel.nodes[0] = this->GetArgNodeRef ( 4);
2583 flexbody_wheel.nodes[1] = this->GetArgNodeRef ( 5);
2584 flexbody_wheel.rigidity_node = this->GetArgRigidityNode ( 6);
2585 flexbody_wheel.braking = this->GetArgBraking ( 7);
2586 flexbody_wheel.propulsion = this->GetArgPropulsion ( 8);
2587 flexbody_wheel.reference_arm_node = this->GetArgNodeRef ( 9);
2588 flexbody_wheel.mass = this->GetArgFloat (10);
2589 flexbody_wheel.tyre_springiness = this->GetArgFloat (11);
2590 flexbody_wheel.tyre_damping = this->GetArgFloat (12);
2591 flexbody_wheel.rim_springiness = this->GetArgFloat (13);
2592 flexbody_wheel.rim_damping = this->GetArgFloat (14);
2593 flexbody_wheel.side = this->GetArgWheelSide (15);
2594
2595 if (m_num_args > 16) { flexbody_wheel.rim_mesh_name = this->GetArgStr(16); }
2596 if (m_num_args > 17) { flexbody_wheel.tyre_mesh_name = this->GetArgStr(17); }
2597
2599 {
2601 }
2602
2603 m_current_module->flexbodywheels.push_back(flexbody_wheel);
2605}
2606
2608{
2609 if (! this->CheckNumArguments(2)) { return; }
2610
2611 MaterialFlareBinding binding;
2612 binding.flare_number = this->GetArgInt(0);
2613 binding.material_name = this->GetArgStr(1);
2614
2615 m_current_module->materialflarebindings.push_back(binding);
2616 this->FlushPendingDocComment(m_current_module->materialflarebindings.size(), Keyword::MATERIALFLAREBINDINGS);
2617}
2618
2620{
2621 if (! this->CheckNumArguments(2)) { return; }
2622
2623 ManagedMaterial managed_mat;
2625 managed_mat.name = this->GetArgStr(0);
2626 managed_mat.type = this->GetArgManagedMatType(1);
2627
2628 if (managed_mat.type != ManagedMaterialType::INVALID)
2629 {
2630 if (! this->CheckNumArguments(3)) { return; }
2631
2632 managed_mat.diffuse_map = this->GetArgStr(2);
2633
2634 if (managed_mat.type == ManagedMaterialType::MESH_STANDARD ||
2636 {
2637 if (m_num_args > 3) { managed_mat.specular_map = this->GetArgManagedTex(3); }
2638 }
2639 else if (managed_mat.type == ManagedMaterialType::FLEXMESH_STANDARD ||
2641 {
2642 if (m_num_args > 3) { managed_mat.damaged_diffuse_map = this->GetArgManagedTex(3); }
2643 if (m_num_args > 4) { managed_mat.specular_map = this->GetArgManagedTex(4); }
2644 }
2645
2646 m_current_module->managedmaterials.push_back(managed_mat);
2647 this->FlushPendingDocComment(m_current_module->managedmaterials.size(), Keyword::MANAGEDMATERIALS);
2648 }
2649}
2650
2652{
2653 if (! this->CheckNumArguments(2)) { return; } // Lockgroup num. + at least 1 node...
2654
2655 Lockgroup lockgroup;
2656 lockgroup.number = this->GetArgInt(0);
2657
2658 for (int i = 1; i < m_num_args; ++i)
2659 {
2660 lockgroup.nodes.push_back(this->GetArgNodeRef(i));
2661 }
2662
2663 m_current_module->lockgroups.push_back(lockgroup);
2665}
2666
2668{
2669 if (! this->CheckNumArguments(3)) { return; }
2670
2671 Hydro hydro;
2675
2676 hydro.nodes[0] = this->GetArgNodeRef(0);
2677 hydro.nodes[1] = this->GetArgNodeRef(1);
2678 hydro.lenghtening_factor = this->GetArgFloat (2);
2679
2680 if (m_num_args > 3) { hydro.options = this->GetArgHydroOptions(3); }
2681
2682 if (!hydro.options)
2683 {
2685 }
2686
2687 this->ParseOptionalInertia(hydro.inertia, 4);
2688
2689 m_current_module->hydros.push_back(hydro);
2691}
2692
2693void Parser::ParseOptionalInertia(Inertia & inertia, int index)
2694{
2695 if (m_num_args > index) { inertia.start_delay_factor = this->GetArgFloat(index++); }
2696 if (m_num_args > index) { inertia.stop_delay_factor = this->GetArgFloat(index++); }
2697 if (m_num_args > index) { inertia.start_function = this->GetArgStr (index++); }
2698 if (m_num_args > index) { inertia.stop_function = this->GetArgStr (index++); }
2699}
2700
2701void Parser::_ParseDifferentialTypes(DifferentialTypeVec& diff_types, std::string const& options_str)
2702{
2703 for (char c: options_str)
2704 {
2705 switch(c)
2706 {
2707 case (char)DifferentialType::o_OPEN:
2708 case (char)DifferentialType::l_LOCKED:
2709 case (char)DifferentialType::s_SPLIT:
2710 case (char)DifferentialType::v_VISCOUS:
2711 diff_types.push_back(DifferentialType(c));
2712 break;
2713
2714 default:
2716 fmt::format("ignoring invalid differential type '{}'", c));
2717 break;
2718 }
2719 }
2720}
2721
2723{
2724 if (! this->CheckNumArguments(2)) { return; }
2725
2726 Beam beam;
2729
2730 beam.nodes[0] = this->GetArgNodeRef(0);
2731 beam.nodes[1] = this->GetArgNodeRef(1);
2732 if (m_num_args > 2) beam.options = this->GetArgBeamOptions(2);
2733
2735 {
2736 float support_break_limit = 0.0f;
2737 float support_break_factor = this->GetArgInt(3);
2738 if (support_break_factor > 0.0f)
2739 {
2740 support_break_limit = support_break_factor;
2741 }
2742 beam.extension_break_limit = support_break_limit;
2743 beam._has_extension_break_limit = true;
2744 }
2745
2746 m_current_module->beams.push_back(beam);
2748}
2749
2751{
2752 auto args = Ogre::StringUtil::split(m_current_line, ",");
2753 if (args.size() < 4) { return; }
2754
2755 Animator animator;
2759
2760 animator.nodes[0] = this->_ParseNodeRef(args[0]);
2761 animator.nodes[1] = this->_ParseNodeRef(args[1]);
2762 animator.lenghtening_factor = this->ParseArgFloat(args[2]);
2763
2764 // Parse options; Just use the split/trim/compare method
2765 Ogre::StringVector attrs = Ogre::StringUtil::split(args[3], "|");
2766
2767 auto itor = attrs.begin();
2768 auto endi = attrs.end();
2769 for (; itor != endi; ++itor)
2770 {
2771 Ogre::String token = *itor;
2772 Ogre::StringUtil::trim(token);
2773 std::smatch results;
2774 bool is_shortlimit = false;
2775
2776 // Numbered keywords
2777 if (std::regex_search(token, results, Regexes::PARSE_ANIMATORS_NUMBERED_KEYWORD))
2778 {
2779 if (results[1] == "throttle") animator.aero_animator.flags |= AeroAnimator::OPTION_THROTTLE;
2780 else if (results[1] == "rpm") animator.aero_animator.flags |= AeroAnimator::OPTION_RPM;
2781 else if (results[1] == "aerotorq") animator.aero_animator.flags |= AeroAnimator::OPTION_TORQUE;
2782 else if (results[1] == "aeropit") animator.aero_animator.flags |= AeroAnimator::OPTION_PITCH;
2783 else if (results[1] == "aerostatus") animator.aero_animator.flags |= AeroAnimator::OPTION_STATUS;
2784
2785 animator.aero_animator.engine_idx = this->ParseArgUint(results[2].str().c_str()) - 1;
2786 }
2787 else if ((is_shortlimit = (token.compare(0, 10, "shortlimit") == 0)) || (token.compare(0, 9, "longlimit") == 0))
2788 {
2789 Ogre::StringVector fields = Ogre::StringUtil::split(token, ":");
2790 if (fields.size() > 1)
2791 {
2792 if (is_shortlimit)
2793 {
2794 animator.short_limit = std::strtod(fields[1].c_str(), nullptr);
2796 }
2797 else
2798 {
2799 animator.long_limit = std::strtod(fields[1].c_str(), nullptr);
2801 }
2802 }
2803 }
2804 else
2805 {
2806 // Standalone keywords
2807 if (token == "vis") animator.flags |= Animator::OPTION_VISIBLE;
2808 else if (token == "inv") animator.flags |= Animator::OPTION_INVISIBLE;
2809 else if (token == "airspeed") animator.flags |= Animator::OPTION_AIRSPEED;
2810 else if (token == "vvi") animator.flags |= Animator::OPTION_VERTICAL_VELOCITY;
2811 else if (token == "altimeter100k") animator.flags |= Animator::OPTION_ALTIMETER_100K;
2812 else if (token == "altimeter10k") animator.flags |= Animator::OPTION_ALTIMETER_10K;
2813 else if (token == "altimeter1k") animator.flags |= Animator::OPTION_ALTIMETER_1K;
2814 else if (token == "aoa") animator.flags |= Animator::OPTION_ANGLE_OF_ATTACK;
2815 else if (token == "flap") animator.flags |= Animator::OPTION_FLAP;
2816 else if (token == "airbrake") animator.flags |= Animator::OPTION_AIR_BRAKE;
2817 else if (token == "roll") animator.flags |= Animator::OPTION_ROLL;
2818 else if (token == "pitch") animator.flags |= Animator::OPTION_PITCH;
2819 else if (token == "brakes") animator.flags |= Animator::OPTION_BRAKES;
2820 else if (token == "accel") animator.flags |= Animator::OPTION_ACCEL;
2821 else if (token == "clutch") animator.flags |= Animator::OPTION_CLUTCH;
2822 else if (token == "speedo") animator.flags |= Animator::OPTION_SPEEDO;
2823 else if (token == "tacho") animator.flags |= Animator::OPTION_TACHO;
2824 else if (token == "turbo") animator.flags |= Animator::OPTION_TURBO;
2825 else if (token == "parking") animator.flags |= Animator::OPTION_PARKING;
2826 else if (token == "shifterman1") animator.flags |= Animator::OPTION_SHIFT_LEFT_RIGHT;
2827 else if (token == "shifterman2") animator.flags |= Animator::OPTION_SHIFT_BACK_FORTH;
2828 else if (token == "sequential") animator.flags |= Animator::OPTION_SEQUENTIAL_SHIFT;
2829 else if (token == "shifterlin") animator.flags |= Animator::OPTION_GEAR_SELECT;
2830 else if (token == "torque") animator.flags |= Animator::OPTION_TORQUE;
2831 else if (token == "difflock") animator.flags |= Animator::OPTION_DIFFLOCK;
2832 else if (token == "rudderboat") animator.flags |= Animator::OPTION_BOAT_RUDDER;
2833 else if (token == "throttleboat") animator.flags |= Animator::OPTION_BOAT_THROTTLE;
2834 }
2835 }
2836
2837 m_current_module->animators.push_back(animator);
2839}
2840
2842{
2843 if (! this->CheckNumArguments(2)) { return; }
2844
2845 Author author;
2846 if (m_num_args > 1) { author.type = this->GetArgStr(1); }
2847 if (m_num_args > 2) { author.forum_account_id = this->GetArgInt(2); author._has_forum_account = true; }
2848 if (m_num_args > 3) { author.name = this->GetArgStr(3); }
2849 if (m_num_args > 4) { author.email = this->GetArgStr(4); }
2850
2851 m_current_module->author.push_back(author);
2854}
2855
2856// --------------------------------------------------------------------------
2857// Utilities
2858// --------------------------------------------------------------------------
2859
2860void Parser::LogMessage(Console::MessageType type, std::string const& msg)
2861{
2864 type,
2865 fmt::format("{}:{} ({}): {}",
2867}
2868
2869Keyword Parser::IdentifyKeyword(const std::string& line)
2870{
2871 // Search and ignore lettercase
2872 std::smatch results;
2873 std::regex_search(line, results, Regexes::IDENTIFY_KEYWORD_IGNORE_CASE); // Always returns true.
2874
2875 // The 'results' array contains a complete match at positon [0] and sub-matches starting with [1],
2876 // so we get exact positions in Regexes::IDENTIFY_KEYWORD, which again match Keyword enum members
2877 for (unsigned int i = 1; i < results.size(); i++)
2878 {
2879 std::ssub_match sub = results[i];
2880 if (sub.matched)
2881 {
2882 // Build enum value directly from result offset
2883 return Keyword(i);
2884 }
2885 }
2886
2887 return Keyword::INVALID;
2888}
2889
2891{
2896 m_current_detacher_group = 0; // Global detacher group
2897
2901
2902 m_user_beam_defaults = std::shared_ptr<BeamDefaults>(new BeamDefaults);
2903 m_user_beam_defaults->springiness = DEFAULT_SPRING;
2904 m_user_beam_defaults->damping_constant = DEFAULT_DAMP;
2905 m_user_beam_defaults->deformation_threshold = BEAM_DEFORM;
2906 m_user_beam_defaults->breaking_threshold = BEAM_BREAK;
2907 m_user_beam_defaults->visual_beam_diameter = DEFAULT_BEAM_DIAMETER;
2908
2909 m_root_module = m_definition->root_module;
2910 m_current_module = m_definition->root_module;
2911
2912 m_sequential_importer.Init(true); // Enabled=true
2913}
2914
2916{
2917 if (keyword == Keyword::INVALID) // also means 'end'
2918 {
2919 // flush staged submesh, if any
2920 if (m_current_submesh != nullptr)
2921 {
2922 m_current_module->submeshes.push_back(*m_current_submesh);
2923 m_current_submesh.reset(); // Set to nullptr
2924 }
2925
2926 // flush staged camerarail, if any
2927 if (m_current_camera_rail != nullptr)
2928 {
2929 if (m_current_camera_rail->nodes.size() == 0)
2930 {
2931 this->LogMessage(Console::CONSOLE_SYSTEM_WARNING, "Empty section 'camerarail', ignoring...");
2932 }
2933 else
2934 {
2935 m_current_module->camerarail.push_back(*m_current_camera_rail);
2936 m_current_camera_rail.reset();
2937 }
2938 }
2939 }
2940 else if (keyword == Keyword::CAMERARAIL)
2941 {
2942 this->BeginBlock(Keyword::INVALID); // flush staged rail
2943 m_current_camera_rail = std::shared_ptr<CameraRail>( new CameraRail() );
2944 }
2946}
2947
2949{
2950 // Determine and verify new module
2951 std::string new_module_name;
2953 {
2955 {
2956 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "Misplaced keyword 'end_section' (already in root module), ignoring...");
2957 return;
2958 }
2959 new_module_name = ROOT_MODULE_NAME;
2960 }
2961 else if (keyword == Keyword::SECTION)
2962 {
2963 if (!this->CheckNumArguments(3)) // Syntax: "section VERSION NAME"; VERSION is unused
2964 {
2965 return; // Error already reported
2966 }
2967
2968 new_module_name = this->GetArgStr(2);
2969 if (new_module_name == m_current_module->name)
2970 {
2971 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "Attempt to re-enter current module, ignoring...");
2972 return;
2973 }
2974 }
2975
2976 // Perform the switch
2978
2979 if (new_module_name == ROOT_MODULE_NAME)
2980 {
2982 return;
2983 }
2984
2985 auto search_itor = m_definition->user_modules.find(new_module_name);
2986 if (search_itor != m_definition->user_modules.end())
2987 {
2988 m_current_module = search_itor->second;
2989 }
2990 else
2991 {
2992 m_current_module = std::make_shared<Document::Module>(new_module_name);
2993 m_definition->user_modules.insert(std::make_pair(new_module_name, m_current_module));
2994 }
2995}
2996
2998{
2999 if (m_pending_doc_comment.comment_text == "" || vectorlen == 0)
3000 {
3001 return;
3002 }
3003
3005 m_pending_doc_comment.commented_datapos = static_cast<int>(vectorlen - 1);
3006 m_current_module->_comments.push_back(m_pending_doc_comment);
3007
3009}
3010
3015
3017{
3018 // FIXME: restore this, see branch 'retro-0407'
3019}
3020
3022{
3024
3026 {
3028 }
3029}
3030
3031std::string Parser::GetArgStr(int index)
3032{
3033 return std::string(m_args[index].start, m_args[index].length);
3034}
3035
3036char Parser::GetArgChar(int index)
3037{
3038 return *(m_args[index].start);
3039}
3040
3042{
3043 char c = this->GetArgChar(index);
3044 switch (c)
3045 {
3046 case (char)WheelSide::RIGHT:
3047 case (char)WheelSide::LEFT:
3048 return WheelSide(c);
3049
3050 default:
3052 fmt::format("Bad arg~{} 'side' (value: {}), parsing as 'l' for backwards compatibility.", index + 1, c));
3053 return WheelSide::LEFT;
3054 }
3055}
3056
3057long Parser::GetArgLong(int index)
3058{
3059 errno = 0;
3060 char* out_end = nullptr;
3061 long res = std::strtol(m_args[index].start, &out_end, 10);
3062 if (errno != 0)
3063 {
3065 fmt::format("Cannot parse argument [{}] as integer, errno: {}", index + 1, errno));
3066 return 0; // Compatibility
3067 }
3068 if (out_end == m_args[index].start)
3069 {
3071 fmt::format("Argument [{}] is not valid integer", index + 1));
3072 return 0; // Compatibility
3073 }
3074 else if (out_end != (m_args[index].start + m_args[index].length))
3075 {;
3077 fmt::format("Integer argument [{}] has invalid trailing characters", index + 1));
3078 }
3079 return res;
3080}
3081
3082int Parser::GetArgInt(int index)
3083{
3084 return static_cast<int>(this->GetArgLong(index));
3085}
3086
3088{
3089 std::string rigidity_node = this->GetArgStr(index);
3090 if (rigidity_node != "9999") // Special null value
3091 {
3092 return this->GetArgNodeRef(index);
3093 }
3094 return Node::Ref(); // Defaults to invalid ref
3095}
3096
3098{
3099 int p = this->GetArgInt(index);
3100 switch (p)
3101 {
3102 case (int)WheelPropulsion::NONE:
3103 case (int)WheelPropulsion::FORWARD:
3104 case (int)WheelPropulsion::BACKWARD:
3105 return WheelPropulsion(p);
3106
3107 default:
3109 fmt::format("Bad value of param ~{} (propulsion), using 0 (no propulsion)", index + 1));
3110 return WheelPropulsion::NONE;
3111 }
3112}
3113
3115{
3116 int b = this->GetArgInt(index);
3117 switch (b)
3118 {
3119 case (int)WheelBraking::NONE:
3120 case (int)WheelBraking::FOOT_HAND:
3121 case (int)WheelBraking::FOOT_HAND_SKID_LEFT:
3122 case (int)WheelBraking::FOOT_HAND_SKID_RIGHT:
3123 case (int)WheelBraking::FOOT_ONLY:
3124 return WheelBraking(b);
3125
3126 default:
3128 fmt::format("Bad value of param ~{} (braking), using 0 (not braked)", index + 1));
3129 return WheelBraking::NONE;
3130 }
3131}
3132
3134{
3135 return this->_ParseNodeRef(this->GetArgStr(index));
3136}
3137
3139{
3140 if (! (Ogre::StringConverter::parseReal(this->GetArgStr(index)) == -1.f))
3141 {
3142 return this->GetArgNodeRef(index);
3143 }
3144 return Node::Ref(); // Defaults to empty ref.
3145}
3146
3147unsigned Parser::GetArgUint(int index)
3148{
3149 return static_cast<unsigned>(this->GetArgLong(index));
3150}
3151
3153{
3154 char in = this->GetArgChar(index);
3155 switch (in)
3156 {
3157 // Front lights
3158 case (char)FlareType::HEADLIGHT:
3159 case (char)FlareType::HIGH_BEAM:
3160 case (char)FlareType::FOG_LIGHT:
3161 // Rear lighs
3162 case (char)FlareType::TAIL_LIGHT:
3163 case (char)FlareType::BRAKE_LIGHT:
3164 case (char)FlareType::REVERSE_LIGHT:
3165 // Special lights
3166 case (char)FlareType::SIDELIGHT:
3167 case (char)FlareType::BLINKER_LEFT:
3168 case (char)FlareType::BLINKER_RIGHT:
3169 case (char)FlareType::USER:
3170 case (char)FlareType::DASHBOARD:
3171 return FlareType(in);
3172
3173 default:
3175 fmt::format("Invalid flare type '{}', falling back to type 'f' (front light)...", in));
3176 return FlareType::HEADLIGHT;
3177 }
3178}
3179
3181{
3182 std::string str = this->GetArgStr(index);
3183 if (str == "classic") return ExtCameraMode::CLASSIC;
3184 if (str == "cinecam") return ExtCameraMode::CINECAM;
3185 if (str == "node") return ExtCameraMode::NODE;
3186
3188 fmt::format("Invalid ExtCameraMode '{}', falling back to type 'classic'...", str));
3189 return ExtCameraMode::CLASSIC;
3190}
3191
3192float Parser::GetArgFloat(int index)
3193{
3194 return (float) Ogre::StringConverter::parseReal(this->GetArgStr(index), 0.f);
3195}
3196
3197float Parser::ParseArgFloat(const char* str)
3198{
3199 return (float) Ogre::StringConverter::parseReal(str, 0.f);
3200}
3201
3202float Parser::ParseArgFloat(std::string const & str)
3203{
3204 return this->ParseArgFloat(str.c_str());
3205}
3206
3207unsigned Parser::ParseArgUint(const char* str)
3208{
3209 errno = 0;
3210 long res = std::strtol(str, nullptr, 10);
3211 if (errno != 0)
3212 {
3214 fmt::format("Cannot parse argument '{}' as int, errno: {}", str, errno));
3215 return 0.f; // Compatibility
3216 }
3217 return static_cast<unsigned>(res);
3218}
3219
3220unsigned Parser::ParseArgUint(std::string const & str)
3221{
3222 return this->ParseArgUint(str.c_str());
3223}
3224
3225int Parser::ParseArgInt(const char* str)
3226{
3227 return static_cast<int>(this->ParseArgUint(str));
3228}
3229
3230bool Parser::GetArgBool(int index)
3231{
3232 return Ogre::StringConverter::parseBool(this->GetArgStr(index));
3233}
3234
3236{
3237 char c = this->GetArgChar(index);
3238 switch (c)
3239 {
3240 case (char)WingControlSurface::n_NONE:
3243 case (char)WingControlSurface::f_FLAP:
3256 return WingControlSurface(c);
3257
3258 default:
3259 fmt::format("invalid WingControlSurface '{}', falling back to 'n' (none)", c);
3261 }
3262}
3263
3264std::string Parser::GetArgManagedTex(int index)
3265{
3266 std::string tex_name = this->GetArgStr(index);
3267 return (tex_name.at(0) != '-') ? tex_name : "";
3268}
3269
3271{
3272 switch (this->GetArgStr(index)[0])
3273 {
3276
3277 case (char)MinimassOption::n_DUMMY:
3279
3280 default:
3282 fmt::format("Not a valid minimass option: {}, falling back to 'n' (dummy)", this->GetArgStr(index)));
3284 }
3285}
3286
3288{
3289 BitMask_t ret = 0;
3290 for (char c: this->GetArgStr(index))
3291 {
3292 switch (c)
3293 {
3294 case (char)CabOption::n_DUMMY: break;
3295 case (char)CabOption::c_CONTACT: ret |= Cab::OPTION_c_CONTACT ; break;
3296 case (char)CabOption::b_BUOYANT: ret |= Cab::OPTION_b_BUOYANT ; break;
3297 case (char)CabOption::p_10xTOUGHER: ret |= Cab::OPTION_p_10xTOUGHER ; break;
3298 case (char)CabOption::u_INVULNERABLE: ret |= Cab::OPTION_u_INVULNERABLE ; break;
3304
3305 default:
3307 fmt::format("ignoring invalid flag '{}'", c));
3308 }
3309 }
3310 return ret;
3311}
3312
3314{
3315 BitMask_t ret = 0;
3316 for (char c: this->GetArgStr(index))
3317 {
3318 switch(c)
3319 {
3331
3332 default:
3334 fmt::format("ignoring invalid option '{}'", c));
3335 }
3336 }
3337 return ret;
3338}
3339
3341{
3342 BitMask_t ret = 0;
3343 for (char c: this->GetArgStr(index))
3344 {
3345 switch (c)
3346 {
3347 case (char)BeamOption::i_INVISIBLE: ret |= Beam::OPTION_i_INVISIBLE; break;
3348 case (char)BeamOption::r_ROPE : ret |= Beam::OPTION_r_ROPE ; break;
3349 case (char)BeamOption::s_SUPPORT : ret |= Beam::OPTION_s_SUPPORT ; break;
3350
3351 case (char)BeamOption::v_DUMMY: break;
3352
3353 default:
3355 fmt::format("ignoring invalid option '{}'", c));
3356 }
3357 }
3358 return ret;
3359}
3360
3362{
3363 BitMask_t ret = 0;
3364 for (char c: this->GetArgStr(index))
3365 {
3366 switch (c)
3367 {
3368 case (char)HydroOption::j_INVISIBLE : ret |= Hydro::OPTION_j_INVISIBLE ; break;
3380
3382 if (ret == 0)
3383 {
3384 // Original intent: when using 'i' flag alone, also force 'n' (steering input).
3385 // For backward compatibility, do it every time 'i' comes first, even if not alone.
3387 }
3389 break;
3390
3391 default:
3393 fmt::format("ignoring invalid option '{}'", c));
3394 }
3395 }
3396
3397 return ret;
3398}
3399
3401{
3402 BitMask_t ret = 0;
3403 for (char c: this->GetArgStr(index))
3404 {
3405 switch (c)
3406 {
3407 case (char)ShockOption::i_INVISIBLE : ret |= Shock::OPTION_i_INVISIBLE ; break;
3408 case (char)ShockOption::L_ACTIVE_LEFT : ret |= Shock::OPTION_L_ACTIVE_LEFT ; break;
3410 case (char)ShockOption::m_METRIC : ret |= Shock::OPTION_m_METRIC ; break;
3411
3412 case (char)ShockOption::n_DUMMY: break;
3413 case (char)ShockOption::v_DUMMY: break;
3414
3415 default:
3417 fmt::format("ignoring invalid option '{}'", c));
3418 }
3419 }
3420 return ret;
3421}
3422
3424{
3425 BitMask_t ret = 0;
3426 for (char c: this->GetArgStr(index))
3427 {
3428 switch (c)
3429 {
3430 case (char)Shock2Option::i_INVISIBLE: ret |= Shock2::OPTION_i_INVISIBLE; break;
3432 case (char)Shock2Option::m_METRIC: ret |= Shock2::OPTION_m_METRIC; break;
3434
3435 case (char)Shock2Option::n_DUMMY: break;
3436 case (char)Shock2Option::v_DUMMY: break;
3437
3438 default:
3440 fmt::format("ignoring invalid option '{}'", c));
3441 }
3442 }
3443 return ret;
3444}
3445
3447{
3448 BitMask_t ret = 0;
3449 for (char c: this->GetArgStr(index))
3450 {
3451 switch (c)
3452 {
3453 case (char)Shock3Option::i_INVISIBLE: ret |= Shock3::OPTION_i_INVISIBLE; break;
3454 case (char)Shock3Option::m_METRIC: ret |= Shock3::OPTION_m_METRIC; break;
3456
3457 case (char)Shock3Option::n_DUMMY: break;
3458 case (char)Shock3Option::v_DUMMY: break;
3459
3460 default:
3462 fmt::format("ignoring invalid option '{}'", c));
3463 }
3464 }
3465 return ret;
3466}
3467
3469{
3470 BitMask_t ret = 0;
3471 for (char c: this->GetArgStr(index))
3472 {
3473 switch (c)
3474 {
3476 case (char)NodeOption::f_NO_SPARKS : ret |= Node::OPTION_f_NO_SPARKS ; break;
3480 case (char)NodeOption::h_HOOK_POINT : ret |= Node::OPTION_h_HOOK_POINT ; break;
3483 case (char)NodeOption::p_NO_PARTICLES : ret |= Node::OPTION_p_NO_PARTICLES ; break;
3484 case (char)NodeOption::L_LOG : ret |= Node::OPTION_L_LOG ; break;
3485 case (char)NodeOption::l_LOAD_WEIGHT : ret |= Node::OPTION_l_LOAD_WEIGHT ; break;
3486
3487 case (char)NodeOption::n_DUMMY: break;
3488
3489 default:
3491 fmt::format("ignoring invalid option '{}'", c));
3492 }
3493 }
3494 return ret;
3495}
3496
3498{
3499 if (str.find("leftmirror" ) != std::string::npos) { return SpecialProp::MIRROR_LEFT; }
3500 if (str.find("rightmirror" ) != std::string::npos) { return SpecialProp::MIRROR_RIGHT; }
3501 if (str.find("dashboard-rh") != std::string::npos) { return SpecialProp::DASHBOARD_RIGHT; }
3502 if (str.find("dashboard" ) != std::string::npos) { return SpecialProp::DASHBOARD_LEFT; }
3503 if (Ogre::StringUtil::startsWith(str, "spinprop", false) ) { return SpecialProp::AERO_PROP_SPIN; }
3504 if (Ogre::StringUtil::startsWith(str, "pale", false) ) { return SpecialProp::AERO_PROP_BLADE; }
3505 if (Ogre::StringUtil::startsWith(str, "seat", false) ) { return SpecialProp::DRIVER_SEAT; }
3506 if (Ogre::StringUtil::startsWith(str, "seat2", false) ) { return SpecialProp::DRIVER_SEAT_2; }
3507 if (Ogre::StringUtil::startsWith(str, "beacon", false) ) { return SpecialProp::BEACON; }
3508 if (Ogre::StringUtil::startsWith(str, "redbeacon", false)) { return SpecialProp::REDBEACON; }
3509 if (Ogre::StringUtil::startsWith(str, "lightb", false) ) { return SpecialProp::LIGHTBAR; }
3510 return SpecialProp::NONE;
3511}
3512
3514{
3515 std::string str = this->GetArgStr(index);
3516 if (str == "bool") return DC_BOOL;
3517 if (str == "int") return DC_INT;
3518 if (str == "float") return DC_FLOAT;
3519 if (str == "string") return DC_CHAR;
3520
3522 fmt::format("Not a valid dashboard input data type: '{}'", str));
3523 return DC_INVALID;
3524}
3525
3527{
3528 char c = this->GetArgChar(index);
3529 switch (c)
3530 {
3531 case (char)EngineType::t_TRUCK:
3532 case (char)EngineType::c_CAR:
3533 case (char)EngineType::e_ECAR:
3534 return EngineType(c);
3535
3536 default:
3537 fmt::format("invalid EngineType '{}', falling back to 't' (truck)", c);
3538 return EngineType::t_TRUCK;
3539 }
3540}
3541
3543{
3544 std::string str = this->GetArgStr(index);
3545 if (str == "mesh_standard") return ManagedMaterialType::MESH_STANDARD;
3546 if (str == "mesh_transparent") return ManagedMaterialType::MESH_TRANSPARENT;
3547 if (str == "flexmesh_standard") return ManagedMaterialType::FLEXMESH_STANDARD;
3548 if (str == "flexmesh_transparent") return ManagedMaterialType::FLEXMESH_TRANSPARENT;
3549
3551 fmt::format("Not a valid ManagedMaterialType: '{}'", str));
3553}
3554
3556{
3557 int role = this->GetArgInt(index);
3558 switch (role)
3559 {
3564 default:
3566 fmt::format("Invalid value of 'camera role' ({}), videocamera will not work", role));
3567 return VCAM_ROLE_INVALID;
3568 }
3569}
3570
3572{
3573 int cur_arg = 0;
3574 const char* cur_char = m_current_line;
3575 int arg_len = 0;
3576 while ((*cur_char != '\0') && (cur_arg < Parser::LINE_MAX_ARGS))
3577 {
3578 const bool is_arg = !IsSeparator(*cur_char);
3579 if ((arg_len == 0) && is_arg)
3580 {
3581 m_args[cur_arg].start = cur_char;
3582 arg_len = 1;
3583 }
3584 else if ((arg_len > 0) && !is_arg)
3585 {
3586 m_args[cur_arg].length = arg_len;
3587 arg_len = 0;
3588 ++cur_arg;
3589 }
3590 else if (is_arg)
3591 {
3592 ++arg_len;
3593 }
3594 ++cur_char;
3595 }
3596 if (arg_len > 0)
3597 {
3598 m_args[cur_arg].length = arg_len;
3599 ++cur_arg;
3600 }
3601
3602 m_num_args = cur_arg;
3603 return cur_arg;
3604}
3605
3606void Parser::ProcessOgreStream(Ogre::DataStream* stream, Ogre::String resource_group)
3607{
3608 m_resource_group = resource_group;
3609 m_filename = stream->getName();
3610
3611 char raw_line_buf[LINE_BUFFER_LENGTH];
3612 while (!stream->eof())
3613 {
3614 try
3615 {
3616 stream->readLine(raw_line_buf, LINE_BUFFER_LENGTH - 1); // Ensure we don't overflow
3617 raw_line_buf[LINE_BUFFER_LENGTH - 1] = '\0'; // Null-terminate the buffer
3618 }
3619 catch (Ogre::Exception &ex)
3620 {
3622 fmt::format("Could not read truck file: {}", ex.getFullDescription()));
3623 break;
3624 }
3625
3626 this->ProcessRawLine(raw_line_buf);
3627 }
3628}
3629
3630void Parser::ProcessRawLine(const char* raw_line_buf)
3631{
3632 const char* raw_start = raw_line_buf;
3633 const char* raw_end = raw_line_buf + strnlen(raw_line_buf, LINE_BUFFER_LENGTH);
3634
3635 // Trim leading whitespace
3636 while (IsWhitespace(*raw_start) && (raw_start != raw_end))
3637 {
3638 ++raw_start;
3639 }
3640
3641 // Skip empty lines
3642 if (raw_start == raw_end)
3643 {
3645 return;
3646 }
3647
3648 // Record comment lines
3649 if ((*raw_start == ';') || (*raw_start == '/'))
3650 {
3651 m_pending_doc_comment.comment_text += raw_line_buf;
3654 return;
3655 }
3656
3657 // Sanitize UTF-8
3659 char* out_start = m_current_line;
3660 utf8::replace_invalid(raw_start, raw_end, out_start, '?');
3661
3662 // Process
3663 this->ProcessCurrentLine();
3665}
3666
3667} // namespace RigDef
Central state/object manager and communications hub.
#define PARSEINT(x)
Definition Application.h:59
void LOG(const char *msg)
Legacy alias - formerly a macro.
#define BITMASK_IS_1(VAR, FLAGS)
Definition BitFlags.h:14
#define BITMASK_SET_1(VAR, FLAGS)
Definition BitFlags.h:17
uint32_t BitMask_t
Definition BitFlags.h:7
A database of user-installed content alias 'mods' (vehicles, terrains...)
#define strnlen(str, len)
Data structures representing 'truck' file format, see https://docs.rigsofrods.org/vehicle-creation/fi...
Checks the rig-def file syntax and loads data to memory.
Defines regular expressions to verify and pull data from rig-def file. 'E' stands for Expression.
void SetNum(unsigned int id_num)
void setStr(std::string const &id_str)
Legacy parser resolved references on-the-fly and the condition to check named nodes was "are there an...
Definition RigDef_Node.h:78
bool IsValidAnyState() const
char m_current_line[LINE_BUFFER_LENGTH]
int ParseArgInt(const char *str)
bool GetArgBool(int index)
BitMask_t GetArgBeamOptions(int index)
RigDef::DocumentPtr m_definition
std::shared_ptr< DefaultMinimass > m_set_default_minimass
void ParseTractionControl()
Ogre::String m_resource_group
void ParseDirectiveSetDefaultMinimass()
std::shared_ptr< NodeDefaults > m_ror_node_defaults
void ParseMaterialFlareBindings()
BitMask_t GetArgShock3Options(int index)
static const int LINE_MAX_ARGS
WingControlSurface GetArgWingSurface(int index)
std::shared_ptr< Inertia > m_ror_default_inertia
RoR::VideoCamRole GetArgVideoCamRole(int index)
Node::Ref GetArgRigidityNode(int index)
RoR::WheelBraking GetArgBraking(int index)
Node::Ref _ParseNodeRef(std::string const &node_id_str)
void ParseCustomDashboardInputs()
void ParseDirectiveSetInertiaDefaults()
BitMask_t GetArgShockOptions(int index)
Token m_args[LINE_MAX_ARGS]
Tokens of current line.
std::shared_ptr< Document::Module > m_root_module
std::shared_ptr< Submesh > m_current_submesh
Parser state.
void ParseDirectiveForvert()
void ParseDirectivePropCameraMode()
Node::Ref GetArgNodeRef(int index)
MinimassOption GetArgMinimassOption(int index)
void BeginBlock(RigDef::Keyword keyword)
static const int LINE_BUFFER_LENGTH
void ParseDirectiveSetManagedMaterialsOptions()
RoR::FlareType GetArgFlareType(int index)
bool m_any_named_node_defined
Parser state.
static void ProcessForsetLine(RigDef::Flexbody &def, const std::string &line, int line_number=-1)
std::string GetArgStr(int index)
int GetArgInt(int index)
int GetArgDashboardInputDataType(int index)
Keyword m_log_keyword
void ParseDirectiveSetBeamDefaultsScale()
unsigned int m_current_line_number
unsigned GetArgUint(int index)
void ParseDirectiveDefaultSkin()
RoR::WheelSide GetArgWheelSide(int index)
ManagedMaterialType GetArgManagedMatType(int index)
void ParseDirectiveSubmesh()
ManagedMaterialsOptions m_current_managed_material_options
void ParseDirectiveBackmesh()
EngineType GetArgEngineType(int index)
BitMask_t GetArgNodeOptions(int index)
static SpecialProp IdentifySpecialProp(const std::string &str)
long GetArgLong(int index)
bool CheckNumArguments(int num_required_args)
void ParseSetSkeletonSettings()
void _ParseBaseMeshWheel(BaseMeshWheel &mesh_wheel)
void ProcessGlobalDirective(Keyword keyword)
Directives that should only appear in root module.
std::string GetArgManagedTex(int index)
float ParseArgFloat(const char *str)
std::shared_ptr< Document::Module > m_current_module
void LogMessage(RoR::Console::MessageType type, std::string const &msg)
Adds a message to console.
void ProcessCurrentLine()
std::shared_ptr< CameraRail > m_current_camera_rail
Parser state.
std::shared_ptr< BeamDefaults > m_user_beam_defaults
void ParseDirectiveAddAnimation()
void ParseFileFormatVersion()
void ParseDirectiveSectionConfig()
Ogre::String m_filename
RoR::WheelPropulsion GetArgPropulsion(int index)
SequentialImporter m_sequential_importer
BitMask_t GetArgCabOptions(int index)
Keyword m_current_block
static Keyword IdentifyKeyword(const std::string &line)
std::shared_ptr< NodeDefaults > m_user_node_defaults
int m_num_args
Number of tokens on current line.
void ParseFlaregroupsNoImport()
std::shared_ptr< Inertia > m_user_default_inertia
void ParseDirectiveFlexbodyCameraMode()
static void _TrimTrailingComments(std::string const &line_in, std::string &line_out)
void ParseDirectiveSetBeamDefaults()
void ParseOptionalInertia(Inertia &inertia, int index)
void ParseSubmeshGroundModel()
RoR::ExtCameraMode GetArgExtCameraMode(int index)
void ProcessRawLine(const char *line)
char GetArgChar(int index)
BitMask_t GetArgTriggerOptions(int index)
void ParseSetCollisionRange()
DocComment m_pending_doc_comment
To be linked with the following element.
unsigned ParseArgUint(const char *str)
BitMask_t GetArgHydroOptions(int index)
BitMask_t GetArgShock2Options(int index)
void ProcessChangeModuleLine(Keyword keyword)
void ParseTurbopropsUnified()
float GetArgFloat(int index)
void ProcessOgreStream(Ogre::DataStream *stream, Ogre::String resource_group)
void ParseDirectiveForset()
void FlushPendingDocComment(size_t vectorlen, RigDef::Keyword keyword)
void ParseDirectiveSetNodeDefaults()
void ParseDirectiveDetacherGroup()
void _ParseDifferentialTypes(DifferentialTypeVec &diff_types, std::string const &options_str)
Node::Ref GetArgNullableNode(int index)
bool AddNamedNode(std::string const &name)
void AddGeneratedNode(Keyword generated_from, NodeMapEntry::OriginDetail detail=NodeMapEntry::DETAIL_UNDEFINED)
void Process(RigDef::DocumentPtr def)
Traverse whole rig definition and resolve all node references.
void GenerateNodesForWheel(Keyword generated_from, int num_rays, bool has_rigidity_node)
bool AddNumberedNode(unsigned int number)
@ CONSOLE_MSGTYPE_ACTOR
Parsing/spawn/simulation messages for actors.
Definition Console.h:63
void putMessage(MessageArea area, MessageType type, std::string const &msg, std::string icon="")
Definition Console.cpp:103
@ CONSOLE_SYSTEM_ERROR
Definition Console.h:52
@ CONSOLE_SYSTEM_WARNING
Definition Console.h:53
const char * KeywordToString(RigDef::Keyword keyword)
@ SET_MANAGEDMATERIALS_OPTIONS
static const float BEAM_SKELETON_DIAMETER
static const float DEFAULT_SPRING
static const float DEFAULT_BEAM_DIAMETER
5 centimeters default beam width
static const float BEAM_DEFORM
static const float DEFAULT_DAMP
static const float BEAM_PLASTIC_COEF_DEFAULT
static const float BEAM_BREAK
const char * ROOT_MODULE_NAME
@ M_ABSOLUTE_METRIC
Absolute metric values for shortbound/longbound, settings apply without regarding to the original len...
@ m_METRIC
metric values for shortbound/longbound applying to the length of the beam.
bool IsSeparator(char c)
@ c_COMMAND_STYLE
trigger is set with commandstyle boundaries instead of shocksytle
@ s_CMD_NUM_SWITCH
switch that exchanges cmdshort/cmdshort for all triggers with the same commandnumbers,...
@ t_CONTINUOUS
this trigger sends values between 0 and 1
@ A_INV_TRIGGER_BLOCKER
Blocker that enable/disable other triggers, reversed activation method (inverted Blocker style,...
@ B_TRIGGER_BLOCKER
Blocker that enable/disable other triggers.
@ E_ENGINE_TRIGGER
this trigger is used to control an engine
@ x_START_DISABLED
this trigger is disabled on startup, default is enabled
@ b_KEY_BLOCKER
Set the CommandKeys that are set in a commandkeyblocker or trigger to blocked on startup,...
ManagedMaterialType
Definition RigDef_File.h:79
DifferentialType
Definition RigDef_File.h:98
WingControlSurface
std::vector< DifferentialType > DifferentialTypeVec
@ l_SKIP_LOADED
Only apply minimum mass to nodes without "L" option.
std::shared_ptr< Document > DocumentPtr
bool IsWhitespace(char c)
@ s_SOFT_BUMP_BOUNDS
soft bump boundaries, use when shocks reach limiters too often and "jumprebound" (default is hard bum...
@ M_ABSOLUTE_METRIC
Absolute metric values for shortbound/longbound, settings apply without regarding to the original len...
@ m_METRIC
metric values for shortbound/longbound applying to the length of the beam.
@ i_INVISIBLE_INPUT_NORMAL
For backwards compatibility; combines flags 'j' and 'n'.
bool StrEqualsNocase(std::string const &s1, std::string const &s2)
Console * GetConsole()
WheelSide
Used by rig-def/addonpart/tuneup formats to specify wheel rim mesh orientation.
VideoCamRole
@ VCAM_ROLE_MIRROR
Flips the video output and when not in driver cam, acts like a natural mirror, not a screen.
@ VCAM_ROLE_INVALID
@ VCAM_ROLE_TRACKING_VIDEOCAM
@ VCAM_ROLE_MIRROR_NOFLIP
Same as VCAM_ROLE_MIRROR, but without flipping the texture horizontally (expects texcoords to be alre...
@ VCAM_ROLE_VIDEOCAM
ExtCameraMode
WheelPropulsion
WheelBraking
static const BitMask_t OPTION_THROTTLE
unsigned int engine_idx
static const BitMask_t OPTION_RPM
static const BitMask_t OPTION_TORQUE
static const BitMask_t OPTION_PITCH
static const BitMask_t OPTION_STATUS
Node::Ref aditional_node
Node::Ref y_axis_node
Node::Ref reference_node
float max_inclination_angle
Node::Ref x_axis_node
Ogre::Vector3 offset
static const BitMask_t SOURCE_AERO_PITCH
static const BitMask_t SOURCE_AERO_STATUS
static const BitMask_t SOURCE_AERO_RPM
static const BitMask_t SOURCE_AERO_TORQUE
static const BitMask_t SOURCE_AERO_THROTTLE
static const BitMask_t SOURCE_GEAR_FORWARD
static const BitMask64_t SOURCE_GEAR_REVERSE
static const BitMask64_t SOURCE_TURBO
static const BitMask64_t SOURCE_SEQUENTIAL_SHIFT
static const BitMask64_t SOURCE_AIR_RUDDER
static const BitMask_t MODE_ROTATION_X
static const BitMask64_t SOURCE_ALTIMETER_1K
static const BitMask64_t SOURCE_AUTOSHIFTERLIN
static const BitMask_t MODE_OFFSET_Y
static const BitMask64_t SOURCE_SHIFT_LEFT_RIGHT
static const BitMask_t MODE_NO_FLIP
static const BitMask64_t SOURCE_FLAP
static const BitMask64_t SOURCE_SPEEDO
static const BitMask64_t SOURCE_DIFFLOCK
static const BitMask64_t SOURCE_ALTIMETER_10K
static const BitMask64_t SOURCE_AIRSPEED
BitMask64_t source
static const BitMask_t MODE_AUTO_ANIMATE
static const BitMask64_t SOURCE_ROLL
static const BitMask64_t SOURCE_BOAT_THROTTLE
static const BitMask64_t SOURCE_CLUTCH
static const BitMask_t MODE_OFFSET_Z
static const BitMask64_t SOURCE_PITCH
static const BitMask64_t SOURCE_TACHO
static const BitMask64_t SOURCE_AILERON
static const BitMask_t MODE_ROTATION_Y
static const BitMask64_t SOURCE_ELEVATOR
static const BitMask64_t SOURCE_PERMANENT
Ogre::String dash_link_name
static const BitMask_t MODE_EVENT_LOCK
Ogre::String event_name
static const BitMask64_t SOURCE_STEERING_WHEEL
static const BitMask64_t SOURCE_PARKING
static const BitMask64_t SOURCE_SHIFT_BACK_FORTH
static const BitMask64_t SOURCE_ACCEL
static const BitMask64_t SOURCE_IGNITION
static const BitMask64_t SOURCE_AIR_BRAKE
static const BitMask64_t SOURCE_GEAR_NEUTRAL
static const BitMask64_t SOURCE_ANGLE_OF_ATTACK
static const BitMask64_t SOURCE_VERTICAL_VELOCITY
static const BitMask_t MODE_OFFSET_X
static const BitMask_t MODE_BOUNCE
static const BitMask64_t SOURCE_SHIFTERLIN
static const BitMask64_t SOURCE_ALTIMETER_100K
static const BitMask64_t SOURCE_BOAT_RUDDER
std::list< MotorSource > motor_sources
static const BitMask64_t SOURCE_BRAKES
static const BitMask64_t SOURCE_TORQUE
static const BitMask_t MODE_ROTATION_Z
static const BitMask64_t SOURCE_DASHBOARD
static const BitMask64_t SOURCE_EVENT
static const BitMask64_t SOURCE_HEADING
static const BitMask64_t SOURCE_SIGNALSTALK
static const BitMask_t OPTION_ALTIMETER_100K
static const BitMask_t OPTION_ALTIMETER_1K
static const BitMask_t OPTION_SPEEDO
static const BitMask_t OPTION_ACCEL
static const BitMask_t OPTION_AIR_BRAKE
static const BitMask_t OPTION_ANGLE_OF_ATTACK
static const BitMask_t OPTION_BOAT_RUDDER
static const BitMask_t OPTION_ROLL
static const BitMask_t OPTION_SEQUENTIAL_SHIFT
static const BitMask_t OPTION_PARKING
static const BitMask_t OPTION_VISIBLE
static const BitMask_t OPTION_TURBO
static const BitMask_t OPTION_SHIFT_BACK_FORTH
static const BitMask_t OPTION_PITCH
AeroAnimator aero_animator
static const BitMask_t OPTION_AIRSPEED
std::shared_ptr< Inertia > inertia_defaults
static const BitMask_t OPTION_BOAT_THROTTLE
static const BitMask_t OPTION_SHORT_LIMIT
static const BitMask_t OPTION_ALTIMETER_10K
static const BitMask_t OPTION_VERTICAL_VELOCITY
static const BitMask_t OPTION_FLAP
std::shared_ptr< BeamDefaults > beam_defaults
static const BitMask_t OPTION_DIFFLOCK
static const BitMask_t OPTION_BRAKES
static const BitMask_t OPTION_TORQUE
static const BitMask_t OPTION_SHIFT_LEFT_RIGHT
static const BitMask_t OPTION_LONG_LIMIT
static const BitMask_t OPTION_INVISIBLE
static const BitMask_t OPTION_CLUTCH
Node::Ref nodes[2]
static const BitMask_t OPTION_GEAR_SELECT
static const BitMask_t OPTION_TACHO
std::string filename
Ogre::String name
unsigned int forum_account_id
bool _has_forum_account
Ogre::String type
Ogre::String email
DifferentialTypeVec options
Order matters!
Node::Ref wheels[2][2]
Ogre::String material_name
RoR::WheelSide side
Ogre::String mesh_name
RoR::WheelBraking braking
unsigned int num_rays
std::shared_ptr< BeamDefaults > beam_defaults
std::shared_ptr< NodeDefaults > node_defaults
Node::Ref rigidity_node
RoR::WheelPropulsion propulsion
Node::Ref nodes[2]
Node::Ref reference_arm_node
BeamDefaultsScale scale
bool _is_plastic_deform_coef_user_defined
Ogre::String beam_material_name
bool _is_user_defined
Informs whether these data were read from "set_beam_defaults" directive or filled in by the parser on...
bool _enable_advanced_deformation
Informs whether "enable_advanced_deformation" directive preceded these defaults.
static const BitMask_t OPTION_r_ROPE
BitMask_t options
bool _has_extension_break_limit
std::shared_ptr< BeamDefaults > defaults
float extension_break_limit
static const BitMask_t OPTION_i_INVISIBLE
Node::Ref nodes[2]
static const BitMask_t OPTION_s_SUPPORT
float default_braking_force
float parking_brake_force
static const BitMask_t OPTION_r_BUOYANT_ONLY_DRAG
static const BitMask_t OPTION_p_10xTOUGHER
static const BitMask_t OPTION_s_BUOYANT_NO_DRAG
static const BitMask_t OPTION_F_10xTOUGHER_BUOYANT
static const BitMask_t OPTION_b_BUOYANT
Node::Ref nodes[3]
static const BitMask_t OPTION_D_CONTACT_BUOYANT
static const BitMask_t OPTION_S_INVULNERABLE_BUOYANT
static const BitMask_t OPTION_c_CONTACT
BitMask_t options
static const BitMask_t OPTION_u_INVULNERABLE
Node::Ref left_node
Node::Ref center_node
Node::Ref back_node
Ogre::Vector3 position
std::shared_ptr< BeamDefaults > beam_defaults
Node::Ref nodes[8]
std::shared_ptr< NodeDefaults > node_defaults
std::vector< Node::Ref > nodes
Ogre::String description
std::shared_ptr< Inertia > inertia_defaults
Node::Ref nodes[2]
RoR::CommandkeyID_t extend_key
RoR::CommandkeyID_t contract_key
std::shared_ptr< BeamDefaults > beam_defaults
bool option_o_1press_center
std::string skin_name
< Represents a comment (line starting with ';' or '//') that can be anywhere in the file.
int commented_datapos
Position in the data vector for the given keyword.
std::string comment_text
May contain multiple lines separated by ' ' - MUST contain leading ';' or '//' at each line!
RigDef::Keyword commented_keyword
std::vector< float > gear_ratios
float global_gear_ratio
float neutral_gear_ratio
float reverse_gear_ratio
float shift_time
Seconds.
float post_shift_time
Seconds.
float clutch_time
Seconds.
Node::Ref direction_node
Node::Ref reference_node
Ogre::String particle_name
RoR::ExtCameraMode mode
Ogre::String unique_id
std::shared_ptr< Inertia > inertia_defaults
RoR::FlareType type
Ogre::String material_name
Node::Ref node_axis_x
Node::Ref reference_node
Ogre::Vector3 offset
int control_number
Only 'u' type flares.
std::string dashboard_link
Only 'd' type flares.
Node::Ref node_axis_y
int control_number
Only 'u' type flares.
Ogre::String tyre_mesh_name
RoR::WheelSide side
Ogre::String rim_mesh_name
std::vector< Node::Range > node_list_to_import
Ogre::String mesh_name
Node::Ref y_axis_node
Ogre::Vector3 rotation
Ogre::Vector3 offset
Node::Ref reference_node
Node::Ref x_axis_node
Manually specified (vert => node) bindings for flexbody deformation (overrides 'forset').
Node::Ref node_ref
Node::Ref node_y
Node::Ref node_x
Node::Ref rear_node
Ogre::String airfoil_name
Node::Ref front_node
Ogre::String material_name
std::string guid
std::string material
Node::Ref node
float option_max_force
bool flag_no_disable
bool flag_self_lock
float option_timer
float option_speed_coef
bool flag_auto_lock
float option_hook_range
float option_min_range_meters
static const BitMask_t OPTION_e_INPUT_ELEVATOR
std::shared_ptr< Inertia > inertia_defaults
float lenghtening_factor
static const BitMask_t OPTION_s_DISABLE_ON_HIGH_SPEED
BitMask_t options
static const BitMask_t OPTION_h_INPUT_InvELEVATOR_RUDDER
static const BitMask_t OPTION_g_INPUT_ELEVATOR_RUDDER
static const BitMask_t OPTION_y_INPUT_InvAILERON_RUDDER
Node::Ref nodes[2]
static const BitMask_t OPTION_j_INVISIBLE
static const BitMask_t OPTION_v_INPUT_InvAILERON_ELEVATOR
static const BitMask_t OPTION_a_INPUT_AILERON
static const BitMask_t OPTION_r_INPUT_RUDDER
static const BitMask_t OPTION_n_INPUT_NORMAL
static const BitMask_t OPTION_u_INPUT_AILERON_ELEVATOR
Inertia inertia
static const BitMask_t OPTION_x_INPUT_AILERON_RUDDER
std::shared_ptr< BeamDefaults > beam_defaults
Ogre::String start_function
float start_delay_factor
float stop_delay_factor
Ogre::String stop_function
DifferentialTypeVec options
Order matters!
std::vector< Node::Ref > nodes
ManagedMaterialType type
ManagedMaterialsOptions options
Ogre::String specular_map
Ogre::String damaged_diffuse_map
float global_min_mass_Kg
minimum node mass in Kg - only effective where DefaultMinimass was not set.
MinimassOption option
std::shared_ptr< BeamDefaults > beam_defaults
static const BitMask_t OPTION_c_NO_GROUND_CONTACT
static const BitMask_t OPTION_p_NO_PARTICLES
std::shared_ptr< DefaultMinimass > default_minimass
bool _has_load_weight_override
static const BitMask_t OPTION_f_NO_SPARKS
static const BitMask_t OPTION_b_EXTRA_BUOYANCY
BitMask_t options
static const BitMask_t OPTION_L_LOG
static const BitMask_t OPTION_x_EXHAUST_POINT
static const BitMask_t OPTION_e_TERRAIN_EDIT_POINT
std::shared_ptr< NodeDefaults > node_defaults
float load_weight_override
static const BitMask_t OPTION_h_HOOK_POINT
Ogre::Vector3 position
static const BitMask_t OPTION_m_NO_MOUSE_GRAB
static const BitMask_t OPTION_l_LOAD_WEIGHT
static const BitMask_t OPTION_y_EXHAUST_DIRECTION
Node::Ref emitter_node
Node::Ref reference_node
Ogre::String particle_system_name
Node::Ref reference_node
Ogre::String airfoil
Node::Ref couple_node
Node::Ref blade_tip_nodes[4]
Ogre::ColourValue color
Node::Ref reference_node
Node::Ref x_axis_node
DashboardSpecial special_prop_dashboard
Ogre::Vector3 offset
Ogre::String mesh_name
BeaconSpecial special_prop_beacon
Node::Ref y_axis_node
SpecialProp special
Ogre::Vector3 rotation
std::vector< Node::Range > node_list
unsigned int id
std::shared_ptr< BeamDefaults > beam_defaults
Node::Ref end_node
Node::Ref root_node
Ogre::String description
std::shared_ptr< Inertia > inertia_defaults
unsigned int spin_left_key
Node::Ref base_plate_nodes[4]
Node::Ref axis_nodes[2]
Node::Ref rotating_plate_nodes[4]
unsigned int spin_right_key
std::string filename
BitMask_t options
Node::Ref nodes[2]
static const BitMask_t OPTION_i_INVISIBLE
float short_bound
Maximum contraction limit, in percentage ( 1.00 = 100% )
float progress_factor_damp_out
Progression factor dampout, 0 = disabled, 1...x as multipliers, example:maximum dampingrate == spring...
float damp_out
damping value applied when shock extending
float progress_factor_damp_in
Progression factor for dampin. 0 = disabled, 1...x as multipliers, example:maximum dampingrate == spr...
std::shared_ptr< BeamDefaults > beam_defaults
static const BitMask_t OPTION_m_METRIC
float spring_in
Spring value applied when the shock is compressing.
static const BitMask_t OPTION_s_SOFT_BUMP_BOUNDS
float precompression
Changes compression or extension of the suspension when the truck spawns. This can be used to "level"...
float progress_factor_spring_out
Progression factor springout, 0 = disabled, 1...x as multipliers, example:maximum springrate == sprin...
static const BitMask_t OPTION_M_ABSOLUTE_METRIC
float spring_out
spring value applied when shock extending
float long_bound
Maximum extension limit, in percentage ( 1.00 = 100% )
float damp_in
Damping value applied when the shock is compressing.
float progress_factor_spring_in
Progression factor for springin. A value of 0 disables this option. 1...x as multipliers,...
float damp_in_slow
Damping value applied when shock is commpressing slower than split in velocity.
float spring_in
Spring value applied when the shock is compressing.
float spring_out
Spring value applied when shock extending.
static const BitMask_t OPTION_M_ABSOLUTE_METRIC
float damp_in
Damping value applied when the shock is compressing.
float split_vel_in
Split velocity in (m/s) - threshold for slow / fast damping during compression.
BitMask_t options
float precompression
Changes compression or extension of the suspension when the truck spawns. This can be used to "level"...
Node::Ref nodes[2]
static const BitMask_t OPTION_i_INVISIBLE
static const BitMask_t OPTION_m_METRIC
std::shared_ptr< BeamDefaults > beam_defaults
float damp_out
Damping value applied when shock extending.
float long_bound
Maximum extension limit, in percentage ( 1.00 = 100% )
float short_bound
Maximum contraction limit, in percentage ( 1.00 = 100% )
float damp_out_slow
Damping value applied when shock is commpressing slower than split out velocity.
float damp_out_fast
Damping value applied when shock is commpressing faster than split out velocity.
float damp_in_fast
Damping value applied when shock is commpressing faster than split in velocity.
float split_vel_out
Split velocity in (m/s) - threshold for slow / fast damping during extension.
static const BitMask_t OPTION_L_ACTIVE_LEFT
std::shared_ptr< BeamDefaults > beam_defaults
float damping
The 'resistance to motion' of the shock. The best value is given by this equation: 2 * sqrt(suspended...
float spring_rate
The 'stiffness' of the shock. The higher the value, the less the shock will move for a given bump.
static const BitMask_t OPTION_m_METRIC
float precompression
Changes compression or extension of the suspension when the truck spawns. This can be used to "level"...
Node::Ref nodes[2]
float short_bound
Maximum contraction. The shortest length the shock can be, as a proportion of its original length....
float long_bound
Maximum extension. The longest length a shock can be, as a proportion of its original length....
static const BitMask_t OPTION_i_INVISIBLE
static const BitMask_t OPTION_R_ACTIVE_RIGHT
BitMask_t options
static const BitMask_t CONSTRAINT_ATTACH_ALL
static const BitMask_t CONSTRAINT_ATTACH_NONE
static const BitMask_t CONSTRAINT_ATTACH_FOREIGN
static const BitMask_t CONSTRAINT_ATTACH_SELF
BitMask_t constraint_flags
std::vector< Node::Range > rail_node_ranges
int mode
A special constant or cinecam index.
Ogre::String sound_script_name
Node::Ref root_node
float auto_shorten_rate
static const BitMask_t OPTION_i_INVISIBLE
static const BitMask_t OPTION_s_DISABLE_SELF_LOCK
std::shared_ptr< BeamDefaults > beam_defaults
BitMask_t options
float max_reach_length
std::vector< float > gear_ratios
static const BitMask_t OPTION_B_TRIGGER_BLOCKER
static const BitMask_t OPTION_s_CMD_NUM_SWITCH
static const BitMask_t OPTION_h_UNLOCKS_HOOK_GROUP
static const BitMask_t OPTION_i_INVISIBLE
static const BitMask_t OPTION_t_CONTINUOUS
static const BitMask_t OPTION_E_ENGINE_TRIGGER
static const BitMask_t OPTION_c_COMMAND_STYLE
static const BitMask_t OPTION_b_KEY_BLOCKER
float contraction_trigger_limit
static const BitMask_t OPTION_x_START_DISABLED
std::shared_ptr< BeamDefaults > beam_defaults
BitMask_t options
float expansion_trigger_limit
Node::Ref nodes[2]
static const BitMask_t OPTION_H_LOCKS_HOOK_GROUP
static const BitMask_t OPTION_A_INV_TRIGGER_BLOCKER
int shortbound_trigger_action
Node::Ref front_node
Node::Ref back_node
Node::Ref side_node
Node::Ref blade_tip_nodes[4]
Node::Ref reference_node
Ogre::String airfoil
RoR::VideoCamRole camera_role
Ogre::String material_name
unsigned int texture_width
Ogre::String camera_name
Node::Ref alt_orientation_node
Node::Ref alt_reference_node
unsigned int texture_height
Ogre::Vector3 rotation
Ogre::Vector3 offset
Ogre::String face_material_name
Ogre::String band_material_name
Ogre::String band_material_name
Ogre::String face_material_name
float tex_coords[8]
float min_deflection
float efficacy_coef
WingControlSurface control_surface
Ogre::String airfoil
float max_deflection
Node::Ref nodes[8]