Rigs of Rods 2023.09
Soft-body Physics Simulation
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
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
1300 else
1301 {
1302 Animation::MotorSource motor_source;
1303 // aeroengines...
1304 if (entry[1].compare(0, 8, "throttle") == 0)
1305 {
1307 motor_source.motor = this->ParseArgUint(entry[1].substr(8));
1308 }
1309 else if (entry[1].compare(0, 3, "rpm") == 0)
1310 {
1312 motor_source.motor = this->ParseArgUint(entry[1].substr(3));
1313 }
1314 else if (entry[1].compare(0, 8, "aerotorq") == 0)
1315 {
1317 motor_source.motor = this->ParseArgUint(entry[1].substr(8));
1318 }
1319 else if (entry[1].compare(0, 7, "aeropit") == 0)
1320 {
1322 motor_source.motor = this->ParseArgUint(entry[1].substr(7));
1323 }
1324 else if (entry[1].compare(0, 10, "aerostatus") == 0)
1325 {
1327 motor_source.motor = this->ParseArgUint(entry[1].substr(10));
1328 }
1329 // gears... (hack)
1330 else if (entry[1].compare(0, 4, "gear") == 0)
1331 {
1333 motor_source.motor = this->ParseArgUint(entry[1].substr(4));
1334 }
1335 else
1336 {
1337 snprintf(warn_msg, WARN_LEN, "Invalid 'source': %s, ignoring...", entry[1].c_str());
1338 continue;
1339 }
1340 animation.motor_sources.push_back(motor_source);
1341 }
1342 }
1343 }
1344 else
1345 {
1346 snprintf(warn_msg, WARN_LEN, "Invalid keyword: %s, ignoring...", entry[0].c_str());
1347 }
1348 }
1349 else
1350 {
1351 snprintf(warn_msg, WARN_LEN, "Invalid item: %s, ignoring...", entry[0].c_str());
1352 }
1353
1354 if (warn_msg[0] != '\0')
1355 {
1357 fmt::format("Ignoring invalid token '{}' ({})", itor->c_str(), warn_msg));
1358 }
1359 }
1360
1361 m_current_module->props.back().animations.push_back(animation);
1362}
1363
1365{
1366 AntiLockBrakes alb;
1367 Ogre::StringVector tokens = Ogre::StringUtil::split(m_current_line + 15, ","); // "AntiLockBrakes " = 15 characters
1368 m_num_args = (int)tokens.size();
1369 if (! this->CheckNumArguments(2)) { return; }
1370
1371 alb.regulation_force = this->ParseArgFloat(tokens[0].c_str());
1372 alb.min_speed = this->ParseArgInt (tokens[1].c_str());
1373
1374 if (tokens.size() > 3) { alb.pulse_per_sec = this->ParseArgFloat(tokens[2].c_str()); }
1375
1376 for (unsigned int i=3; i<tokens.size(); i++)
1377 {
1378 Ogre::StringVector args2 = Ogre::StringUtil::split(tokens[i], ":");
1379 Ogre::StringUtil::trim(args2[0]);
1380 Ogre::StringUtil::toLowerCase(args2[0]);
1381 if (args2[0] == "mode" && args2.size() == 2)
1382 {
1383 Ogre::StringVector attrs = Ogre::StringUtil::split(args2[1], "&");
1384 auto itor = attrs.begin();
1385 auto endi = attrs.end();
1386 for (; itor != endi; ++itor)
1387 {
1388 std::string attr = *itor;
1389 Ogre::StringUtil::trim(attr);
1390 Ogre::StringUtil::toLowerCase(attr);
1391 if (strncmp(attr.c_str(), "nodash", 6) == 0) { alb.attr_no_dashboard = true; }
1392 else if (strncmp(attr.c_str(), "notoggle", 8) == 0) { alb.attr_no_toggle = true; }
1393 else if (strncmp(attr.c_str(), "on", 2) == 0) { alb.attr_is_on = true; }
1394 else if (strncmp(attr.c_str(), "off", 3) == 0) { alb.attr_is_on = false; }
1395 }
1396 }
1397 else
1398 {
1399 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "missing mode");
1400 alb.attr_no_dashboard = false;
1401 alb.attr_no_toggle = false;
1402 alb.attr_is_on = true;
1403 }
1404 }
1405
1406 m_current_module->antilockbrakes.push_back(alb);
1408}
1409
1411{
1412 if (! this->CheckNumArguments(1)) { return; }
1413
1414 Engoption engoption;
1415 engoption.inertia = this->GetArgFloat(0);
1416
1417 if (m_num_args > 1) { engoption.type = this->GetArgEngineType(1); }
1418 if (m_num_args > 2) { engoption.clutch_force = this->GetArgFloat(2); }
1419 if (m_num_args > 3) { engoption.shift_time = this->GetArgFloat(3); }
1420 if (m_num_args > 4) { engoption.clutch_time = this->GetArgFloat(4); }
1421 if (m_num_args > 5) { engoption.post_shift_time = this->GetArgFloat(5); }
1422 if (m_num_args > 6) { engoption.stall_rpm = this->GetArgFloat(6); }
1423 if (m_num_args > 7) { engoption.idle_rpm = this->GetArgFloat(7); }
1424 if (m_num_args > 8) { engoption.max_idle_mixture = this->GetArgFloat(8); }
1425 if (m_num_args > 9) { engoption.min_idle_mixture = this->GetArgFloat(9); }
1426 if (m_num_args > 10){ engoption.braking_torque = this->GetArgFloat(10);}
1427
1428 m_current_module->engoption.push_back(engoption);
1430}
1431
1433{
1434 if (! this->CheckNumArguments(4)) { return; }
1435
1436 Engturbo engturbo;
1437 engturbo.version = this->GetArgInt ( 0);
1438 engturbo.tinertiaFactor = this->GetArgFloat( 1);
1439 engturbo.nturbos = this->GetArgInt ( 2);
1440 engturbo.param1 = this->GetArgFloat( 3);
1441
1442 if (m_num_args > 4) { engturbo.param2 = this->GetArgFloat( 4); }
1443 if (m_num_args > 5) { engturbo.param3 = this->GetArgFloat( 5); }
1444 if (m_num_args > 6) { engturbo.param4 = this->GetArgFloat( 6); }
1445 if (m_num_args > 7) { engturbo.param5 = this->GetArgFloat( 7); }
1446 if (m_num_args > 8) { engturbo.param6 = this->GetArgFloat( 8); }
1447 if (m_num_args > 9) { engturbo.param7 = this->GetArgFloat( 9); }
1448 if (m_num_args > 10) { engturbo.param8 = this->GetArgFloat(10); }
1449 if (m_num_args > 11) { engturbo.param9 = this->GetArgFloat(11); }
1450 if (m_num_args > 12) { engturbo.param10 = this->GetArgFloat(12); }
1451 if (m_num_args > 13) { engturbo.param11 = this->GetArgFloat(13); }
1452
1453 if (engturbo.nturbos > 4)
1454 {
1455 this->LogMessage(Console::CONSOLE_SYSTEM_WARNING, "You cannot have more than 4 turbos. Fallback: using 4 instead.");
1456 engturbo.nturbos = 4;
1457 }
1458
1459 m_current_module->engturbo.push_back(engturbo);
1461}
1462
1464{
1465 if (! this->CheckNumArguments(6)) { return; }
1466
1467 Engine engine;
1468 engine.shift_down_rpm = this->GetArgFloat(0);
1469 engine.shift_up_rpm = this->GetArgFloat(1);
1470 engine.torque = this->GetArgFloat(2);
1471 engine.global_gear_ratio = this->GetArgFloat(3);
1472 engine.reverse_gear_ratio = this->GetArgFloat(4);
1473 engine.neutral_gear_ratio = this->GetArgFloat(5);
1474
1475 // Forward gears
1476 for (int i = 6; i < m_num_args; i++)
1477 {
1478 float ratio = this->GetArgFloat(i);
1479 if (ratio < 0.f)
1480 {
1481 break; // Optional terminator argument
1482 }
1483 engine.gear_ratios.push_back(ratio);
1484 }
1485
1486 if (engine.gear_ratios.size() == 0)
1487 {
1488 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "no forward gear");
1489 return;
1490 }
1491
1492 m_current_module->engine.push_back(engine);
1494}
1495
1497{
1498 if (! this->CheckNumArguments(1)) { return; }
1499
1500 m_current_module->contacters.push_back(this->GetArgNodeRef(0));
1502}
1503
1505{
1506 if (!this->CheckNumArguments(2)) { return; }
1507
1508 CustomDashboardInput dashVal;
1509 dashVal.name = this->GetArgStr(0);
1510 dashVal.data_type = this->GetArgDashboardInputDataType(1);
1511 if (dashVal.data_type == -1)
1512 {
1514 fmt::format("Dashboard value \"{}\" has an invalid data type. Ignoring it.", dashVal.name));
1515 return;
1516 }
1517
1518 m_current_module->customdashboardinputs.push_back(dashVal);
1520}
1521
1523{
1524 const bool is_commands2 = (m_current_block == Keyword::COMMANDS2);
1525 const int max_args = (is_commands2 ? 8 : 7);
1526 if (! this->CheckNumArguments(max_args)) { return; }
1527
1528 Command2 command2;
1532
1533 int pos = 0;
1534 command2.nodes[0] = this->GetArgNodeRef(pos++);
1535 command2.nodes[1] = this->GetArgNodeRef(pos++);
1536 command2.shorten_rate = this->GetArgFloat (pos++);
1537
1538 if (is_commands2)
1539 {
1540 command2.lengthen_rate = this->GetArgFloat(pos++);
1541 }
1542 else
1543 {
1544 command2.lengthen_rate = command2.shorten_rate;
1545 }
1546
1547 command2.max_contraction = this->GetArgFloat(pos++);
1548 command2.max_extension = this->GetArgFloat(pos++);
1549 command2.contract_key = this->GetArgInt (pos++);
1550 command2.extend_key = this->GetArgInt (pos++);
1551
1552 if (m_num_args <= max_args) // No more args?
1553 {
1554 m_current_module->commands2.push_back(command2);
1555 return;
1556 }
1557
1558 // Parse options
1559 std::string options_str = this->GetArgStr(pos++);
1560 char winner = 0;
1561 for (auto itor = options_str.begin(); itor != options_str.end(); ++itor)
1562 {
1563 const char c = *itor;
1564 if ((winner == 0) && (c == 'o' || c == 'p' || c == 'c')) { winner = c; }
1565
1566 if (c == 'n') {} // Filler, does nothing
1567 else if (c == 'i') { command2.option_i_invisible = true; }
1568 else if (c == 'r') { command2.option_r_rope = true; }
1569 else if (c == 'f') { command2.option_f_not_faster = true; }
1570 else if (c == 'c') { command2.option_c_auto_center = true; }
1571 else if (c == 'p') { command2.option_p_1press = true; }
1572 else if (c == 'o') { command2.option_o_1press_center = true; }
1573 else
1574 {
1576 fmt::format("ignoring unknown flag '{}'", c));
1577 }
1578 }
1579
1580 // Resolve option conflicts
1581 if (command2.option_c_auto_center && winner != 'c' && winner != 0)
1582 {
1583 this->LogMessage(Console::CONSOLE_SYSTEM_WARNING, "Command cannot be one-pressed and self centering at the same time, ignoring flag 'c'");
1584 command2.option_c_auto_center = false;
1585 }
1586 char ignored = '\0';
1587 if (command2.option_o_1press_center && winner != 'o' && winner != 0)
1588 {
1589 command2.option_o_1press_center = false;
1590 ignored = 'o';
1591 }
1592 else if (command2.option_p_1press && winner != 'p' && winner != 0)
1593 {
1594 command2.option_p_1press = false;
1595 ignored = 'p';
1596 }
1597
1598 // Report conflicts
1599 if (ignored != 0 && winner == 'c')
1600 {
1602 "Command cannot be one-pressed and self centering at the same time, ignoring flag '%c'");
1603 }
1604 else if (ignored != 0 && (winner == 'o' || winner == 'p'))
1605 {
1607 "Command already has a one-pressed c.mode, ignoring flag '%c'");
1608 }
1609
1610 if (m_num_args > pos) { command2.description = this->GetArgStr (pos++);}
1611
1612 if (m_num_args > pos) { ParseOptionalInertia(command2.inertia, pos); pos += 4; }
1613
1614 if (m_num_args > pos) { command2.affect_engine = this->GetArgFloat(pos++);}
1615 if (m_num_args > pos) { command2.needs_engine = this->GetArgBool (pos++);}
1616 if (m_num_args > pos) { command2.plays_sound = this->GetArgBool (pos++);}
1617
1618 m_current_module->commands2.push_back(command2);
1620}
1621
1623{
1624 CollisionBox collisionbox;
1625
1626 Ogre::StringVector tokens = Ogre::StringUtil::split(m_current_line, ",");
1627 Ogre::StringVector::iterator iter = tokens.begin();
1628 for ( ; iter != tokens.end(); iter++)
1629 {
1630 collisionbox.nodes.push_back( this->_ParseNodeRef(*iter) );
1631 }
1632
1633 m_current_module->collisionboxes.push_back(collisionbox);
1635}
1636
1638{
1639 if (! this->CheckNumArguments(11)) { return; }
1640
1641 Cinecam cinecam;
1644
1645 // Required arguments
1646 cinecam.position.x = this->GetArgFloat ( 0);
1647 cinecam.position.y = this->GetArgFloat ( 1);
1648 cinecam.position.z = this->GetArgFloat ( 2);
1649 cinecam.nodes[0] = this->GetArgNodeRef( 3);
1650 cinecam.nodes[1] = this->GetArgNodeRef( 4);
1651 cinecam.nodes[2] = this->GetArgNodeRef( 5);
1652 cinecam.nodes[3] = this->GetArgNodeRef( 6);
1653 cinecam.nodes[4] = this->GetArgNodeRef( 7);
1654 cinecam.nodes[5] = this->GetArgNodeRef( 8);
1655 cinecam.nodes[6] = this->GetArgNodeRef( 9);
1656 cinecam.nodes[7] = this->GetArgNodeRef(10);
1657
1658 // Optional arguments
1659 if (m_num_args > 11) { cinecam.spring = this->GetArgFloat(11); }
1660 if (m_num_args > 12) { cinecam.damping = this->GetArgFloat(12); }
1661
1662 if (m_num_args > 13)
1663 {
1664 float value = this->GetArgFloat(13);
1665 if (value > 0.f) // Invalid input (for example illegal trailing ";pseudo-comment") parses as 0
1666 cinecam.node_mass = value;
1667 }
1668
1670 {
1672 }
1673
1674 m_current_module->cinecam.push_back(cinecam);
1676}
1677
1679{
1680 m_current_camera_rail->nodes.push_back( this->GetArgNodeRef(0) );
1681}
1682
1684{
1685 if (!this->CheckNumArguments(1)) { return; }
1686
1687 Brakes brakes;
1688 brakes.default_braking_force = this->GetArgFloat(0);
1689 if (m_num_args > 1)
1690 {
1691 brakes.parking_brake_force = this->GetArgFloat(1);
1692 }
1693 m_current_module->brakes.push_back(brakes);
1695}
1696
1698{
1699 Axle axle;
1700
1701 Ogre::StringVector tokens = Ogre::StringUtil::split(m_current_line, ",");
1702 Ogre::StringVector::iterator iter = tokens.begin();
1703 for ( ; iter != tokens.end(); iter++)
1704 {
1705 std::smatch results;
1706 if (! std::regex_search(*iter, results, Regexes::SECTION_AXLES_PROPERTY))
1707 {
1708 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "Invalid property, ignoring whole line...");
1709 return;
1710 }
1711 // NOTE: Positions in 'results' array match E_CAPTURE*() positions (starting with 1) in the respective regex.
1712
1713 if (results[1].matched)
1714 {
1715 unsigned int wheel_index = PARSEINT(results[2]) - 1;
1716 axle.wheels[wheel_index][0] = _ParseNodeRef(results[3]);
1717 axle.wheels[wheel_index][1] = _ParseNodeRef(results[4]);
1718 }
1719 else if (results[5].matched)
1720 {
1721 this->_ParseDifferentialTypes(axle.options, results[6].str());
1722 }
1723 }
1724
1725 m_current_module->axles.push_back(axle);
1727}
1728
1730{
1731 auto args = Ogre::StringUtil::split(m_current_line, ",");
1732 if (args.size() < 2) { return; }
1733
1734 InterAxle interaxle;
1735
1736 interaxle.a1 = this->ParseArgInt(args[0].c_str()) - 1;
1737 interaxle.a2 = this->ParseArgInt(args[1].c_str()) - 1;
1738
1739 std::smatch results;
1740 if (! std::regex_search(args[2], results, Regexes::SECTION_AXLES_PROPERTY))
1741 {
1742 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "Invalid property, ignoring whole line...");
1743 return;
1744 }
1745 // NOTE: Positions in 'results' array match E_CAPTURE*() positions (starting with 1) in the respective regex.
1746
1747 if (results[5].matched)
1748 {
1749 this->_ParseDifferentialTypes(interaxle.options, results[6].str());
1750 }
1751
1752 m_current_module->interaxles.push_back(interaxle);
1754}
1755
1757{
1758 if (! this->CheckNumArguments(14)) { return; }
1759
1760 Airbrake airbrake;
1761 airbrake.reference_node = this->GetArgNodeRef( 0);
1762 airbrake.x_axis_node = this->GetArgNodeRef( 1);
1763 airbrake.y_axis_node = this->GetArgNodeRef( 2);
1764 airbrake.aditional_node = this->GetArgNodeRef( 3);
1765 airbrake.offset.x = this->GetArgFloat ( 4);
1766 airbrake.offset.y = this->GetArgFloat ( 5);
1767 airbrake.offset.z = this->GetArgFloat ( 6);
1768 airbrake.width = this->GetArgFloat ( 7);
1769 airbrake.height = this->GetArgFloat ( 8);
1770 airbrake.max_inclination_angle = this->GetArgFloat ( 9);
1771 airbrake.texcoord_x1 = this->GetArgFloat (10);
1772 airbrake.texcoord_y1 = this->GetArgFloat (11);
1773 airbrake.texcoord_x2 = this->GetArgFloat (12);
1774 airbrake.texcoord_y2 = this->GetArgFloat (13);
1775
1776 m_current_module->airbrakes.push_back(airbrake);
1778}
1779
1781{
1782 if (! this->CheckNumArguments(1)) { return; }
1783
1784 Assetpack assetpack;
1785
1786 assetpack.filename = this->GetArgStr(0);
1787
1788 m_current_module->assetpacks.push_back(assetpack);
1790}
1791
1793{
1794 if (! this->CheckNumArguments(19)) { return; }
1795
1796 VideoCamera videocamera;
1797
1798 videocamera.reference_node = this->GetArgNodeRef ( 0);
1799 videocamera.left_node = this->GetArgNodeRef ( 1);
1800 videocamera.bottom_node = this->GetArgNodeRef ( 2);
1801 videocamera.alt_reference_node = this->GetArgNullableNode( 3);
1802 videocamera.alt_orientation_node = this->GetArgNullableNode( 4);
1803 videocamera.offset.x = this->GetArgFloat ( 5);
1804 videocamera.offset.y = this->GetArgFloat ( 6);
1805 videocamera.offset.z = this->GetArgFloat ( 7);
1806 videocamera.rotation.x = this->GetArgFloat ( 8);
1807 videocamera.rotation.y = this->GetArgFloat ( 9);
1808 videocamera.rotation.z = this->GetArgFloat (10);
1809 videocamera.field_of_view = this->GetArgFloat (11);
1810 videocamera.texture_width = this->GetArgInt (12);
1811 videocamera.texture_height = this->GetArgInt (13);
1812 videocamera.min_clip_distance = this->GetArgFloat (14);
1813 videocamera.max_clip_distance = this->GetArgFloat (15);
1814 videocamera.camera_role = this->GetArgVideoCamRole(16);
1815 videocamera.camera_mode = this->GetArgInt (17);
1816 videocamera.material_name = this->GetArgStr (18);
1817
1818 if (m_num_args > 19) { videocamera.camera_name = this->GetArgStr(19); }
1819
1820 m_current_module->videocameras.push_back(videocamera);
1822}
1823
1825{
1826 if (! this->CheckNumArguments(3)) { return; }
1827
1828 Camera camera;
1829 camera.center_node = this->GetArgNodeRef(0);
1830 camera.back_node = this->GetArgNodeRef(1);
1831 camera.left_node = this->GetArgNodeRef(2);
1832
1833 m_current_module->cameras.push_back(camera);
1835}
1836
1838{
1839 bool is_turboprop_2 = m_current_block == Keyword::TURBOPROPS2;
1840
1841 if (! this->CheckNumArguments(is_turboprop_2 ? 9 : 8)) { return; }
1842
1843 Turboprop2 turboprop;
1844
1845 turboprop.reference_node = this->GetArgNodeRef(0);
1846 turboprop.axis_node = this->GetArgNodeRef(1);
1847 turboprop.blade_tip_nodes[0] = this->GetArgNodeRef(2);
1848 turboprop.blade_tip_nodes[1] = this->GetArgNodeRef(3);
1849 turboprop.blade_tip_nodes[2] = this->GetArgNullableNode(4);
1850 turboprop.blade_tip_nodes[3] = this->GetArgNullableNode(5);
1851
1852 int offset = 0;
1853
1854 if (is_turboprop_2)
1855 {
1856 turboprop.couple_node = this->GetArgNullableNode(6);
1857
1858 offset = 1;
1859 }
1860
1861 turboprop.turbine_power_kW = this->GetArgFloat (6 + offset);
1862 turboprop.airfoil = this->GetArgStr (7 + offset);
1863
1864 m_current_module->turboprops2.push_back(turboprop);
1866}
1867
1869{
1870 if (! this->CheckNumArguments(9)) { return; }
1871
1872 Turbojet turbojet;
1873 turbojet.front_node = this->GetArgNodeRef(0);
1874 turbojet.back_node = this->GetArgNodeRef(1);
1875 turbojet.side_node = this->GetArgNodeRef(2);
1876 turbojet.is_reversable = this->GetArgInt (3);
1877 turbojet.dry_thrust = this->GetArgFloat (4);
1878 turbojet.wet_thrust = this->GetArgFloat (5);
1879 turbojet.front_diameter = this->GetArgFloat (6);
1880 turbojet.back_diameter = this->GetArgFloat (7);
1881 turbojet.nozzle_length = this->GetArgFloat (8);
1882
1883 m_current_module->turbojets.push_back(turbojet);
1885}
1886
1888{
1889 if (! this->CheckNumArguments(6)) { return; }
1890
1891 Trigger trigger;
1894 trigger.nodes[0] = this->GetArgNodeRef(0);
1895 trigger.nodes[1] = this->GetArgNodeRef(1);
1896 trigger.contraction_trigger_limit = this->GetArgFloat (2);
1897 trigger.expansion_trigger_limit = this->GetArgFloat (3);
1898 trigger.shortbound_trigger_action = this->GetArgInt (4);
1899 trigger.longbound_trigger_action = this->GetArgInt (5);
1900 if (m_num_args > 6) trigger.options = this->GetArgTriggerOptions(6);
1901 if (m_num_args > 7) trigger.boundary_timer = this->GetArgFloat(7);
1902
1903 m_current_module->triggers.push_back(trigger);
1905}
1906
1908{
1909 if (m_current_module->torquecurve.size() == 0)
1910 {
1911 m_current_module->torquecurve.push_back(TorqueCurve());
1912 }
1913
1914 Ogre::StringVector args = Ogre::StringUtil::split(m_current_line, ",");
1915
1916 if (args.size() == 1u)
1917 {
1918 m_current_module->torquecurve[0].predefined_func_name = args[0];
1919 }
1920 else if (args.size() == 2u)
1921 {
1922 TorqueCurve::Sample sample;
1923 sample.power = this->ParseArgFloat(args[0].c_str());
1924 sample.torque_percent = this->ParseArgFloat(args[1].c_str());
1925 m_current_module->torquecurve[0].samples.push_back(sample);
1926 }
1927 else
1928 {
1929 // Consistent with 0.38's parser.
1930 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "too many arguments, skipping");
1931 }
1932}
1933
1935{
1936 if (! this->CheckNumArguments(5)) { return; }
1937
1938 Tie tie;
1941
1942 tie.root_node = this->GetArgNodeRef(0);
1943 tie.max_reach_length = this->GetArgFloat (1);
1944 tie.auto_shorten_rate = this->GetArgFloat (2);
1945 tie.min_length = this->GetArgFloat (3);
1946 tie.max_length = this->GetArgFloat (4);
1947
1948 if (m_num_args > 5)
1949 {
1950 for (char c: this->GetArgStr(5))
1951 {
1952 switch (c)
1953 {
1954 case (char)TieOption::n_DUMMY:
1955 case (char)TieOption::v_DUMMY:
1956 break;
1957
1958 case (char)TieOption::i_INVISIBLE:
1960 break;
1961
1962 case (char)TieOption::s_NO_SELF_LOCK:
1964 break;
1965
1966 default:
1968 fmt::format("ignoring invalid option '{}'", c));
1969 break;
1970 }
1971 }
1972 }
1973
1974 if (m_num_args > 6) { tie.max_stress = this->GetArgFloat (6); }
1975 if (m_num_args > 7) { tie.group = this->GetArgInt (7); }
1976
1977 m_current_module->ties.push_back(tie);
1979}
1980
1982{
1983 if (! this->CheckNumArguments(2)) { return; }
1984
1985 SoundSource soundsource;
1986 soundsource.node = this->GetArgNodeRef(0);
1987 soundsource.sound_script_name = this->GetArgStr(1);
1988
1989 m_current_module->soundsources.push_back(soundsource);
1991}
1992
1994{
1995 if (! this->CheckNumArguments(3)) { return; }
1996
1997 SoundSource2 soundsource2;
1998 soundsource2.node = this->GetArgNodeRef(0);
1999 soundsource2.mode = this->GetArgInt(1);
2000 soundsource2.sound_script_name = this->GetArgStr(2);
2001
2002 if (soundsource2.mode < -2)
2003 {
2005 fmt::format("invalid mode {}, falling back to default -2", soundsource2.mode));
2006 soundsource2.mode = -2;
2007 }
2008
2009 m_current_module->soundsources2.push_back(soundsource2);
2011}
2012
2014{
2015 Ogre::StringVector args = Ogre::StringUtil::split(m_current_line, ", ");
2016 m_num_args = (int)args.size();
2017 if (! this->CheckNumArguments(2)) { return; }
2018
2019 SlideNode slidenode;
2020 slidenode.slide_node = this->_ParseNodeRef(args[0]);
2021
2022 bool in_rail_node_list = true;
2023
2024 for (auto itor = args.begin() + 1; itor != args.end(); ++itor)
2025 {
2026 char c = toupper(itor->at(0));
2027 switch (c)
2028 {
2029 case 'S':
2030 slidenode.spring_rate = this->ParseArgFloat(itor->substr(1));
2031 slidenode._spring_rate_set = true;
2032 in_rail_node_list = false;
2033 break;
2034 case 'B':
2035 slidenode.break_force = this->ParseArgFloat(itor->substr(1));
2036 slidenode._break_force_set = true;
2037 in_rail_node_list = false;
2038 break;
2039 case 'T':
2040 slidenode.tolerance = this->ParseArgFloat(itor->substr(1));
2041 slidenode._tolerance_set = true;
2042 in_rail_node_list = false;
2043 break;
2044 case 'R':
2045 slidenode.attachment_rate = this->ParseArgFloat(itor->substr(1));
2046 slidenode._attachment_rate_set = true;
2047 in_rail_node_list = false;
2048 break;
2049 case 'G':
2050 slidenode.railgroup_id = this->ParseArgFloat(itor->substr(1));
2051 slidenode._railgroup_id_set = true;
2052 in_rail_node_list = false;
2053 break;
2054 case 'D':
2055 slidenode.max_attach_dist = this->ParseArgFloat(itor->substr(1));
2056 slidenode._max_attach_dist_set = true;
2057 in_rail_node_list = false;
2058 break;
2059 case 'C':
2060 switch (itor->at(1))
2061 {
2062 case 'a':
2064 break;
2065 case 'f':
2067 break;
2068 case 's':
2070 break;
2071 case 'n':
2073 break;
2074 default:
2076 fmt::format("Ignoring invalid option '{}'", itor->at(1)));
2077 break;
2078 }
2079 in_rail_node_list = false;
2080 break;
2081 default:
2082 if (in_rail_node_list)
2083 slidenode.rail_node_ranges.push_back( _ParseNodeRef(*itor));
2084 break;
2085 }
2086 }
2087
2088 m_current_module->slidenodes.push_back(slidenode);
2090}
2091
2093{
2094 if (! this->CheckNumArguments(15)) { return; }
2095
2096 Shock3 shock_3;
2099
2100 shock_3.nodes[0] = this->GetArgNodeRef( 0);
2101 shock_3.nodes[1] = this->GetArgNodeRef( 1);
2102 shock_3.spring_in = this->GetArgFloat ( 2);
2103 shock_3.damp_in = this->GetArgFloat ( 3);
2104 shock_3.damp_in_slow = this->GetArgFloat ( 4);
2105 shock_3.split_vel_in = this->GetArgFloat ( 5);
2106 shock_3.damp_in_fast = this->GetArgFloat ( 6);
2107 shock_3.spring_out = this->GetArgFloat ( 7);
2108 shock_3.damp_out = this->GetArgFloat ( 8);
2109 shock_3.damp_out_slow = this->GetArgFloat ( 9);
2110 shock_3.split_vel_out = this->GetArgFloat (10);
2111 shock_3.damp_out_fast = this->GetArgFloat (11);
2112 shock_3.short_bound = this->GetArgFloat (12);
2113 shock_3.long_bound = this->GetArgFloat (13);
2114 shock_3.precompression = this->GetArgFloat (14);
2115
2116 if (m_num_args > 15) shock_3.options = this->GetArgShock3Options(15);
2117
2118 m_current_module->shocks3.push_back(shock_3);
2120}
2121
2123{
2124 if (! this->CheckNumArguments(13)) { return; }
2125
2126 Shock2 shock_2;
2129
2130 shock_2.nodes[0] = this->GetArgNodeRef( 0);
2131 shock_2.nodes[1] = this->GetArgNodeRef( 1);
2132 shock_2.spring_in = this->GetArgFloat ( 2);
2133 shock_2.damp_in = this->GetArgFloat ( 3);
2134 shock_2.progress_factor_spring_in = this->GetArgFloat ( 4);
2135 shock_2.progress_factor_damp_in = this->GetArgFloat ( 5);
2136 shock_2.spring_out = this->GetArgFloat ( 6);
2137 shock_2.damp_out = this->GetArgFloat ( 7);
2138 shock_2.progress_factor_spring_out = this->GetArgFloat ( 8);
2139 shock_2.progress_factor_damp_out = this->GetArgFloat ( 9);
2140 shock_2.short_bound = this->GetArgFloat (10);
2141 shock_2.long_bound = this->GetArgFloat (11);
2142 shock_2.precompression = this->GetArgFloat (12);
2143
2144 if (m_num_args > 13) shock_2.options = this->GetArgShock2Options(13);
2145
2146 m_current_module->shocks2.push_back(shock_2);
2148}
2149
2151{
2152 if (! this->CheckNumArguments(7)) { return; }
2153
2154 Shock shock;
2157
2158 shock.nodes[0] = this->GetArgNodeRef(0);
2159 shock.nodes[1] = this->GetArgNodeRef(1);
2160 shock.spring_rate = this->GetArgFloat (2);
2161 shock.damping = this->GetArgFloat (3);
2162 shock.short_bound = this->GetArgFloat (4);
2163 shock.long_bound = this->GetArgFloat (5);
2164 shock.precompression = this->GetArgFloat (6);
2165 if (m_num_args > 7) shock.options = this->GetArgShockOptions(7);
2166
2167 m_current_module->shocks.push_back(shock);
2169}
2170
2171Node::Ref Parser::_ParseNodeRef(std::string const & node_id_str)
2172{
2174 {
2175 // Import of legacy fileformatversion
2176 int node_id_num = PARSEINT(node_id_str);
2177 if (node_id_num < 0)
2178 {
2179 node_id_num *= -1;
2180 }
2181 // Since fileformatversion is not known from the beginning of parsing, 2 states must be kept
2182 // at the same time: IMPORT_STATE and REGULAR_STATE. The outer logic must make the right pick.
2183 unsigned int flags = Node::Ref::IMPORT_STATE_IS_VALID | // Import state
2184 Node::Ref::REGULAR_STATE_IS_VALID | Node::Ref::REGULAR_STATE_IS_NAMED; // Regular state (fileformatversion >= 450)
2186 {
2187 flags |= Node::Ref::IMPORT_STATE_MUST_CHECK_NAMED_FIRST;
2188 }
2189 return Node::Ref(node_id_str, node_id_num, flags, m_current_line_number);
2190 }
2191 else
2192 {
2193 // fileformatversion >= 450, use named-only nodes
2194 return Node::Ref(node_id_str, 0, Node::Ref::REGULAR_STATE_IS_VALID | Node::Ref::REGULAR_STATE_IS_NAMED, m_current_line_number);
2195 }
2196}
2197
2199{
2200 if (! this->CheckNumArguments(2)) { return; } // Directive name + parameter
2201
2202 m_set_default_minimass = std::shared_ptr<DefaultMinimass>(new DefaultMinimass());
2203 m_set_default_minimass->min_mass_Kg = this->GetArgFloat(1);
2204}
2205
2207{
2208 // ;flare_groups_no_import
2209 // ; <flaretype> <controlnumber 1-10>
2210 if (!this->CheckNumArguments(1)) { return; }
2211
2213 fni.type = this->GetArgFlareType(0);
2214 if (m_num_args > 1)
2215 {
2216 fni.control_number = this->GetArgInt(1);
2217 if (fni.control_number < 1)
2218 {
2219 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));
2220 }
2221 if (fni.control_number > 10)
2222 {
2223 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));
2224 }
2225 }
2226
2227 LOG(fmt::format("[RoR|RigDef::Parser] parsed FlaregroupNoImport ({} {})", (char)fni.type, fni.control_number));
2228 m_current_module->flaregroups_no_import.push_back(fni);
2229}
2230
2232{
2233 if (! this->CheckNumArguments(2)) { return; }
2234
2235 float start_delay = this->GetArgFloat(1);
2236 float stop_delay = 0;
2237 if (m_num_args > 2) { stop_delay = this->GetArgFloat(2); }
2238
2239 if (start_delay < 0 || stop_delay < 0)
2240 {
2241 m_user_default_inertia = m_ror_default_inertia; // Reset and return
2242 return;
2243 }
2244
2245 // Create
2246 Inertia* i = new Inertia(*m_user_default_inertia.get());
2247 i->start_delay_factor = start_delay;
2248 i->stop_delay_factor = stop_delay;
2249
2250 if (m_num_args > 3) { i->start_function = this->GetArgStr(3); }
2251 if (m_num_args > 4) { i->stop_function = this->GetArgStr(4); }
2252
2253 m_user_default_inertia = std::shared_ptr<Inertia>(i);
2254}
2255
2257{
2258 if (! this->CheckNumArguments(4)) { return; }
2259
2260 Screwprop screwprop;
2261
2262 screwprop.prop_node = this->GetArgNodeRef(0);
2263 screwprop.back_node = this->GetArgNodeRef(1);
2264 screwprop.top_node = this->GetArgNodeRef(2);
2265 screwprop.power = this->GetArgFloat (3);
2266
2267 m_current_module->screwprops.push_back(screwprop);
2269}
2270
2272{
2273 if (!this->CheckNumArguments(1)) { return; }
2274
2275 Script script;
2276
2277 script.filename = this->GetArgStr(0);
2278
2279 m_current_module->scripts.push_back(script);
2281}
2282
2284{
2285 if (! this->CheckNumArguments(13)) { return; }
2286
2287 Rotator2 rotator;
2289
2290 rotator.axis_nodes[0] = this->GetArgNodeRef( 0);
2291 rotator.axis_nodes[1] = this->GetArgNodeRef( 1);
2292 rotator.base_plate_nodes[0] = this->GetArgNodeRef( 2);
2293 rotator.base_plate_nodes[1] = this->GetArgNodeRef( 3);
2294 rotator.base_plate_nodes[2] = this->GetArgNodeRef( 4);
2295 rotator.base_plate_nodes[3] = this->GetArgNodeRef( 5);
2296 rotator.rotating_plate_nodes[0] = this->GetArgNodeRef( 6);
2297 rotator.rotating_plate_nodes[1] = this->GetArgNodeRef( 7);
2298 rotator.rotating_plate_nodes[2] = this->GetArgNodeRef( 8);
2299 rotator.rotating_plate_nodes[3] = this->GetArgNodeRef( 9);
2300 rotator.rate = this->GetArgFloat (10);
2301 rotator.spin_left_key = this->GetArgInt (11);
2302 rotator.spin_right_key = this->GetArgInt (12);
2303
2304 int offset = 0;
2305
2307 {
2308 if (! this->CheckNumArguments(16)) { return; }
2309 if (m_num_args > 13) { rotator.rotating_force = this->GetArgFloat(13); }
2310 if (m_num_args > 14) { rotator.tolerance = this->GetArgFloat(14); }
2311 if (m_num_args > 15) { rotator.description = this->GetArgStr (15); }
2312
2313 offset = 3;
2314 }
2315
2316 this->ParseOptionalInertia(rotator.inertia, 13 + offset);
2317 if (m_num_args > 17 + offset) { rotator.engine_coupling = this->GetArgFloat(17 + offset); }
2318 if (m_num_args > 18 + offset) { rotator.needs_engine = this->GetArgBool (18 + offset); }
2319
2321 {
2322 m_current_module->rotators2.push_back(rotator);
2324 }
2325 else
2326 {
2327 m_current_module->rotators.push_back(rotator);
2329 }
2330}
2331
2333{
2334 if (! this->CheckNumArguments(2)) { return; }
2335
2336 Fileinfo fileinfo;
2337
2338 fileinfo.unique_id = this->GetArgStr(1);
2339 Ogre::StringUtil::trim(fileinfo.unique_id);
2340
2341 if (m_num_args > 2) { fileinfo.category_id = this->GetArgInt(2); }
2342 if (m_num_args > 3) { fileinfo.file_version = this->GetArgInt(3); }
2343
2344 m_current_module->fileinfo.push_back(fileinfo);
2345
2347}
2348
2350{
2351 if (! this->CheckNumArguments(2)) { return; }
2352
2353 Rope rope;
2356 rope.root_node = this->GetArgNodeRef(0);
2357 rope.end_node = this->GetArgNodeRef(1);
2358
2359 if (m_num_args > 2) { rope.invisible = (this->GetArgChar(2) == 'i'); }
2360
2361 m_current_module->ropes.push_back(rope);
2363}
2364
2366{
2367 if (! this->CheckNumArguments(1)) { return; }
2368
2369 Ropable ropable;
2370 ropable.node = this->GetArgNodeRef(0);
2371
2372 if (m_num_args > 1) { ropable.group = this->GetArgInt(1); }
2373 if (m_num_args > 2) { ropable.has_multilock = (this->GetArgInt(2) == 1); }
2374
2375 m_current_module->ropables.push_back(ropable);
2377}
2378
2380{
2381 Ogre::StringVector args = Ogre::StringUtil::split(m_current_line, ",");
2382 m_num_args = (int)args.size();
2383 if (! this->CheckNumArguments(3)) { return; }
2384
2385 RailGroup railgroup;
2386 railgroup.id = this->ParseArgInt(args[0].c_str());
2387
2388 for (auto itor = args.begin() + 1; itor != args.end(); itor++)
2389 {
2390 railgroup.node_list.push_back( this->_ParseNodeRef(*itor));
2391 }
2392
2393 m_current_module->railgroups.push_back(railgroup);
2395}
2396
2398{
2399 if (! this->CheckNumArguments(10)) { return; }
2400
2401 Prop prop;
2402 prop.reference_node = this->GetArgNodeRef(0);
2403 prop.x_axis_node = this->GetArgNodeRef(1);
2404 prop.y_axis_node = this->GetArgNodeRef(2);
2405 prop.offset.x = this->GetArgFloat (3);
2406 prop.offset.y = this->GetArgFloat (4);
2407 prop.offset.z = this->GetArgFloat (5);
2408 prop.rotation.x = this->GetArgFloat (6);
2409 prop.rotation.y = this->GetArgFloat (7);
2410 prop.rotation.z = this->GetArgFloat (8);
2411 prop.mesh_name = this->GetArgStr(9);
2413
2414 if ((prop.special == SpecialProp::BEACON) && (m_num_args >= 14))
2415 {
2417 Ogre::StringUtil::trim(prop.special_prop_beacon.flare_material_name);
2418
2419 prop.special_prop_beacon.color = Ogre::ColourValue(
2420 this->GetArgFloat(11), this->GetArgFloat(12), this->GetArgFloat(13));
2421 }
2422 else if (prop.special == SpecialProp::DASHBOARD_LEFT ||
2424 {
2425 if (m_num_args > 10) prop.special_prop_dashboard.mesh_name = this->GetArgStr(10);
2426 if (m_num_args > 13)
2427 {
2428 prop.special_prop_dashboard.offset = Ogre::Vector3(this->GetArgFloat(11), this->GetArgFloat(12), this->GetArgFloat(13));
2430 }
2432 }
2433
2434 m_current_module->props.push_back(prop);
2436}
2437
2439{
2440 if (!this->CheckNumArguments(10)) { return; }
2441
2442 Pistonprop pistonprop;
2443 pistonprop.reference_node = this->GetArgNodeRef (0);
2444 pistonprop.axis_node = this->GetArgNodeRef (1);
2445 pistonprop.blade_tip_nodes[0] = this->GetArgNodeRef (2);
2446 pistonprop.blade_tip_nodes[1] = this->GetArgNodeRef (3);
2447 pistonprop.blade_tip_nodes[2] = this->GetArgNullableNode(4);
2448 pistonprop.blade_tip_nodes[3] = this->GetArgNullableNode(5);
2449 pistonprop.couple_node = this->GetArgNullableNode(6);
2450 pistonprop.turbine_power_kW = this->GetArgFloat (7);
2451 pistonprop.pitch = this->GetArgFloat (8);
2452 pistonprop.airfoil = this->GetArgStr (9);
2453
2454 m_current_module->pistonprops.push_back(pistonprop);
2456}
2457
2459{
2460 if (!this->CheckNumArguments(3)) { return; }
2461
2462 Particle particle;
2463 particle.emitter_node = this->GetArgNodeRef(0);
2464 particle.reference_node = this->GetArgNodeRef(1);
2465 particle.particle_system_name = this->GetArgStr (2);
2466
2467 m_current_module->particles.push_back(particle);
2469}
2470
2471// Static
2472void Parser::_TrimTrailingComments(std::string const & line_in, std::string & line_out)
2473{
2474 // Trim trailing comment
2475 // We need to handle a case of lines as [keyword 1, 2, 3 ;;///// Comment!]
2476 int comment_start = static_cast<int>(line_in.find_first_of(";"));
2477 if (comment_start != Ogre::String::npos)
2478 {
2479 line_out = line_in.substr(0, comment_start);
2480 return;
2481 }
2482 // The [//Comment] is harder - the '/' character may also be present in DESCRIPTION arguments!
2483 comment_start = static_cast<int>(line_in.find_last_of("/"));
2484 if (comment_start != Ogre::String::npos)
2485 {
2486 while (comment_start >= 0)
2487 {
2488 char c = line_in[comment_start - 1];
2489 if (c != '/' && c != ' ' && c != '\t')
2490 {
2491 break; // Start of comment found
2492 }
2493 --comment_start;
2494 }
2495 line_out = line_in.substr(0, comment_start);
2496 return;
2497 }
2498 // No comment found
2499 line_out = line_in;
2500}
2501
2503{
2504 if (! this->CheckNumArguments(4)) { return; }
2505
2506 Node node;
2511
2513 {
2514 std::string node_name = this->GetArgStr(0);
2515 node.id.setStr(node_name);
2517 {
2519 }
2520 m_any_named_node_defined = true; // For import logic
2521 }
2522 else
2523 {
2524 const unsigned int node_num = this->GetArgUint(0);
2525 node.id.SetNum(node_num);
2527 {
2529 }
2530 }
2531
2532 node.position.x = this->GetArgFloat(1);
2533 node.position.y = this->GetArgFloat(2);
2534 node.position.z = this->GetArgFloat(3);
2535 if (m_num_args > 4)
2536 {
2537 node.options = this->GetArgNodeOptions(4);
2538 }
2539 if (m_num_args > 5)
2540 {
2542 {
2543 node.load_weight_override = this->GetArgFloat(5);
2544 node._has_load_weight_override = true;
2545 }
2546 else
2547 {
2549 "Node has load-weight-override value specified, but option 'l' is not present. Ignoring value...");
2550 }
2551 }
2552
2553 m_current_module->nodes.push_back(node);
2555}
2556
2558{
2559 if (! this->CheckNumArguments(1)) { return; }
2560
2561 Minimass mm;
2562 mm.global_min_mass_Kg = this->GetArgFloat(0);
2563 if (m_num_args > 1) { mm.option = this->GetArgMinimassOption(1); }
2564
2565 m_current_module->minimass.push_back(mm);
2567}
2568
2570{
2571 if (! this->CheckNumArguments(16)) { return; }
2572
2573 FlexBodyWheel flexbody_wheel;
2574 flexbody_wheel.node_defaults = m_user_node_defaults;
2575 flexbody_wheel.beam_defaults = m_user_beam_defaults;
2576
2577 flexbody_wheel.tyre_radius = this->GetArgFloat ( 0);
2578 flexbody_wheel.rim_radius = this->GetArgFloat ( 1);
2579 flexbody_wheel.width = this->GetArgFloat ( 2);
2580 flexbody_wheel.num_rays = this->GetArgInt ( 3);
2581 flexbody_wheel.nodes[0] = this->GetArgNodeRef ( 4);
2582 flexbody_wheel.nodes[1] = this->GetArgNodeRef ( 5);
2583 flexbody_wheel.rigidity_node = this->GetArgRigidityNode ( 6);
2584 flexbody_wheel.braking = this->GetArgBraking ( 7);
2585 flexbody_wheel.propulsion = this->GetArgPropulsion ( 8);
2586 flexbody_wheel.reference_arm_node = this->GetArgNodeRef ( 9);
2587 flexbody_wheel.mass = this->GetArgFloat (10);
2588 flexbody_wheel.tyre_springiness = this->GetArgFloat (11);
2589 flexbody_wheel.tyre_damping = this->GetArgFloat (12);
2590 flexbody_wheel.rim_springiness = this->GetArgFloat (13);
2591 flexbody_wheel.rim_damping = this->GetArgFloat (14);
2592 flexbody_wheel.side = this->GetArgWheelSide (15);
2593
2594 if (m_num_args > 16) { flexbody_wheel.rim_mesh_name = this->GetArgStr(16); }
2595 if (m_num_args > 17) { flexbody_wheel.tyre_mesh_name = this->GetArgStr(17); }
2596
2598 {
2600 }
2601
2602 m_current_module->flexbodywheels.push_back(flexbody_wheel);
2604}
2605
2607{
2608 if (! this->CheckNumArguments(2)) { return; }
2609
2610 MaterialFlareBinding binding;
2611 binding.flare_number = this->GetArgInt(0);
2612 binding.material_name = this->GetArgStr(1);
2613
2614 m_current_module->materialflarebindings.push_back(binding);
2615 this->FlushPendingDocComment(m_current_module->materialflarebindings.size(), Keyword::MATERIALFLAREBINDINGS);
2616}
2617
2619{
2620 if (! this->CheckNumArguments(2)) { return; }
2621
2622 ManagedMaterial managed_mat;
2624 managed_mat.name = this->GetArgStr(0);
2625 managed_mat.type = this->GetArgManagedMatType(1);
2626
2627 if (managed_mat.type != ManagedMaterialType::INVALID)
2628 {
2629 if (! this->CheckNumArguments(3)) { return; }
2630
2631 managed_mat.diffuse_map = this->GetArgStr(2);
2632
2633 if (managed_mat.type == ManagedMaterialType::MESH_STANDARD ||
2635 {
2636 if (m_num_args > 3) { managed_mat.specular_map = this->GetArgManagedTex(3); }
2637 }
2638 else if (managed_mat.type == ManagedMaterialType::FLEXMESH_STANDARD ||
2640 {
2641 if (m_num_args > 3) { managed_mat.damaged_diffuse_map = this->GetArgManagedTex(3); }
2642 if (m_num_args > 4) { managed_mat.specular_map = this->GetArgManagedTex(4); }
2643 }
2644
2645 m_current_module->managedmaterials.push_back(managed_mat);
2646 this->FlushPendingDocComment(m_current_module->managedmaterials.size(), Keyword::MANAGEDMATERIALS);
2647 }
2648}
2649
2651{
2652 if (! this->CheckNumArguments(2)) { return; } // Lockgroup num. + at least 1 node...
2653
2654 Lockgroup lockgroup;
2655 lockgroup.number = this->GetArgInt(0);
2656
2657 for (int i = 1; i < m_num_args; ++i)
2658 {
2659 lockgroup.nodes.push_back(this->GetArgNodeRef(i));
2660 }
2661
2662 m_current_module->lockgroups.push_back(lockgroup);
2664}
2665
2667{
2668 if (! this->CheckNumArguments(3)) { return; }
2669
2670 Hydro hydro;
2674
2675 hydro.nodes[0] = this->GetArgNodeRef(0);
2676 hydro.nodes[1] = this->GetArgNodeRef(1);
2677 hydro.lenghtening_factor = this->GetArgFloat (2);
2678
2679 if (m_num_args > 3) { hydro.options = this->GetArgHydroOptions(3); }
2680
2681 if (!hydro.options)
2682 {
2684 }
2685
2686 this->ParseOptionalInertia(hydro.inertia, 4);
2687
2688 m_current_module->hydros.push_back(hydro);
2690}
2691
2692void Parser::ParseOptionalInertia(Inertia & inertia, int index)
2693{
2694 if (m_num_args > index) { inertia.start_delay_factor = this->GetArgFloat(index++); }
2695 if (m_num_args > index) { inertia.stop_delay_factor = this->GetArgFloat(index++); }
2696 if (m_num_args > index) { inertia.start_function = this->GetArgStr (index++); }
2697 if (m_num_args > index) { inertia.stop_function = this->GetArgStr (index++); }
2698}
2699
2700void Parser::_ParseDifferentialTypes(DifferentialTypeVec& diff_types, std::string const& options_str)
2701{
2702 for (char c: options_str)
2703 {
2704 switch(c)
2705 {
2706 case (char)DifferentialType::o_OPEN:
2707 case (char)DifferentialType::l_LOCKED:
2708 case (char)DifferentialType::s_SPLIT:
2709 case (char)DifferentialType::v_VISCOUS:
2710 diff_types.push_back(DifferentialType(c));
2711 break;
2712
2713 default:
2715 fmt::format("ignoring invalid differential type '{}'", c));
2716 break;
2717 }
2718 }
2719}
2720
2722{
2723 if (! this->CheckNumArguments(2)) { return; }
2724
2725 Beam beam;
2728
2729 beam.nodes[0] = this->GetArgNodeRef(0);
2730 beam.nodes[1] = this->GetArgNodeRef(1);
2731 if (m_num_args > 2) beam.options = this->GetArgBeamOptions(2);
2732
2734 {
2735 float support_break_limit = 0.0f;
2736 float support_break_factor = this->GetArgInt(3);
2737 if (support_break_factor > 0.0f)
2738 {
2739 support_break_limit = support_break_factor;
2740 }
2741 beam.extension_break_limit = support_break_limit;
2742 beam._has_extension_break_limit = true;
2743 }
2744
2745 m_current_module->beams.push_back(beam);
2747}
2748
2750{
2751 auto args = Ogre::StringUtil::split(m_current_line, ",");
2752 if (args.size() < 4) { return; }
2753
2754 Animator animator;
2758
2759 animator.nodes[0] = this->_ParseNodeRef(args[0]);
2760 animator.nodes[1] = this->_ParseNodeRef(args[1]);
2761 animator.lenghtening_factor = this->ParseArgFloat(args[2]);
2762
2763 // Parse options; Just use the split/trim/compare method
2764 Ogre::StringVector attrs = Ogre::StringUtil::split(args[3], "|");
2765
2766 auto itor = attrs.begin();
2767 auto endi = attrs.end();
2768 for (; itor != endi; ++itor)
2769 {
2770 Ogre::String token = *itor;
2771 Ogre::StringUtil::trim(token);
2772 std::smatch results;
2773 bool is_shortlimit = false;
2774
2775 // Numbered keywords
2776 if (std::regex_search(token, results, Regexes::PARSE_ANIMATORS_NUMBERED_KEYWORD))
2777 {
2778 if (results[1] == "throttle") animator.aero_animator.flags |= AeroAnimator::OPTION_THROTTLE;
2779 else if (results[1] == "rpm") animator.aero_animator.flags |= AeroAnimator::OPTION_RPM;
2780 else if (results[1] == "aerotorq") animator.aero_animator.flags |= AeroAnimator::OPTION_TORQUE;
2781 else if (results[1] == "aeropit") animator.aero_animator.flags |= AeroAnimator::OPTION_PITCH;
2782 else if (results[1] == "aerostatus") animator.aero_animator.flags |= AeroAnimator::OPTION_STATUS;
2783
2784 animator.aero_animator.engine_idx = this->ParseArgUint(results[2].str().c_str()) - 1;
2785 }
2786 else if ((is_shortlimit = (token.compare(0, 10, "shortlimit") == 0)) || (token.compare(0, 9, "longlimit") == 0))
2787 {
2788 Ogre::StringVector fields = Ogre::StringUtil::split(token, ":");
2789 if (fields.size() > 1)
2790 {
2791 if (is_shortlimit)
2792 {
2793 animator.short_limit = std::strtod(fields[1].c_str(), nullptr);
2795 }
2796 else
2797 {
2798 animator.long_limit = std::strtod(fields[1].c_str(), nullptr);
2800 }
2801 }
2802 }
2803 else
2804 {
2805 // Standalone keywords
2806 if (token == "vis") animator.flags |= Animator::OPTION_VISIBLE;
2807 else if (token == "inv") animator.flags |= Animator::OPTION_INVISIBLE;
2808 else if (token == "airspeed") animator.flags |= Animator::OPTION_AIRSPEED;
2809 else if (token == "vvi") animator.flags |= Animator::OPTION_VERTICAL_VELOCITY;
2810 else if (token == "altimeter100k") animator.flags |= Animator::OPTION_ALTIMETER_100K;
2811 else if (token == "altimeter10k") animator.flags |= Animator::OPTION_ALTIMETER_10K;
2812 else if (token == "altimeter1k") animator.flags |= Animator::OPTION_ALTIMETER_1K;
2813 else if (token == "aoa") animator.flags |= Animator::OPTION_ANGLE_OF_ATTACK;
2814 else if (token == "flap") animator.flags |= Animator::OPTION_FLAP;
2815 else if (token == "airbrake") animator.flags |= Animator::OPTION_AIR_BRAKE;
2816 else if (token == "roll") animator.flags |= Animator::OPTION_ROLL;
2817 else if (token == "pitch") animator.flags |= Animator::OPTION_PITCH;
2818 else if (token == "brakes") animator.flags |= Animator::OPTION_BRAKES;
2819 else if (token == "accel") animator.flags |= Animator::OPTION_ACCEL;
2820 else if (token == "clutch") animator.flags |= Animator::OPTION_CLUTCH;
2821 else if (token == "speedo") animator.flags |= Animator::OPTION_SPEEDO;
2822 else if (token == "tacho") animator.flags |= Animator::OPTION_TACHO;
2823 else if (token == "turbo") animator.flags |= Animator::OPTION_TURBO;
2824 else if (token == "parking") animator.flags |= Animator::OPTION_PARKING;
2825 else if (token == "shifterman1") animator.flags |= Animator::OPTION_SHIFT_LEFT_RIGHT;
2826 else if (token == "shifterman2") animator.flags |= Animator::OPTION_SHIFT_BACK_FORTH;
2827 else if (token == "sequential") animator.flags |= Animator::OPTION_SEQUENTIAL_SHIFT;
2828 else if (token == "shifterlin") animator.flags |= Animator::OPTION_GEAR_SELECT;
2829 else if (token == "torque") animator.flags |= Animator::OPTION_TORQUE;
2830 else if (token == "difflock") animator.flags |= Animator::OPTION_DIFFLOCK;
2831 else if (token == "rudderboat") animator.flags |= Animator::OPTION_BOAT_RUDDER;
2832 else if (token == "throttleboat") animator.flags |= Animator::OPTION_BOAT_THROTTLE;
2833 }
2834 }
2835
2836 m_current_module->animators.push_back(animator);
2838}
2839
2841{
2842 if (! this->CheckNumArguments(2)) { return; }
2843
2844 Author author;
2845 if (m_num_args > 1) { author.type = this->GetArgStr(1); }
2846 if (m_num_args > 2) { author.forum_account_id = this->GetArgInt(2); author._has_forum_account = true; }
2847 if (m_num_args > 3) { author.name = this->GetArgStr(3); }
2848 if (m_num_args > 4) { author.email = this->GetArgStr(4); }
2849
2850 m_current_module->author.push_back(author);
2853}
2854
2855// --------------------------------------------------------------------------
2856// Utilities
2857// --------------------------------------------------------------------------
2858
2859void Parser::LogMessage(Console::MessageType type, std::string const& msg)
2860{
2863 type,
2864 fmt::format("{}:{} ({}): {}",
2866}
2867
2868Keyword Parser::IdentifyKeyword(const std::string& line)
2869{
2870 // Search and ignore lettercase
2871 std::smatch results;
2872 std::regex_search(line, results, Regexes::IDENTIFY_KEYWORD_IGNORE_CASE); // Always returns true.
2873
2874 // The 'results' array contains a complete match at positon [0] and sub-matches starting with [1],
2875 // so we get exact positions in Regexes::IDENTIFY_KEYWORD, which again match Keyword enum members
2876 for (unsigned int i = 1; i < results.size(); i++)
2877 {
2878 std::ssub_match sub = results[i];
2879 if (sub.matched)
2880 {
2881 // Build enum value directly from result offset
2882 return Keyword(i);
2883 }
2884 }
2885
2886 return Keyword::INVALID;
2887}
2888
2890{
2895 m_current_detacher_group = 0; // Global detacher group
2896
2900
2901 m_user_beam_defaults = std::shared_ptr<BeamDefaults>(new BeamDefaults);
2902 m_user_beam_defaults->springiness = DEFAULT_SPRING;
2903 m_user_beam_defaults->damping_constant = DEFAULT_DAMP;
2904 m_user_beam_defaults->deformation_threshold = BEAM_DEFORM;
2905 m_user_beam_defaults->breaking_threshold = BEAM_BREAK;
2906 m_user_beam_defaults->visual_beam_diameter = DEFAULT_BEAM_DIAMETER;
2907
2908 m_root_module = m_definition->root_module;
2909 m_current_module = m_definition->root_module;
2910
2911 m_sequential_importer.Init(true); // Enabled=true
2912}
2913
2915{
2916 if (keyword == Keyword::INVALID) // also means 'end'
2917 {
2918 // flush staged submesh, if any
2919 if (m_current_submesh != nullptr)
2920 {
2921 m_current_module->submeshes.push_back(*m_current_submesh);
2922 m_current_submesh.reset(); // Set to nullptr
2923 }
2924
2925 // flush staged camerarail, if any
2926 if (m_current_camera_rail != nullptr)
2927 {
2928 if (m_current_camera_rail->nodes.size() == 0)
2929 {
2930 this->LogMessage(Console::CONSOLE_SYSTEM_WARNING, "Empty section 'camerarail', ignoring...");
2931 }
2932 else
2933 {
2934 m_current_module->camerarail.push_back(*m_current_camera_rail);
2935 m_current_camera_rail.reset();
2936 }
2937 }
2938 }
2939 else if (keyword == Keyword::CAMERARAIL)
2940 {
2941 this->BeginBlock(Keyword::INVALID); // flush staged rail
2942 m_current_camera_rail = std::shared_ptr<CameraRail>( new CameraRail() );
2943 }
2945}
2946
2948{
2949 // Determine and verify new module
2950 std::string new_module_name;
2952 {
2954 {
2955 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "Misplaced keyword 'end_section' (already in root module), ignoring...");
2956 return;
2957 }
2958 new_module_name = ROOT_MODULE_NAME;
2959 }
2960 else if (keyword == Keyword::SECTION)
2961 {
2962 if (!this->CheckNumArguments(3)) // Syntax: "section VERSION NAME"; VERSION is unused
2963 {
2964 return; // Error already reported
2965 }
2966
2967 new_module_name = this->GetArgStr(2);
2968 if (new_module_name == m_current_module->name)
2969 {
2970 this->LogMessage(Console::CONSOLE_SYSTEM_ERROR, "Attempt to re-enter current module, ignoring...");
2971 return;
2972 }
2973 }
2974
2975 // Perform the switch
2977
2978 if (new_module_name == ROOT_MODULE_NAME)
2979 {
2981 return;
2982 }
2983
2984 auto search_itor = m_definition->user_modules.find(new_module_name);
2985 if (search_itor != m_definition->user_modules.end())
2986 {
2987 m_current_module = search_itor->second;
2988 }
2989 else
2990 {
2991 m_current_module = std::make_shared<Document::Module>(new_module_name);
2992 m_definition->user_modules.insert(std::make_pair(new_module_name, m_current_module));
2993 }
2994}
2995
2997{
2998 if (m_pending_doc_comment.comment_text == "" || vectorlen == 0)
2999 {
3000 return;
3001 }
3002
3004 m_pending_doc_comment.commented_datapos = static_cast<int>(vectorlen - 1);
3005 m_current_module->_comments.push_back(m_pending_doc_comment);
3006
3008}
3009
3014
3016{
3017 // FIXME: restore this, see branch 'retro-0407'
3018}
3019
3021{
3023
3025 {
3027 }
3028}
3029
3030std::string Parser::GetArgStr(int index)
3031{
3032 return std::string(m_args[index].start, m_args[index].length);
3033}
3034
3035char Parser::GetArgChar(int index)
3036{
3037 return *(m_args[index].start);
3038}
3039
3041{
3042 char c = this->GetArgChar(index);
3043 switch (c)
3044 {
3045 case (char)WheelSide::RIGHT:
3046 case (char)WheelSide::LEFT:
3047 return WheelSide(c);
3048
3049 default:
3051 fmt::format("Bad arg~{} 'side' (value: {}), parsing as 'l' for backwards compatibility.", index + 1, c));
3052 return WheelSide::LEFT;
3053 }
3054}
3055
3056long Parser::GetArgLong(int index)
3057{
3058 errno = 0;
3059 char* out_end = nullptr;
3060 long res = std::strtol(m_args[index].start, &out_end, 10);
3061 if (errno != 0)
3062 {
3064 fmt::format("Cannot parse argument [{}] as integer, errno: {}", index + 1, errno));
3065 return 0; // Compatibility
3066 }
3067 if (out_end == m_args[index].start)
3068 {
3070 fmt::format("Argument [{}] is not valid integer", index + 1));
3071 return 0; // Compatibility
3072 }
3073 else if (out_end != (m_args[index].start + m_args[index].length))
3074 {;
3076 fmt::format("Integer argument [{}] has invalid trailing characters", index + 1));
3077 }
3078 return res;
3079}
3080
3081int Parser::GetArgInt(int index)
3082{
3083 return static_cast<int>(this->GetArgLong(index));
3084}
3085
3087{
3088 std::string rigidity_node = this->GetArgStr(index);
3089 if (rigidity_node != "9999") // Special null value
3090 {
3091 return this->GetArgNodeRef(index);
3092 }
3093 return Node::Ref(); // Defaults to invalid ref
3094}
3095
3097{
3098 int p = this->GetArgInt(index);
3099 switch (p)
3100 {
3101 case (int)WheelPropulsion::NONE:
3102 case (int)WheelPropulsion::FORWARD:
3103 case (int)WheelPropulsion::BACKWARD:
3104 return WheelPropulsion(p);
3105
3106 default:
3108 fmt::format("Bad value of param ~{} (propulsion), using 0 (no propulsion)", index + 1));
3109 return WheelPropulsion::NONE;
3110 }
3111}
3112
3114{
3115 int b = this->GetArgInt(index);
3116 switch (b)
3117 {
3118 case (int)WheelBraking::NONE:
3119 case (int)WheelBraking::FOOT_HAND:
3120 case (int)WheelBraking::FOOT_HAND_SKID_LEFT:
3121 case (int)WheelBraking::FOOT_HAND_SKID_RIGHT:
3122 case (int)WheelBraking::FOOT_ONLY:
3123 return WheelBraking(b);
3124
3125 default:
3127 fmt::format("Bad value of param ~{} (braking), using 0 (not braked)", index + 1));
3128 return WheelBraking::NONE;
3129 }
3130}
3131
3133{
3134 return this->_ParseNodeRef(this->GetArgStr(index));
3135}
3136
3138{
3139 if (! (Ogre::StringConverter::parseReal(this->GetArgStr(index)) == -1.f))
3140 {
3141 return this->GetArgNodeRef(index);
3142 }
3143 return Node::Ref(); // Defaults to empty ref.
3144}
3145
3146unsigned Parser::GetArgUint(int index)
3147{
3148 return static_cast<unsigned>(this->GetArgLong(index));
3149}
3150
3152{
3153 char in = this->GetArgChar(index);
3154 switch (in)
3155 {
3156 // Front lights
3157 case (char)FlareType::HEADLIGHT:
3158 case (char)FlareType::HIGH_BEAM:
3159 case (char)FlareType::FOG_LIGHT:
3160 // Rear lighs
3161 case (char)FlareType::TAIL_LIGHT:
3162 case (char)FlareType::BRAKE_LIGHT:
3163 case (char)FlareType::REVERSE_LIGHT:
3164 // Special lights
3165 case (char)FlareType::SIDELIGHT:
3166 case (char)FlareType::BLINKER_LEFT:
3167 case (char)FlareType::BLINKER_RIGHT:
3168 case (char)FlareType::USER:
3169 case (char)FlareType::DASHBOARD:
3170 return FlareType(in);
3171
3172 default:
3174 fmt::format("Invalid flare type '{}', falling back to type 'f' (front light)...", in));
3175 return FlareType::HEADLIGHT;
3176 }
3177}
3178
3180{
3181 std::string str = this->GetArgStr(index);
3182 if (str == "classic") return ExtCameraMode::CLASSIC;
3183 if (str == "cinecam") return ExtCameraMode::CINECAM;
3184 if (str == "node") return ExtCameraMode::NODE;
3185
3187 fmt::format("Invalid ExtCameraMode '{}', falling back to type 'classic'...", str));
3188 return ExtCameraMode::CLASSIC;
3189}
3190
3191float Parser::GetArgFloat(int index)
3192{
3193 return (float) Ogre::StringConverter::parseReal(this->GetArgStr(index), 0.f);
3194}
3195
3196float Parser::ParseArgFloat(const char* str)
3197{
3198 return (float) Ogre::StringConverter::parseReal(str, 0.f);
3199}
3200
3201float Parser::ParseArgFloat(std::string const & str)
3202{
3203 return this->ParseArgFloat(str.c_str());
3204}
3205
3206unsigned Parser::ParseArgUint(const char* str)
3207{
3208 errno = 0;
3209 long res = std::strtol(str, nullptr, 10);
3210 if (errno != 0)
3211 {
3213 fmt::format("Cannot parse argument '{}' as int, errno: {}", str, errno));
3214 return 0.f; // Compatibility
3215 }
3216 return static_cast<unsigned>(res);
3217}
3218
3219unsigned Parser::ParseArgUint(std::string const & str)
3220{
3221 return this->ParseArgUint(str.c_str());
3222}
3223
3224int Parser::ParseArgInt(const char* str)
3225{
3226 return static_cast<int>(this->ParseArgUint(str));
3227}
3228
3229bool Parser::GetArgBool(int index)
3230{
3231 return Ogre::StringConverter::parseBool(this->GetArgStr(index));
3232}
3233
3235{
3236 char c = this->GetArgChar(index);
3237 switch (c)
3238 {
3239 case (char)WingControlSurface::n_NONE:
3242 case (char)WingControlSurface::f_FLAP:
3255 return WingControlSurface(c);
3256
3257 default:
3258 fmt::format("invalid WingControlSurface '{}', falling back to 'n' (none)", c);
3260 }
3261}
3262
3263std::string Parser::GetArgManagedTex(int index)
3264{
3265 std::string tex_name = this->GetArgStr(index);
3266 return (tex_name.at(0) != '-') ? tex_name : "";
3267}
3268
3270{
3271 switch (this->GetArgStr(index)[0])
3272 {
3275
3276 case (char)MinimassOption::n_DUMMY:
3278
3279 default:
3281 fmt::format("Not a valid minimass option: {}, falling back to 'n' (dummy)", this->GetArgStr(index)));
3283 }
3284}
3285
3287{
3288 BitMask_t ret = 0;
3289 for (char c: this->GetArgStr(index))
3290 {
3291 switch (c)
3292 {
3293 case (char)CabOption::n_DUMMY: break;
3294 case (char)CabOption::c_CONTACT: ret |= Cab::OPTION_c_CONTACT ; break;
3295 case (char)CabOption::b_BUOYANT: ret |= Cab::OPTION_b_BUOYANT ; break;
3296 case (char)CabOption::p_10xTOUGHER: ret |= Cab::OPTION_p_10xTOUGHER ; break;
3297 case (char)CabOption::u_INVULNERABLE: ret |= Cab::OPTION_u_INVULNERABLE ; break;
3303
3304 default:
3306 fmt::format("ignoring invalid flag '{}'", c));
3307 }
3308 }
3309 return ret;
3310}
3311
3313{
3314 BitMask_t ret = 0;
3315 for (char c: this->GetArgStr(index))
3316 {
3317 switch(c)
3318 {
3330
3331 default:
3333 fmt::format("ignoring invalid option '{}'", c));
3334 }
3335 }
3336 return ret;
3337}
3338
3340{
3341 BitMask_t ret = 0;
3342 for (char c: this->GetArgStr(index))
3343 {
3344 switch (c)
3345 {
3346 case (char)BeamOption::i_INVISIBLE: ret |= Beam::OPTION_i_INVISIBLE; break;
3347 case (char)BeamOption::r_ROPE : ret |= Beam::OPTION_r_ROPE ; break;
3348 case (char)BeamOption::s_SUPPORT : ret |= Beam::OPTION_s_SUPPORT ; break;
3349
3350 case (char)BeamOption::v_DUMMY: break;
3351
3352 default:
3354 fmt::format("ignoring invalid option '{}'", c));
3355 }
3356 }
3357 return ret;
3358}
3359
3361{
3362 BitMask_t ret = 0;
3363 for (char c: this->GetArgStr(index))
3364 {
3365 switch (c)
3366 {
3367 case (char)HydroOption::j_INVISIBLE : ret |= Hydro::OPTION_j_INVISIBLE ; break;
3379
3381 if (ret == 0)
3382 {
3383 // Original intent: when using 'i' flag alone, also force 'n' (steering input).
3384 // For backward compatibility, do it every time 'i' comes first, even if not alone.
3386 }
3388 break;
3389
3390 default:
3392 fmt::format("ignoring invalid option '{}'", c));
3393 }
3394 }
3395
3396 return ret;
3397}
3398
3400{
3401 BitMask_t ret = 0;
3402 for (char c: this->GetArgStr(index))
3403 {
3404 switch (c)
3405 {
3406 case (char)ShockOption::i_INVISIBLE : ret |= Shock::OPTION_i_INVISIBLE ; break;
3407 case (char)ShockOption::L_ACTIVE_LEFT : ret |= Shock::OPTION_L_ACTIVE_LEFT ; break;
3409 case (char)ShockOption::m_METRIC : ret |= Shock::OPTION_m_METRIC ; break;
3410
3411 case (char)ShockOption::n_DUMMY: break;
3412 case (char)ShockOption::v_DUMMY: break;
3413
3414 default:
3416 fmt::format("ignoring invalid option '{}'", c));
3417 }
3418 }
3419 return ret;
3420}
3421
3423{
3424 BitMask_t ret = 0;
3425 for (char c: this->GetArgStr(index))
3426 {
3427 switch (c)
3428 {
3429 case (char)Shock2Option::i_INVISIBLE: ret |= Shock2::OPTION_i_INVISIBLE; break;
3431 case (char)Shock2Option::m_METRIC: ret |= Shock2::OPTION_m_METRIC; break;
3433
3434 case (char)Shock2Option::n_DUMMY: break;
3435 case (char)Shock2Option::v_DUMMY: break;
3436
3437 default:
3439 fmt::format("ignoring invalid option '{}'", c));
3440 }
3441 }
3442 return ret;
3443}
3444
3446{
3447 BitMask_t ret = 0;
3448 for (char c: this->GetArgStr(index))
3449 {
3450 switch (c)
3451 {
3452 case (char)Shock3Option::i_INVISIBLE: ret |= Shock3::OPTION_i_INVISIBLE; break;
3453 case (char)Shock3Option::m_METRIC: ret |= Shock3::OPTION_m_METRIC; break;
3455
3456 case (char)Shock3Option::n_DUMMY: break;
3457 case (char)Shock3Option::v_DUMMY: break;
3458
3459 default:
3461 fmt::format("ignoring invalid option '{}'", c));
3462 }
3463 }
3464 return ret;
3465}
3466
3468{
3469 BitMask_t ret = 0;
3470 for (char c: this->GetArgStr(index))
3471 {
3472 switch (c)
3473 {
3475 case (char)NodeOption::f_NO_SPARKS : ret |= Node::OPTION_f_NO_SPARKS ; break;
3479 case (char)NodeOption::h_HOOK_POINT : ret |= Node::OPTION_h_HOOK_POINT ; break;
3482 case (char)NodeOption::p_NO_PARTICLES : ret |= Node::OPTION_p_NO_PARTICLES ; break;
3483 case (char)NodeOption::L_LOG : ret |= Node::OPTION_L_LOG ; break;
3484 case (char)NodeOption::l_LOAD_WEIGHT : ret |= Node::OPTION_l_LOAD_WEIGHT ; break;
3485
3486 case (char)NodeOption::n_DUMMY: break;
3487
3488 default:
3490 fmt::format("ignoring invalid option '{}'", c));
3491 }
3492 }
3493 return ret;
3494}
3495
3497{
3498 if (str.find("leftmirror" ) != std::string::npos) { return SpecialProp::MIRROR_LEFT; }
3499 if (str.find("rightmirror" ) != std::string::npos) { return SpecialProp::MIRROR_RIGHT; }
3500 if (str.find("dashboard-rh") != std::string::npos) { return SpecialProp::DASHBOARD_RIGHT; }
3501 if (str.find("dashboard" ) != std::string::npos) { return SpecialProp::DASHBOARD_LEFT; }
3502 if (Ogre::StringUtil::startsWith(str, "spinprop", false) ) { return SpecialProp::AERO_PROP_SPIN; }
3503 if (Ogre::StringUtil::startsWith(str, "pale", false) ) { return SpecialProp::AERO_PROP_BLADE; }
3504 if (Ogre::StringUtil::startsWith(str, "seat", false) ) { return SpecialProp::DRIVER_SEAT; }
3505 if (Ogre::StringUtil::startsWith(str, "seat2", false) ) { return SpecialProp::DRIVER_SEAT_2; }
3506 if (Ogre::StringUtil::startsWith(str, "beacon", false) ) { return SpecialProp::BEACON; }
3507 if (Ogre::StringUtil::startsWith(str, "redbeacon", false)) { return SpecialProp::REDBEACON; }
3508 if (Ogre::StringUtil::startsWith(str, "lightb", false) ) { return SpecialProp::LIGHTBAR; }
3509 return SpecialProp::NONE;
3510}
3511
3513{
3514 std::string str = this->GetArgStr(index);
3515 if (str == "bool") return DC_BOOL;
3516 if (str == "int") return DC_INT;
3517 if (str == "float") return DC_FLOAT;
3518 if (str == "string") return DC_CHAR;
3519
3521 fmt::format("Not a valid dashboard input data type: '{}'", str));
3522 return DC_INVALID;
3523}
3524
3526{
3527 char c = this->GetArgChar(index);
3528 switch (c)
3529 {
3530 case (char)EngineType::t_TRUCK:
3531 case (char)EngineType::c_CAR:
3532 case (char)EngineType::e_ECAR:
3533 return EngineType(c);
3534
3535 default:
3536 fmt::format("invalid EngineType '{}', falling back to 't' (truck)", c);
3537 return EngineType::t_TRUCK;
3538 }
3539}
3540
3542{
3543 std::string str = this->GetArgStr(index);
3544 if (str == "mesh_standard") return ManagedMaterialType::MESH_STANDARD;
3545 if (str == "mesh_transparent") return ManagedMaterialType::MESH_TRANSPARENT;
3546 if (str == "flexmesh_standard") return ManagedMaterialType::FLEXMESH_STANDARD;
3547 if (str == "flexmesh_transparent") return ManagedMaterialType::FLEXMESH_TRANSPARENT;
3548
3550 fmt::format("Not a valid ManagedMaterialType: '{}'", str));
3552}
3553
3555{
3556 int role = this->GetArgInt(index);
3557 switch (role)
3558 {
3563 default:
3565 fmt::format("Invalid value of 'camera role' ({}), videocamera will not work", role));
3566 return VCAM_ROLE_INVALID;
3567 }
3568}
3569
3571{
3572 int cur_arg = 0;
3573 const char* cur_char = m_current_line;
3574 int arg_len = 0;
3575 while ((*cur_char != '\0') && (cur_arg < Parser::LINE_MAX_ARGS))
3576 {
3577 const bool is_arg = !IsSeparator(*cur_char);
3578 if ((arg_len == 0) && is_arg)
3579 {
3580 m_args[cur_arg].start = cur_char;
3581 arg_len = 1;
3582 }
3583 else if ((arg_len > 0) && !is_arg)
3584 {
3585 m_args[cur_arg].length = arg_len;
3586 arg_len = 0;
3587 ++cur_arg;
3588 }
3589 else if (is_arg)
3590 {
3591 ++arg_len;
3592 }
3593 ++cur_char;
3594 }
3595 if (arg_len > 0)
3596 {
3597 m_args[cur_arg].length = arg_len;
3598 ++cur_arg;
3599 }
3600
3601 m_num_args = cur_arg;
3602 return cur_arg;
3603}
3604
3605void Parser::ProcessOgreStream(Ogre::DataStream* stream, Ogre::String resource_group)
3606{
3607 m_resource_group = resource_group;
3608 m_filename = stream->getName();
3609
3610 char raw_line_buf[LINE_BUFFER_LENGTH];
3611 while (!stream->eof())
3612 {
3613 try
3614 {
3615 stream->readLine(raw_line_buf, LINE_BUFFER_LENGTH - 1); // Ensure we don't overflow
3616 raw_line_buf[LINE_BUFFER_LENGTH - 1] = '\0'; // Null-terminate the buffer
3617 }
3618 catch (Ogre::Exception &ex)
3619 {
3621 fmt::format("Could not read truck file: {}", ex.getFullDescription()));
3622 break;
3623 }
3624
3625 this->ProcessRawLine(raw_line_buf);
3626 }
3627}
3628
3629void Parser::ProcessRawLine(const char* raw_line_buf)
3630{
3631 const char* raw_start = raw_line_buf;
3632 const char* raw_end = raw_line_buf + strnlen(raw_line_buf, LINE_BUFFER_LENGTH);
3633
3634 // Trim leading whitespace
3635 while (IsWhitespace(*raw_start) && (raw_start != raw_end))
3636 {
3637 ++raw_start;
3638 }
3639
3640 // Skip empty lines
3641 if (raw_start == raw_end)
3642 {
3644 return;
3645 }
3646
3647 // Record comment lines
3648 if ((*raw_start == ';') || (*raw_start == '/'))
3649 {
3650 m_pending_doc_comment.comment_text += raw_line_buf;
3653 return;
3654 }
3655
3656 // Sanitize UTF-8
3658 char* out_start = m_current_line;
3659 utf8::replace_invalid(raw_start, raw_end, out_start, '?');
3660
3661 // Process
3662 this->ProcessCurrentLine();
3664}
3665
3666} // namespace RigDef
Central state/object manager and communications hub.
#define PARSEINT(x)
Definition Application.h:58
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_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]