18_CharacterDemo.as 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. // Moving character example.
  2. // This sample demonstrates:
  3. // - Controlling a humanoid character through physics
  4. // - Driving animations using the AnimationController component
  5. // - Manual control of a bone scene node
  6. // - Implementing 1st and 3rd person cameras, using raycasts to avoid the 3rd person camera clipping into scenery
  7. // - Saving and loading the variables of a script object
  8. // - Using touch inputs/gyroscope for iOS/Android (implemented through an external file)
  9. #include "Scripts/Utilities/Sample.as"
  10. #include "Scripts/Utilities/Touch.as"
  11. const int CTRL_FORWARD = 1;
  12. const int CTRL_BACK = 2;
  13. const int CTRL_LEFT = 4;
  14. const int CTRL_RIGHT = 8;
  15. const int CTRL_JUMP = 16;
  16. const float MOVE_FORCE = 0.8f;
  17. const float INAIR_MOVE_FORCE = 0.02f;
  18. const float BRAKE_FORCE = 0.2f;
  19. const float JUMP_FORCE = 7.0f;
  20. const float YAW_SENSITIVITY = 0.1f;
  21. const float INAIR_THRESHOLD_TIME = 0.1f;
  22. bool firstPerson = false; // First person camera flag
  23. Node@ characterNode;
  24. void Start()
  25. {
  26. // Execute the common startup for samples
  27. SampleStart();
  28. // Create static scene content
  29. CreateScene();
  30. // Create the controllable character
  31. CreateCharacter();
  32. // Create the UI content
  33. CreateInstructions();
  34. // Set the mouse mode to use in the sample
  35. SampleInitMouseMode(MM_RELATIVE);
  36. // Subscribe to necessary events
  37. SubscribeToEvents();
  38. }
  39. void CreateScene()
  40. {
  41. scene_ = Scene();
  42. // Create scene subsystem components
  43. scene_.CreateComponent("Octree");
  44. scene_.CreateComponent("PhysicsWorld");
  45. // Create camera and define viewport. Camera does not necessarily have to belong to the scene
  46. cameraNode = Node();
  47. Camera@ camera = cameraNode.CreateComponent("Camera");
  48. camera.farClip = 300.0f;
  49. renderer.viewports[0] = Viewport(scene_, camera);
  50. // Create a Zone component for ambient lighting & fog control
  51. Node@ zoneNode = scene_.CreateChild("Zone");
  52. Zone@ zone = zoneNode.CreateComponent("Zone");
  53. zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
  54. zone.ambientColor = Color(0.15f, 0.15f, 0.15f);
  55. zone.fogColor = Color(0.5f, 0.5f, 0.7f);
  56. zone.fogStart = 100.0f;
  57. zone.fogEnd = 300.0f;
  58. // Create a directional light to the world. Enable cascaded shadows on it
  59. Node@ lightNode = scene_.CreateChild("DirectionalLight");
  60. lightNode.direction = Vector3(0.6f, -1.0f, 0.8f);
  61. Light@ light = lightNode.CreateComponent("Light");
  62. light.lightType = LIGHT_DIRECTIONAL;
  63. light.castShadows = true;
  64. light.shadowBias = BiasParameters(0.00025f, 0.5f);
  65. // Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
  66. light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
  67. // Create the floor object
  68. Node@ floorNode = scene_.CreateChild("Floor");
  69. floorNode.position = Vector3(0.0f, -0.5f, 0.0f);
  70. floorNode.scale = Vector3(200.0f, 1.0f, 200.0f);
  71. StaticModel@ object = floorNode.CreateComponent("StaticModel");
  72. object.model = cache.GetResource("Model", "Models/Box.mdl");
  73. object.material = cache.GetResource("Material", "Materials/Stone.xml");
  74. RigidBody@ body = floorNode.CreateComponent("RigidBody");
  75. // Use collision layer bit 2 to mark world scenery. This is what we will raycast against to prevent camera from going
  76. // inside geometry
  77. body.collisionLayer = 2;
  78. CollisionShape@ shape = floorNode.CreateComponent("CollisionShape");
  79. shape.SetBox(Vector3::ONE);
  80. // Create mushrooms of varying sizes
  81. const uint NUM_MUSHROOMS = 60;
  82. for (uint i = 0; i < NUM_MUSHROOMS; ++i)
  83. {
  84. Node@ objectNode = scene_.CreateChild("Mushroom");
  85. objectNode.position = Vector3(Random(180.0f) - 90.0f, 0.0f, Random(180.0f) - 90.0f);
  86. objectNode.rotation = Quaternion(0.0f, Random(360.0f), 0.0f);
  87. objectNode.SetScale(2.0f + Random(5.0f));
  88. StaticModel@ object = objectNode.CreateComponent("StaticModel");
  89. object.model = cache.GetResource("Model", "Models/Mushroom.mdl");
  90. object.material = cache.GetResource("Material", "Materials/Mushroom.xml");
  91. object.castShadows = true;
  92. RigidBody@ body = objectNode.CreateComponent("RigidBody");
  93. body.collisionLayer = 2;
  94. CollisionShape@ shape = objectNode.CreateComponent("CollisionShape");
  95. shape.SetTriangleMesh(object.model, 0);
  96. }
  97. // Create movable boxes. Let them fall from the sky at first
  98. const uint NUM_BOXES = 100;
  99. for (uint i = 0; i < NUM_BOXES; ++i)
  100. {
  101. float scale = Random(2.0f) + 0.5f;
  102. Node@ objectNode = scene_.CreateChild("Box");
  103. objectNode.position = Vector3(Random(180.0f) - 90.0f, Random(10.0f) + 10.0f, Random(180.0f) - 90.0f);
  104. objectNode.rotation = Quaternion(Random(360.0f), Random(360.0f), Random(360.0f));
  105. objectNode.SetScale(scale);
  106. StaticModel@ object = objectNode.CreateComponent("StaticModel");
  107. object.model = cache.GetResource("Model", "Models/Box.mdl");
  108. object.material = cache.GetResource("Material", "Materials/Stone.xml");
  109. object.castShadows = true;
  110. RigidBody@ body = objectNode.CreateComponent("RigidBody");
  111. body.collisionLayer = 2;
  112. // Bigger boxes will be heavier and harder to move
  113. body.mass = scale * 2.0f;
  114. CollisionShape@ shape = objectNode.CreateComponent("CollisionShape");
  115. shape.SetBox(Vector3::ONE);
  116. }
  117. }
  118. void CreateCharacter()
  119. {
  120. characterNode = scene_.CreateChild("Jack");
  121. characterNode.position = Vector3(0.0f, 1.0f, 0.0f);
  122. Node@ adjNode = characterNode.CreateChild("AdjNode");
  123. adjNode.rotation = Quaternion(180.0f, Vector3::UP);
  124. // Create the rendering component + animation controller
  125. AnimatedModel@ object = adjNode.CreateComponent("AnimatedModel");
  126. object.model = cache.GetResource("Model", "Models/Mutant/Mutant.mdl");
  127. object.material = cache.GetResource("Material", "Models/Mutant/Materials/mutant_M.xml");
  128. object.castShadows = true;
  129. adjNode.CreateComponent("AnimationController");
  130. // Set the head bone for manual control
  131. object.skeleton.GetBone("Mutant:Head").animated = false;
  132. // Create rigidbody, and set non-zero mass so that the body becomes dynamic
  133. RigidBody@ body = characterNode.CreateComponent("RigidBody");
  134. body.collisionLayer = 1;
  135. body.mass = 1.0f;
  136. // Set zero angular factor so that physics doesn't turn the character on its own.
  137. // Instead we will control the character yaw manually
  138. body.angularFactor = Vector3::ZERO;
  139. // Set the rigidbody to signal collision also when in rest, so that we get ground collisions properly
  140. body.collisionEventMode = COLLISION_ALWAYS;
  141. // Set a capsule shape for collision
  142. CollisionShape@ shape = characterNode.CreateComponent("CollisionShape");
  143. shape.SetCapsule(0.7f, 1.8f, Vector3(0.0f, 0.9f, 0.0f));
  144. // Create the character logic object, which takes care of steering the rigidbody
  145. characterNode.CreateScriptObject(scriptFile, "Character");
  146. }
  147. void CreateInstructions()
  148. {
  149. // Construct new Text object, set string to display and font to use
  150. Text@ instructionText = ui.root.CreateChild("Text");
  151. instructionText.text =
  152. "Use WASD keys and mouse to move\n"
  153. "Space to jump, F to toggle 1st/3rd person\n"
  154. "F5 to save scene, F7 to load";
  155. instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
  156. // The text has multiple rows. Center them in relation to each other
  157. instructionText.textAlignment = HA_CENTER;
  158. // Position the text relative to the screen center
  159. instructionText.horizontalAlignment = HA_CENTER;
  160. instructionText.verticalAlignment = VA_CENTER;
  161. instructionText.SetPosition(0, ui.root.height / 4);
  162. }
  163. void SubscribeToEvents()
  164. {
  165. // Subscribe to Update event for setting the character controls before physics simulation
  166. SubscribeToEvent("Update", "HandleUpdate");
  167. // Subscribe to PostUpdate event for updating the camera position after physics simulation
  168. SubscribeToEvent("PostUpdate", "HandlePostUpdate");
  169. // Unsubscribe the SceneUpdate event from base class as the camera node is being controlled in HandlePostUpdate() in this sample
  170. UnsubscribeFromEvent("SceneUpdate");
  171. }
  172. void HandleUpdate(StringHash eventType, VariantMap& eventData)
  173. {
  174. if (characterNode is null)
  175. return;
  176. Character@ character = cast<Character>(characterNode.scriptObject);
  177. if (character is null)
  178. return;
  179. // Clear previous controls
  180. character.controls.Set(CTRL_FORWARD | CTRL_BACK | CTRL_LEFT | CTRL_RIGHT | CTRL_JUMP, false);
  181. // Update controls using touch utility
  182. if (touchEnabled)
  183. UpdateTouches(character.controls);
  184. // Update controls using keys (desktop)
  185. if (ui.focusElement is null)
  186. {
  187. if (touchEnabled || !useGyroscope)
  188. {
  189. character.controls.Set(CTRL_FORWARD, input.keyDown[KEY_W]);
  190. character.controls.Set(CTRL_BACK, input.keyDown[KEY_S]);
  191. character.controls.Set(CTRL_LEFT, input.keyDown[KEY_A]);
  192. character.controls.Set(CTRL_RIGHT, input.keyDown[KEY_D]);
  193. }
  194. character.controls.Set(CTRL_JUMP, input.keyDown[KEY_SPACE]);
  195. // Add character yaw & pitch from the mouse motion or touch input
  196. if (touchEnabled)
  197. {
  198. for (uint i = 0; i < input.numTouches; ++i)
  199. {
  200. TouchState@ state = input.touches[i];
  201. if (state.touchedElement is null) // Touch on empty space
  202. {
  203. Camera@ camera = cameraNode.GetComponent("Camera");
  204. if (camera is null)
  205. return;
  206. character.controls.yaw += TOUCH_SENSITIVITY * camera.fov / graphics.height * state.delta.x;
  207. character.controls.pitch += TOUCH_SENSITIVITY * camera.fov / graphics.height * state.delta.y;
  208. }
  209. }
  210. }
  211. else
  212. {
  213. character.controls.yaw += input.mouseMoveX * YAW_SENSITIVITY;
  214. character.controls.pitch += input.mouseMoveY * YAW_SENSITIVITY;
  215. }
  216. // Limit pitch
  217. character.controls.pitch = Clamp(character.controls.pitch, -80.0f, 80.0f);
  218. // Set rotation already here so that it's updated every rendering frame instead of every physics frame
  219. characterNode.rotation = Quaternion(character.controls.yaw, Vector3::UP);
  220. // Switch between 1st and 3rd person
  221. if (input.keyPress[KEY_F])
  222. firstPerson = !firstPerson;
  223. // Turn on/off gyroscope on mobile platform
  224. if (input.keyPress[KEY_G])
  225. useGyroscope = !useGyroscope;
  226. // Check for loading / saving the scene
  227. if (input.keyPress[KEY_F5])
  228. {
  229. File saveFile(fileSystem.programDir + "Data/Scenes/CharacterDemo.xml", FILE_WRITE);
  230. scene_.SaveXML(saveFile);
  231. }
  232. if (input.keyPress[KEY_F7])
  233. {
  234. File loadFile(fileSystem.programDir + "Data/Scenes/CharacterDemo.xml", FILE_READ);
  235. scene_.LoadXML(loadFile);
  236. // After loading we have to reacquire the character scene node, as it has been recreated
  237. // Simply find by name as there's only one of them
  238. characterNode = scene_.GetChild("Jack", true);
  239. if (characterNode is null)
  240. return;
  241. }
  242. }
  243. }
  244. void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
  245. {
  246. if (characterNode is null)
  247. return;
  248. Character@ character = cast<Character>(characterNode.scriptObject);
  249. if (character is null)
  250. return;
  251. // Get camera lookat dir from character yaw + pitch
  252. Quaternion rot = characterNode.rotation;
  253. Quaternion dir = rot * Quaternion(character.controls.pitch, Vector3::RIGHT);
  254. // Turn head to camera pitch, but limit to avoid unnatural animation
  255. Node@ headNode = characterNode.GetChild("Mutant:Head", true);
  256. float limitPitch = Clamp(character.controls.pitch, -45.0f, 45.0f);
  257. Quaternion headDir = rot * Quaternion(limitPitch, Vector3(1.0f, 0.0f, 0.0f));
  258. // This could be expanded to look at an arbitrary target, now just look at a point in front
  259. Vector3 headWorldTarget = headNode.worldPosition + headDir * Vector3(0.0f, 0.0f, -1.0f);
  260. headNode.LookAt(headWorldTarget, Vector3(0.0f, 1.0f, 0.0f));
  261. if (firstPerson)
  262. {
  263. // First person camera: position to the head bone + offset slightly forward & up
  264. cameraNode.position = headNode.worldPosition + rot * Vector3(0.0f, 0.15f, 0.2f);
  265. cameraNode.rotation = dir;
  266. }
  267. else
  268. {
  269. // Third person camera: position behind the character
  270. Vector3 aimPoint = characterNode.position + rot * Vector3(0.0f, 1.7f, 0.0f); // You can modify x Vector3 value to translate the fixed character position (indicative range[-2;2])
  271. // Collide camera ray with static physics objects (layer bitmask 2) to ensure we see the character properly
  272. Vector3 rayDir = dir * Vector3::BACK; // For indoor scenes you can use dir * Vector3(0.0, 0.0, -0.5) to prevent camera from crossing the walls
  273. float rayDistance = cameraDistance;
  274. PhysicsRaycastResult result = scene_.physicsWorld.RaycastSingle(Ray(aimPoint, rayDir), rayDistance, 2);
  275. if (result.body !is null)
  276. rayDistance = Min(rayDistance, result.distance);
  277. rayDistance = Clamp(rayDistance, CAMERA_MIN_DIST, cameraDistance);
  278. cameraNode.position = aimPoint + rayDir * rayDistance;
  279. cameraNode.rotation = dir;
  280. }
  281. }
  282. // Character script object class
  283. //
  284. // Those public member variables that can be expressed with a Variant and do not begin with an underscore are automatically
  285. // loaded / saved as attributes of the ScriptInstance component. We also have variables which can not be automatically saved
  286. // (yaw and pitch inside the Controls object) so we write manual binary format load / save methods for them. These functions
  287. // will be called by ScriptInstance when the script object is being loaded or saved.
  288. class Character : ScriptObject
  289. {
  290. // Character controls.
  291. Controls controls;
  292. // Grounded flag for movement.
  293. bool onGround = false;
  294. // Jump flag.
  295. bool okToJump = true;
  296. // In air timer. Due to possible physics inaccuracy, character can be off ground for max. 1/10 second and still be allowed to move.
  297. float inAirTimer = 0.0f;
  298. void Start()
  299. {
  300. SubscribeToEvent(node, "NodeCollision", "HandleNodeCollision");
  301. }
  302. void Load(Deserializer& deserializer)
  303. {
  304. controls.yaw = deserializer.ReadFloat();
  305. controls.pitch = deserializer.ReadFloat();
  306. }
  307. void Save(Serializer& serializer)
  308. {
  309. serializer.WriteFloat(controls.yaw);
  310. serializer.WriteFloat(controls.pitch);
  311. }
  312. void HandleNodeCollision(StringHash eventType, VariantMap& eventData)
  313. {
  314. VectorBuffer contacts = eventData["Contacts"].GetBuffer();
  315. while (!contacts.eof)
  316. {
  317. Vector3 contactPosition = contacts.ReadVector3();
  318. Vector3 contactNormal = contacts.ReadVector3();
  319. float contactDistance = contacts.ReadFloat();
  320. float contactImpulse = contacts.ReadFloat();
  321. // If contact is below node center and pointing up, assume it's a ground contact
  322. if (contactPosition.y < (node.position.y + 1.0f))
  323. {
  324. float level = contactNormal.y;
  325. if (level > 0.75)
  326. onGround = true;
  327. }
  328. }
  329. }
  330. void FixedUpdate(float timeStep)
  331. {
  332. /// \todo Could cache the components for faster access instead of finding them each frame
  333. RigidBody@ body = node.GetComponent("RigidBody");
  334. AnimationController@ animCtrl = node.GetComponent("AnimationController", true);
  335. // Update the in air timer. Reset if grounded
  336. if (!onGround)
  337. inAirTimer += timeStep;
  338. else
  339. inAirTimer = 0.0f;
  340. // When character has been in air less than 1/10 second, it's still interpreted as being on ground
  341. bool softGrounded = inAirTimer < INAIR_THRESHOLD_TIME;
  342. // Update movement & animation
  343. Quaternion rot = node.rotation;
  344. Vector3 moveDir(0.0f, 0.0f, 0.0f);
  345. Vector3 velocity = body.linearVelocity;
  346. // Velocity on the XZ plane
  347. Vector3 planeVelocity(velocity.x, 0.0f, velocity.z);
  348. if (controls.IsDown(CTRL_FORWARD))
  349. moveDir += Vector3::FORWARD;
  350. if (controls.IsDown(CTRL_BACK))
  351. moveDir += Vector3::BACK;
  352. if (controls.IsDown(CTRL_LEFT))
  353. moveDir += Vector3::LEFT;
  354. if (controls.IsDown(CTRL_RIGHT))
  355. moveDir += Vector3::RIGHT;
  356. // Normalize move vector so that diagonal strafing is not faster
  357. if (moveDir.lengthSquared > 0.0f)
  358. moveDir.Normalize();
  359. // If in air, allow control, but slower than when on ground
  360. body.ApplyImpulse(rot * moveDir * (softGrounded ? MOVE_FORCE : INAIR_MOVE_FORCE));
  361. if (softGrounded)
  362. {
  363. // When on ground, apply a braking force to limit maximum ground velocity
  364. Vector3 brakeForce = -planeVelocity * BRAKE_FORCE;
  365. body.ApplyImpulse(brakeForce);
  366. // Jump. Must release jump control between jumps
  367. if (controls.IsDown(CTRL_JUMP))
  368. {
  369. if (okToJump)
  370. {
  371. body.ApplyImpulse(Vector3::UP * JUMP_FORCE);
  372. okToJump = false;
  373. animCtrl.PlayExclusive("Models/Mutant/Mutant_Jump1.ani", 0, false, 0.2f);
  374. }
  375. }
  376. else
  377. okToJump = true;
  378. }
  379. if (!onGround)
  380. {
  381. animCtrl.PlayExclusive("Models/Mutant/Mutant_Jump1.ani", 0, false, 0.2f);
  382. }
  383. else
  384. {
  385. // Play walk animation if moving on ground, otherwise fade it out
  386. if (softGrounded && !moveDir.Equals(Vector3::ZERO))
  387. {
  388. animCtrl.PlayExclusive("Models/Mutant/Mutant_Run.ani", 0, true, 0.2f);
  389. // Set walk animation speed proportional to velocity
  390. animCtrl.SetSpeed("Models/Mutant/Mutant_Run.ani", planeVelocity.length * 0.3f);
  391. }
  392. else
  393. animCtrl.PlayExclusive("Models/Mutant/Mutant_Idle0.ani", 0, true, 0.2f);
  394. }
  395. // Reset grounded flag for next frame
  396. onGround = false;
  397. }
  398. }
  399. // Create XML patch instructions for screen joystick layout specific to this sample app
  400. String patchInstructions =
  401. "<patch>" +
  402. " <add sel=\"/element\">" +
  403. " <element type=\"Button\">" +
  404. " <attribute name=\"Name\" value=\"Button3\" />" +
  405. " <attribute name=\"Position\" value=\"-120 -120\" />" +
  406. " <attribute name=\"Size\" value=\"96 96\" />" +
  407. " <attribute name=\"Horiz Alignment\" value=\"Right\" />" +
  408. " <attribute name=\"Vert Alignment\" value=\"Bottom\" />" +
  409. " <attribute name=\"Texture\" value=\"Texture2D;Textures/TouchInput.png\" />" +
  410. " <attribute name=\"Image Rect\" value=\"96 0 192 96\" />" +
  411. " <attribute name=\"Hover Image Offset\" value=\"0 0\" />" +
  412. " <attribute name=\"Pressed Image Offset\" value=\"0 0\" />" +
  413. " <element type=\"Text\">" +
  414. " <attribute name=\"Name\" value=\"Label\" />" +
  415. " <attribute name=\"Horiz Alignment\" value=\"Center\" />" +
  416. " <attribute name=\"Vert Alignment\" value=\"Center\" />" +
  417. " <attribute name=\"Color\" value=\"0 0 0 1\" />" +
  418. " <attribute name=\"Text\" value=\"Gyroscope\" />" +
  419. " </element>" +
  420. " <element type=\"Text\">" +
  421. " <attribute name=\"Name\" value=\"KeyBinding\" />" +
  422. " <attribute name=\"Text\" value=\"G\" />" +
  423. " </element>" +
  424. " </element>" +
  425. " </add>" +
  426. " <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />" +
  427. " <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">1st/3rd</replace>" +
  428. " <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">" +
  429. " <element type=\"Text\">" +
  430. " <attribute name=\"Name\" value=\"KeyBinding\" />" +
  431. " <attribute name=\"Text\" value=\"F\" />" +
  432. " </element>" +
  433. " </add>" +
  434. " <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />" +
  435. " <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Jump</replace>" +
  436. " <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">" +
  437. " <element type=\"Text\">" +
  438. " <attribute name=\"Name\" value=\"KeyBinding\" />" +
  439. " <attribute name=\"Text\" value=\"SPACE\" />" +
  440. " </element>" +
  441. " </add>" +
  442. "</patch>";