diff --git a/source/main/Application.cpp b/source/main/Application.cpp index 828a47f330..6f54d449d3 100644 --- a/source/main/Application.cpp +++ b/source/main/Application.cpp @@ -108,6 +108,7 @@ CVar* sim_no_self_collisions; CVar* sim_gearbox_mode; CVar* sim_soft_reset_mode; CVar* sim_quickload_dialog; +CVar* sim_character_collisions; // Multiplayer CVar* mp_state; diff --git a/source/main/Application.h b/source/main/Application.h index 4b487b6ee5..e0ea808d34 100644 --- a/source/main/Application.h +++ b/source/main/Application.h @@ -301,6 +301,7 @@ extern CVar* sim_no_self_collisions; extern CVar* sim_gearbox_mode; extern CVar* sim_soft_reset_mode; extern CVar* sim_quickload_dialog; +extern CVar* sim_character_collisions; // Multiplayer extern CVar* mp_state; diff --git a/source/main/GameContext.cpp b/source/main/GameContext.cpp index 9a6669f34b..bce10327fd 100644 --- a/source/main/GameContext.cpp +++ b/source/main/GameContext.cpp @@ -488,7 +488,7 @@ void GameContext::ChangePlayerActor(ActorPtr actor) Character* player_character = this->GetPlayerCharacter(); if (player_character) { - player_character->SetActorCoupling(false, nullptr); + player_character->SetActorCoupling(nullptr); player_character->setRotation(Ogre::Radian(rotation)); player_character->setPosition(position); } @@ -518,7 +518,7 @@ void GameContext::ChangePlayerActor(ActorPtr actor) Character* player_character = this->GetPlayerCharacter(); if (player_character) { - player_character->SetActorCoupling(true, m_player_actor); + player_character->SetActorCoupling(m_player_actor); } App::GetGuiManager()->FlexbodyDebug.AnalyzeFlexbodies(); diff --git a/source/main/gameplay/Character.cpp b/source/main/gameplay/Character.cpp index 686fc36579..a881acd0c0 100644 --- a/source/main/gameplay/Character.cpp +++ b/source/main/gameplay/Character.cpp @@ -82,16 +82,14 @@ void Character::updateCharacterRotation() setRotation(m_character_rotation); } -void Character::setPosition(Vector3 position) // TODO: updates OGRE objects --> belongs to GfxScene ~ only_a_ptr, 05/2018 +void Character::setPosition(Vector3 position) { - //ASYNCSCENE OLD m_character_scenenode->setPosition(position); m_character_position = position; m_prev_position = position; } Vector3 Character::getPosition() { - //ASYNCSCENE OLDreturn m_character_scenenode->getPosition(); return m_character_position; } @@ -145,15 +143,19 @@ void Character::update(float dt) // Trigger script events and handle mesh (ground) collision Vector3 query = position; - App::GetGameContext()->GetTerrain()->GetCollisions()->collisionCorrect(&query); + if (App::GetGameContext()->GetTerrain()->GetCollisions()->collisionCorrect(&query)) + { + m_inertia = false; + } // Auto compensate minor height differences - float depth = calculate_collision_depth(position); - if (depth > 0.0f) + float terrain_depth = calculate_collision_depth(position); + if (terrain_depth > 0.0f) { m_can_jump = true; m_character_v_speed = std::max(0.0f, m_character_v_speed); - position.y += std::min(depth, 2.0f * dt); + position.y += std::min(terrain_depth, 2.0f * dt); + m_inertia = false; } // Submesh "collision" @@ -161,8 +163,10 @@ void Character::update(float dt) float depth = 0.0f; for (ActorPtr actor : App::GetGameContext()->GetActorManager()->GetActors()) { +#if 0 if (actor->ar_bounding_box.contains(position)) { + for (int i = 0; i < actor->ar_num_collcabs; i++) { int tmpv = actor->ar_collcabs[i] * 3; @@ -173,15 +177,79 @@ void Character::update(float dt) if (result.first && result.second < 1.8f) { depth = std::max(depth, result.second); + if (depth > 0 && m_contacting_actor == nullptr) // check fresh contacting actor also, prevents knockbacks with multiple actors that have node position overlapping + { + this->SetContactingActor(actor); + // first contact - initialize 'prev' values to avoid big knockbacks + m_prev_contacting_cab = i; + m_prev_contacting_cab_matrix = CalcCabTransformMatrix(actor, i); + m_prev_contacting_cab_localpos = m_prev_contacting_cab_matrix.inverse() * position; + } + m_contacting_cab = i; + } + } +#endif + + if (depth > 0) + { + m_can_jump = true; + m_character_v_speed = std::max(0.0f, m_character_v_speed); + position.y += std::min(depth, 0.05f); + } + + if (m_contacting_actor != nullptr) + { + // Calculate vehicle movement using old & new position of the same cab (the 'prev') + // -------------------------------------------------------------------------------- + + const Affine3 old_matrix = m_prev_contacting_cab_matrix; + const Affine3 cur_matrix = CalcCabTransformMatrix(actor, m_prev_contacting_cab); + + const int tmpv = actor->ar_collcabs[m_contacting_cab] * 3; + const Vector3 a = actor->ar_nodes[actor->ar_cabs[tmpv + 0]].AbsPosition; + + Vector3 old_pos = old_matrix * m_prev_contacting_cab_localpos; + Vector3 projected_pos = cur_matrix * m_prev_contacting_cab_localpos; + position.x = projected_pos.x + a.x; + position.z = projected_pos.z + a.z; + + // Delta rot + // Ogre::Radian delta_rotation = -(cur_matrix.extractQuaternion() - old_matrix.extractQuaternion()).getYaw(); + // this->setRotation(m_character_rotation + delta_rotation); + + // Activate inertia + m_inertia = true; + m_inertia_position = projected_pos - old_pos; + // m_inertia_rotation = delta_rotation; + + } + else if (m_inertia) + { + //if (App::sim_character_collisions->getBool()) + { + position += m_inertia_position; + this->setRotation(m_character_rotation + m_inertia_rotation); } } +#if 0 + } + + else if (m_contacting_actor != nullptr && !m_contacting_actor->ar_bounding_box.contains(position)) // we lost contact, reset contacting actor + { + this->SetContactingActor(nullptr); } +#endif } - if (depth > 0.0f) + + if (m_contacting_actor != nullptr) { - m_can_jump = true; - m_character_v_speed = std::max(0.0f, m_character_v_speed); - position.y += std::min(depth, 0.05f); + m_contacting_cab_matrix = CalcCabTransformMatrix(m_contacting_actor, m_contacting_cab); + + + m_prev_contacting_cab = m_contacting_cab; + m_prev_contacting_cab_matrix = m_contacting_cab_matrix; + //m_prev_contacting_cab_localpos = m_contacting_cab_matrix.inverse() * position; + } } @@ -214,6 +282,7 @@ void Character::update(float dt) position.y = pheight; m_character_v_speed = 0.0f; m_can_jump = true; + m_inertia = false; } // water stuff @@ -370,9 +439,19 @@ void Character::update(float dt) } m_character_position = position; + /* if (m_contacting_actor) + { + m_contacting_cab_localpos = m_contacting_cab_matrix.inverse() * position; + }*/ } else if (m_actor_coupling) // The character occupies a vehicle or machine { + // Submesh collision - Prevent knockbacks on vehicle exit + if (m_contacting_actor != nullptr) + { + m_contacting_actor = nullptr; + } + // Animation float angle = m_actor_coupling->ar_hydro_dir_wheel_display * -1.0f; // not getSteeringAngle(), but this, as its smoothed float anim_time_pos = ((angle + 1.0f) * 0.5f) * m_driving_anim_length; @@ -389,6 +468,14 @@ void Character::update(float dt) m_anim_time = anim_time_pos; m_net_last_anim_time = 0.0f; } + else if (m_is_remote && m_contacting_actor) + { + // Make sure cab index from network is valid (-1 means no update arrived yet) + if (m_contacting_cab >= 0 && m_contacting_cab < m_contacting_actor->ar_num_cabs) + { + this->setPosition(this->CalcCabTransformMatrix(m_contacting_actor, m_contacting_cab) * m_contacting_cab_localpos); + } + } #ifdef USE_SOCKETW if ((App::mp_state->getEnum() == MpState::CONNECTED) && !m_is_remote) @@ -398,6 +485,22 @@ void Character::update(float dt) #endif // USE_SOCKETW } +Ogre::Affine3 Character::CalcCabTransformMatrix(ActorPtr& actor, int cab_index) +{ + const int tmpv = actor->ar_collcabs[cab_index] * 3; + const Vector3 a = actor->ar_nodes[actor->ar_cabs[tmpv + 0]].AbsPosition; + const Vector3 b = actor->ar_nodes[actor->ar_cabs[tmpv + 1]].AbsPosition; + const Vector3 c = actor->ar_nodes[actor->ar_cabs[tmpv + 2]].AbsPosition; + + // 'a' is the reference point and 'Y' axis is up. + const Vector3 x_axis = b - a; + const Vector3 z_axis = c - a; + const Vector3 y_axis = x_axis.crossProduct(z_axis); + Quaternion rot = Quaternion(x_axis, y_axis, z_axis); + + return Affine3(Vector3::ZERO, rot); +} + void Character::move(Vector3 offset) { m_character_position += offset; //ASYNCSCENE OLD m_character_scenenode->translate(offset); @@ -449,17 +552,28 @@ void Character::SendStreamData() if (m_net_timer.getMilliseconds() - m_net_last_update_time < 100) return; - // do not send position data if coupled to an actor already + // do not send position data if coupled (seated) with an actor already if (m_actor_coupling) return; m_net_last_update_time = m_net_timer.getMilliseconds(); NetCharacterMsgPos msg; - msg.command = CHARACTER_CMD_POSITION; - msg.pos_x = m_character_position.x; - msg.pos_y = m_character_position.y; - msg.pos_z = m_character_position.z; + if (m_contacting_actor) + { + msg.command = CHARACTER_CMD_POSITION_CAB; + msg.cab_index = m_contacting_cab; + msg.pos_x = m_contacting_cab_localpos.x; + msg.pos_y = m_contacting_cab_localpos.y; + msg.pos_z = m_contacting_cab_localpos.z; + } + else + { + msg.command = CHARACTER_CMD_POSITION_GROUND; + msg.pos_x = m_character_position.x; + msg.pos_y = m_character_position.y; + msg.pos_z = m_character_position.z; + } msg.rot_angle = m_character_rotation.valueRadians(); strncpy(msg.anim_name, m_anim_name.c_str(), CHARACTER_ANIM_NAME_LEN); msg.anim_time = m_anim_time - m_net_last_anim_time; @@ -476,10 +590,18 @@ void Character::receiveStreamData(unsigned int& type, int& source, unsigned int& if (type == RoRnet::MSG2_STREAM_DATA && m_source_id == source && m_stream_id == streamid) { auto* msg = reinterpret_cast(buffer); - if (msg->command == CHARACTER_CMD_POSITION) + if (msg->command == CHARACTER_CMD_POSITION_GROUND || msg->command == CHARACTER_CMD_POSITION_CAB) { auto* pos_msg = reinterpret_cast(buffer); - this->setPosition(Ogre::Vector3(pos_msg->pos_x, pos_msg->pos_y, pos_msg->pos_z)); + if (msg->command == CHARACTER_CMD_POSITION_GROUND) + { + this->setPosition(Ogre::Vector3(pos_msg->pos_x, pos_msg->pos_y, pos_msg->pos_z)); + } + else if (msg->command == CHARACTER_CMD_POSITION_CAB) + { + m_contacting_cab_localpos = Ogre::Vector3(pos_msg->pos_x, pos_msg->pos_y, pos_msg->pos_z); + m_contacting_cab = pos_msg->cab_index; + } this->setRotation(Ogre::Radian(pos_msg->rot_angle)); if (strnlen(pos_msg->anim_name, CHARACTER_ANIM_NAME_LEN) < CHARACTER_ANIM_NAME_LEN) { @@ -489,17 +611,27 @@ void Character::receiveStreamData(unsigned int& type, int& source, unsigned int& else if (msg->command == CHARACTER_CMD_DETACH) { if (m_actor_coupling != nullptr) - this->SetActorCoupling(false, nullptr); + this->SetActorCoupling(nullptr); + else if (m_contacting_actor != nullptr) + this->SetContactingActor(nullptr); else this->ReportError("Received command `DETACH`, but not currently attached to a vehicle. Ignoring command."); } - else if (msg->command == CHARACTER_CMD_ATTACH) + else if (msg->command == CHARACTER_CMD_ATTACH_SEAT || msg->command == CHARACTER_CMD_ATTACH_CAB) { auto* attach_msg = reinterpret_cast(buffer); - ActorPtr beam = App::GetGameContext()->GetActorManager()->GetActorByNetworkLinks(attach_msg->source_id, attach_msg->stream_id); - if (beam != nullptr) + ActorPtr actor = App::GetGameContext()->GetActorManager()->GetActorByNetworkLinks(attach_msg->source_id, attach_msg->stream_id); + if (actor != nullptr) { - this->SetActorCoupling(true, beam); + if (msg->command == CHARACTER_CMD_ATTACH_SEAT) + { + this->SetActorCoupling(actor); + } + else if (msg->command == CHARACTER_CMD_ATTACH_CAB) + { + this->SetContactingActor(actor); + m_contacting_cab = -1; // Indicate we have no network updates yet. + } } else { @@ -520,16 +652,16 @@ void Character::receiveStreamData(unsigned int& type, int& source, unsigned int& #endif } -void Character::SetActorCoupling(bool enabled, ActorPtr actor) +void Character::SetActorCoupling(ActorPtr actor) { m_actor_coupling = actor; #ifdef USE_SOCKETW if (App::mp_state->getEnum() == MpState::CONNECTED && !m_is_remote) { - if (enabled) + if (m_actor_coupling) { NetCharacterMsgAttach msg; - msg.command = CHARACTER_CMD_ATTACH; + msg.command = CHARACTER_CMD_ATTACH_SEAT; msg.source_id = m_actor_coupling->ar_net_source_id; msg.stream_id = m_actor_coupling->ar_net_stream_id; App::GetNetwork()->AddPacket(m_stream_id, RoRnet::MSG2_STREAM_DATA, sizeof(NetCharacterMsgAttach), (char*)&msg); @@ -544,6 +676,30 @@ void Character::SetActorCoupling(bool enabled, ActorPtr actor) #endif // USE_SOCKETW } +void Character::SetContactingActor(ActorPtr actor) +{ + m_contacting_actor = actor; +#ifdef USE_SOCKETW + if (App::mp_state->getEnum() == MpState::CONNECTED && !m_is_remote) + { + if (m_contacting_actor) + { + NetCharacterMsgAttach msg; + msg.command = CHARACTER_CMD_ATTACH_CAB; + msg.source_id = m_contacting_actor->ar_net_source_id; + msg.stream_id = m_contacting_actor->ar_net_stream_id; + App::GetNetwork()->AddPacket(m_stream_id, RoRnet::MSG2_STREAM_DATA, sizeof(NetCharacterMsgAttach), (char*)&msg); + } + else + { + NetCharacterMsgGeneric msg; + msg.command = CHARACTER_CMD_DETACH; + App::GetNetwork()->AddPacket(m_stream_id, RoRnet::MSG2_STREAM_DATA, sizeof(NetCharacterMsgGeneric), (char*)&msg); + } + } +#endif // USE_SOCKETW +} + // -------------------------------- // GfxCharacter @@ -714,3 +870,54 @@ void RoR::GfxCharacter::UpdateCharacterInScene() } #endif // USE_SOCKETW } + +void Character::drawCabWalkingDbg() +{ + ImGui::Text("Character '%s' (remote: %d)", + m_instance_name.c_str(), (int)m_is_remote); + + if (m_contacting_actor) + { + ImGui::Text("Contacting actor: %s (%d cabs)", + m_contacting_actor->ar_design_name.c_str(), + m_contacting_actor->ar_num_cabs); + ImGui::Text("... cabIndex: %d, cabLocalPos X=%6.2f, Y=%6.2f, Z=%6.2f", + m_contacting_cab, + m_contacting_cab_localpos.x, m_contacting_cab_localpos.y, m_contacting_cab_localpos.z); + ImGui::Text("... prev_cabIndex: %d, prev_cabLocalPos X=%6.2f, Y=%6.2f, Z=%6.2f", + m_prev_contacting_cab, + m_prev_contacting_cab_localpos.x, m_prev_contacting_cab_localpos.y, m_prev_contacting_cab_localpos.z); + } + ImGui::Separator(); + + if (!m_is_remote) + { + for (ActorPtr actor : App::GetGameContext()->GetActorManager()->GetActors()) + { + ImGui::Text("Available actor: %s (%d collcabs/%d cabs)", + actor->ar_design_name.c_str(), + actor->ar_num_collcabs, + actor->ar_num_cabs); + + for (int collcabid = 0; collcabid < actor->ar_num_collcabs; collcabid++) + { + int cabid = actor->ar_collcabs[collcabid]; + if (ImGui::Button(fmt::format("GlueTo {} (cab {})", collcabid, cabid).c_str())) + { + m_contacting_actor = actor; + m_contacting_cab = cabid; + m_contacting_cab_localpos = Ogre::Vector3(0, 0, 0); + + // first contact - initialize 'prev' values to avoid big knockbacks + m_prev_contacting_cab = cabid; + m_prev_contacting_cab_matrix = CalcCabTransformMatrix(actor, cabid); + m_prev_contacting_cab_localpos = Ogre::Vector3(0, 0, 0); + } + ImGui::SameLine(); + } + ImGui::Separator(); + } + + ImGui::Separator(); + } +} diff --git a/source/main/gameplay/Character.h b/source/main/gameplay/Character.h index 21c6071367..75b76c6b36 100644 --- a/source/main/gameplay/Character.h +++ b/source/main/gameplay/Character.h @@ -61,8 +61,10 @@ class Character void update(float dt); void updateCharacterRotation(); void receiveStreamData(unsigned int& type, int& source, unsigned int& streamid, char* buffer); - void SetActorCoupling(bool enabled, ActorPtr actor); + void SetActorCoupling(ActorPtr actor); //!< Seating + void SetContactingActor(ActorPtr); //!< Standing - collision GfxCharacter* SetupGfx(); + void drawCabWalkingDbg(); private: @@ -71,7 +73,6 @@ class Character void SendStreamSetup(); void SetAnimState(std::string mode, float time = 0); - ActorPtr m_actor_coupling; //!< The vehicle or machine which the character occupies Ogre::Radian m_character_rotation; float m_character_h_speed; float m_character_v_speed; @@ -84,13 +85,29 @@ class Character bool m_is_remote; std::string m_anim_name; float m_anim_time; - float m_net_last_anim_time; - float m_driving_anim_length; + float m_net_last_anim_time; std::string m_instance_name; Ogre::UTFString m_net_username; Ogre::Timer m_net_timer; unsigned long m_net_last_update_time; GfxCharacter* m_gfx_character; + + // Occupying an actor (seating): + ActorPtr m_actor_coupling; //!< The vehicle or machine which the character occupies + float m_driving_anim_length; + + // Collision with actor (standing): + bool m_inertia = false; + Ogre::Vector3 m_inertia_position; + Ogre::Radian m_inertia_rotation; + ActorPtr m_contacting_actor; + int m_contacting_cab = 0; + Ogre::Affine3 m_contacting_cab_matrix; + Ogre::Vector3 m_contacting_cab_localpos; + int m_prev_contacting_cab = 0; + Ogre::Affine3 m_prev_contacting_cab_matrix; + Ogre::Vector3 m_prev_contacting_cab_localpos; + Ogre::Affine3 CalcCabTransformMatrix(ActorPtr& actor, int cab_index); }; /// @} // addtogroup Character @@ -105,7 +122,7 @@ struct GfxCharacter Ogre::UTFString simbuf_net_username; bool simbuf_is_remote; int simbuf_color_number; - ActorPtr simbuf_actor_coupling; + ActorPtr simbuf_actor_coupling; std::string simbuf_anim_name; float simbuf_anim_time; // Intentionally left empty = forces initial update. }; diff --git a/source/main/gameplay/CharacterFactory.cpp b/source/main/gameplay/CharacterFactory.cpp index 78a2e60665..95df243e19 100644 --- a/source/main/gameplay/CharacterFactory.cpp +++ b/source/main/gameplay/CharacterFactory.cpp @@ -86,13 +86,23 @@ void CharacterFactory::Update(float dt) } } +void CharacterFactory::DrawDebug() +{ + m_local_character->drawCabWalkingDbg(); + + for (auto& c : m_remote_characters) + { + c->drawCabWalkingDbg(); + } +} + void CharacterFactory::UndoRemoteActorCoupling(ActorPtr actor) { for (auto& c : m_remote_characters) { if (c->GetActorCoupling() == actor) { - c->SetActorCoupling(false, nullptr); + c->SetActorCoupling(nullptr); } } } diff --git a/source/main/gameplay/CharacterFactory.h b/source/main/gameplay/CharacterFactory.h index 2ddde2794d..7f3f2382de 100644 --- a/source/main/gameplay/CharacterFactory.h +++ b/source/main/gameplay/CharacterFactory.h @@ -45,6 +45,7 @@ class CharacterFactory void DeleteAllCharacters(); void UndoRemoteActorCoupling(ActorPtr actor); void Update(float dt); + void DrawDebug(); #ifdef USE_SOCKETW void handleStreamData(std::vector packet); #endif // USE_SOCKETW diff --git a/source/main/gui/panels/GUI_TopMenubar.cpp b/source/main/gui/panels/GUI_TopMenubar.cpp index 82d6128586..cfbc2544f3 100644 --- a/source/main/gui/panels/GUI_TopMenubar.cpp +++ b/source/main/gui/panels/GUI_TopMenubar.cpp @@ -603,6 +603,12 @@ void TopMenubar::Update() DrawGCheckbox(App::mp_pseudo_collisions, _LC("TopMenubar", "Collisions")); DrawGCheckbox(App::mp_hide_net_labels, _LC("TopMenubar", "Hide labels")); } + if (App::mp_state->getEnum() != MpState::CONNECTED) + { + ImGui::Separator(); + ImGui::TextColored(GRAY_HINT_TEXT, "%s", _LC("TopMenubar", "Miscellaneous:")); + DrawGCheckbox(App::sim_character_collisions, _LC("TopMenubar", "Character collisions")); + } ImGui::PopItemWidth(); m_open_menu_hoverbox_min = menu_pos; m_open_menu_hoverbox_max.x = menu_pos.x + ImGui::GetWindowWidth(); diff --git a/source/main/main.cpp b/source/main/main.cpp index aaf8b12280..560f296338 100644 --- a/source/main/main.cpp +++ b/source/main/main.cpp @@ -1020,6 +1020,7 @@ int main(int argc, char *argv[]) App::GetGuiManager()->FrictionSettings.setActiveCol(App::GetGameContext()->GetPlayerActor()->ar_last_fuzzy_ground_model); } } + App::GetGameContext()->GetCharacterFactory()->DrawDebug(); } #ifdef USE_MUMBLE diff --git a/source/main/network/Network.h b/source/main/network/Network.h index 4ba0339f4f..0a81b2a92c 100644 --- a/source/main/network/Network.h +++ b/source/main/network/Network.h @@ -51,9 +51,11 @@ namespace RoR { enum NetCharacterCmd { CHARACTER_CMD_INVALID, - CHARACTER_CMD_POSITION, - CHARACTER_CMD_ATTACH, - CHARACTER_CMD_DETACH + CHARACTER_CMD_POSITION_GROUND, + CHARACTER_CMD_POSITION_CAB, + CHARACTER_CMD_ATTACH_SEAT, //!< Sets 'actor coupling' for seated (+driving) animation. + CHARACTER_CMD_ATTACH_CAB, //!< Sets 'contacting actor' for walking on cab triangles. + CHARACTER_CMD_DETACH //!< Detaches from actor }; struct NetCharacterMsgGeneric @@ -64,10 +66,13 @@ struct NetCharacterMsgGeneric struct NetCharacterMsgPos { int32_t command; - float pos_x, pos_y, pos_z; - float rot_angle; + // Both on ground and cab: + float pos_x, pos_y, pos_z; //!< Global when on ground, local when on cab. + float rot_angle; //!< Always global. float anim_time; char anim_name[CHARACTER_ANIM_NAME_LEN]; + // Only on cab: + int32_t cab_index; }; struct NetCharacterMsgAttach diff --git a/source/main/system/CVar.cpp b/source/main/system/CVar.cpp index 2500aebad0..a91d37015a 100644 --- a/source/main/system/CVar.cpp +++ b/source/main/system/CVar.cpp @@ -58,6 +58,7 @@ void Console::cVarSetupBuiltins() App::sim_gearbox_mode = this->cVarCreate("sim_gearbox_mode", "GearboxMode", CVAR_ARCHIVE | CVAR_TYPE_INT); App::sim_soft_reset_mode = this->cVarCreate("sim_soft_reset_mode", "", CVAR_TYPE_BOOL, "false"); App::sim_quickload_dialog = this->cVarCreate("sim_quickload_dialog", "", CVAR_ARCHIVE | CVAR_TYPE_BOOL, "true"); + App::sim_character_collisions = this->cVarCreate("sim_character_collisions", "CharacterCollisions", CVAR_ARCHIVE | CVAR_TYPE_BOOL, "false"); App::mp_state = this->cVarCreate("mp_state", "", CVAR_TYPE_INT, "0"/*(int)MpState::DISABLED*/); App::mp_join_on_startup = this->cVarCreate("mp_join_on_startup", "Auto connect", CVAR_ARCHIVE | CVAR_TYPE_BOOL, "false");