level.vala 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  1. /*
  2. * Copyright (c) 2012-2020 Daniele Bartolini and individual contributors.
  3. * License: https://github.com/dbartolini/crown/blob/master/LICENSE
  4. */
  5. using Gee;
  6. namespace Crown
  7. {
  8. /// Manages objects in a level.
  9. public class Level
  10. {
  11. public Project _project;
  12. // Engine connections
  13. public ConsoleClient _client;
  14. // Data
  15. public Database _db;
  16. public Database _prefabs;
  17. public Gee.HashSet<string> _loaded_prefabs;
  18. public Gee.ArrayList<Guid?> _selection;
  19. public uint _num_units;
  20. public uint _num_sounds;
  21. public string _filename;
  22. // Signals
  23. public signal void selection_changed(Gee.ArrayList<Guid?> selection);
  24. public signal void object_editor_name_changed(Guid object_id, string name);
  25. public Level(Database db, ConsoleClient client, Project project)
  26. {
  27. _project = project;
  28. // Engine connections
  29. _client = client;
  30. // Data
  31. _db = db;
  32. _db.undo_redo.connect(undo_redo_action);
  33. _prefabs = new Database();
  34. _loaded_prefabs = new Gee.HashSet<string>();
  35. _selection = new Gee.ArrayList<Guid?>();
  36. reset();
  37. }
  38. /// Resets the level
  39. public void reset()
  40. {
  41. _db.reset();
  42. _prefabs.reset();
  43. _loaded_prefabs.clear();
  44. _selection.clear();
  45. selection_changed(_selection);
  46. _num_units = 0;
  47. _num_sounds = 0;
  48. _filename = null;
  49. }
  50. /// Loads the level from @a path.
  51. public void load(string path)
  52. {
  53. reset();
  54. _db.load(path);
  55. _filename = path;
  56. }
  57. /// Saves the level to @a path.
  58. public void save(string path)
  59. {
  60. _db.save(path);
  61. _filename = path;
  62. }
  63. /// Loads the empty level template.
  64. public void load_empty_level()
  65. {
  66. load(Path.build_filename(_project.toolchain_dir(), "core/editors/levels/empty.level"));
  67. _filename = null;
  68. }
  69. public void spawn_unit(Guid id, string name, Vector3 pos, Quaternion rot, Vector3 scl)
  70. {
  71. on_unit_spawned(id, name, pos, rot, scl);
  72. send_spawn_units(new Guid[] { id });
  73. }
  74. public void destroy_objects(Guid[] ids)
  75. {
  76. Guid[] units = {};
  77. Guid[] sounds = {};
  78. foreach (Guid id in ids)
  79. {
  80. if (is_unit(id))
  81. units += id;
  82. else if (is_sound(id))
  83. sounds += id;
  84. }
  85. if (units.length > 0)
  86. {
  87. _db.add_restore_point((int)ActionType.DESTROY_UNIT, units);
  88. foreach (Guid id in units)
  89. {
  90. _db.remove_from_set(GUID_ZERO, "units", id);
  91. _db.destroy(id);
  92. }
  93. }
  94. if (sounds.length > 0)
  95. {
  96. _db.add_restore_point((int)ActionType.DESTROY_SOUND, sounds);
  97. foreach (Guid id in sounds)
  98. {
  99. _db.remove_from_set(GUID_ZERO, "sounds", id);
  100. _db.destroy(id);
  101. }
  102. }
  103. send_destroy_objects(ids);
  104. }
  105. public void move_selected_objects(Vector3 pos, Quaternion rot, Vector3 scl)
  106. {
  107. if (_selection.size == 0)
  108. return;
  109. Guid id = _selection.last();
  110. on_move_objects(new Guid[] { id }, new Vector3[] { pos }, new Quaternion[] { rot }, new Vector3[] { scl });
  111. send_move_objects(new Guid[] { id }, new Vector3[] { pos }, new Quaternion[] { rot }, new Vector3[] { scl });
  112. }
  113. public void duplicate_selected_objects()
  114. {
  115. if (_selection.size > 0)
  116. {
  117. Guid[] ids = new Guid[_selection.size];
  118. // FIXME
  119. {
  120. Guid?[] tmp = _selection.to_array();
  121. for (int i = 0; i < tmp.length; ++i)
  122. ids[i] = tmp[i];
  123. }
  124. Guid[] new_ids = new Guid[ids.length];
  125. for (int i = 0; i < new_ids.length; ++i)
  126. new_ids[i] = Guid.new_guid();
  127. duplicate_objects(ids, new_ids);
  128. }
  129. }
  130. public void destroy_selected_objects()
  131. {
  132. Guid[] ids = new Guid[_selection.size];
  133. // FIXME
  134. {
  135. Guid?[] tmp = _selection.to_array();
  136. for (int i = 0; i < tmp.length; ++i)
  137. ids[i] = tmp[i];
  138. }
  139. _selection.clear();
  140. destroy_objects(ids);
  141. }
  142. public void duplicate_objects(Guid[] ids, Guid[] new_ids)
  143. {
  144. _db.add_restore_point((int)ActionType.DUPLICATE_OBJECTS, new_ids);
  145. for (int i = 0; i < ids.length; ++i)
  146. {
  147. _db.duplicate(ids[i], new_ids[i]);
  148. if (is_unit(ids[i]))
  149. {
  150. _db.add_to_set(GUID_ZERO, "units", new_ids[i]);
  151. }
  152. else if (is_sound(ids[i]))
  153. {
  154. _db.add_to_set(GUID_ZERO, "sounds", new_ids[i]);
  155. }
  156. }
  157. send_spawn_objects(new_ids);
  158. }
  159. public void on_unit_spawned(Guid id, string name, Vector3 pos, Quaternion rot, Vector3 scl)
  160. {
  161. load_prefab(name);
  162. _db.add_restore_point((int)ActionType.SPAWN_UNIT, new Guid[] { id });
  163. _db.create(id);
  164. _db.set_property_string(id, "editor.name", "unit_%04u".printf(_num_units++));
  165. _db.set_property_string(id, "prefab", name);
  166. Guid transform_id = GUID_ZERO;
  167. Unit unit = new Unit(_db, id, _prefabs);
  168. if (unit.has_component("transform", ref transform_id))
  169. {
  170. unit.set_component_property_vector3 (transform_id, "data.position", pos);
  171. unit.set_component_property_quaternion(transform_id, "data.rotation", rot);
  172. unit.set_component_property_vector3 (transform_id, "data.scale", scl);
  173. unit.set_component_property_string (transform_id, "type", "transform");
  174. }
  175. else
  176. {
  177. _db.set_property_vector3 (id, "position", pos);
  178. _db.set_property_quaternion(id, "rotation", rot);
  179. _db.set_property_vector3 (id, "scale", scl);
  180. }
  181. _db.add_to_set(GUID_ZERO, "units", id);
  182. }
  183. public void on_sound_spawned(Guid id, string name, Vector3 pos, Quaternion rot, Vector3 scl, double range, double volume, bool loop)
  184. {
  185. _db.add_restore_point((int)ActionType.SPAWN_SOUND, new Guid[] { id });
  186. _db.create(id);
  187. _db.set_property_string (id, "editor.name", "sound_%04u".printf(_num_sounds++));
  188. _db.set_property_vector3 (id, "position", pos);
  189. _db.set_property_quaternion(id, "rotation", rot);
  190. _db.set_property_string (id, "name", name);
  191. _db.set_property_double (id, "range", range);
  192. _db.set_property_double (id, "volume", volume);
  193. _db.set_property_bool (id, "loop", loop);
  194. _db.add_to_set(GUID_ZERO, "sounds", id);
  195. }
  196. public void on_move_objects(Guid[] ids, Vector3[] positions, Quaternion[] rotations, Vector3[] scales)
  197. {
  198. _db.add_restore_point((int)ActionType.MOVE_OBJECTS, ids);
  199. for (int i = 0; i < ids.length; ++i)
  200. {
  201. Guid id = ids[i];
  202. Vector3 pos = positions[i];
  203. Quaternion rot = rotations[i];
  204. Vector3 scl = scales[i];
  205. if (is_unit(id))
  206. {
  207. Guid transform_id = GUID_ZERO;
  208. Unit unit = new Unit(_db, id, _prefabs);
  209. if (unit.has_component("transform", ref transform_id))
  210. {
  211. unit.set_component_property_vector3 (transform_id, "data.position", pos);
  212. unit.set_component_property_quaternion(transform_id, "data.rotation", rot);
  213. unit.set_component_property_vector3 (transform_id, "data.scale", scl);
  214. }
  215. else
  216. {
  217. _db.set_property_vector3 (id, "position", pos);
  218. _db.set_property_quaternion(id, "rotation", rot);
  219. _db.set_property_vector3 (id, "scale", scl);
  220. }
  221. }
  222. else if (is_sound(id))
  223. {
  224. _db.set_property_vector3 (id, "position", pos);
  225. _db.set_property_quaternion(id, "rotation", rot);
  226. }
  227. }
  228. // FIXME: Hack to force update the properties view
  229. selection_changed(_selection);
  230. }
  231. public void on_selection(Guid[] ids)
  232. {
  233. _selection.clear();
  234. foreach (Guid id in ids)
  235. _selection.add(id);
  236. selection_changed(_selection);
  237. }
  238. public void selection_set(Guid[] ids)
  239. {
  240. _selection.clear();
  241. for (int i = 0; i < ids.length; ++i)
  242. _selection.add(ids[i]);
  243. _client.send_script(LevelEditorApi.selection_set(ids));
  244. selection_changed(_selection);
  245. }
  246. public void set_light(Guid unit_id, Guid component_id, string type, double range, double intensity, double spot_angle, Vector3 color)
  247. {
  248. _db.add_restore_point((int)ActionType.SET_LIGHT, new Guid[] { unit_id });
  249. Unit unit = new Unit(_db, unit_id, _prefabs);
  250. unit.set_component_property_string (component_id, "data.type", type);
  251. unit.set_component_property_double (component_id, "data.range", range);
  252. unit.set_component_property_double (component_id, "data.intensity", intensity);
  253. unit.set_component_property_double (component_id, "data.spot_angle", spot_angle);
  254. unit.set_component_property_vector3(component_id, "data.color", color);
  255. unit.set_component_property_string (component_id, "type", "light");
  256. _client.send_script(LevelEditorApi.set_light(unit_id, type, range, intensity, spot_angle, color));
  257. }
  258. public void set_mesh(Guid unit_id, Guid component_id, string mesh_resource, string geometry, string material, bool visible)
  259. {
  260. _db.add_restore_point((int)ActionType.SET_MESH, new Guid[] { unit_id });
  261. Unit unit = new Unit(_db, unit_id, _prefabs);
  262. unit.set_component_property_string(component_id, "data.mesh_resource", mesh_resource);
  263. unit.set_component_property_string(component_id, "data.geometry_name", geometry);
  264. unit.set_component_property_string(component_id, "data.material", material);
  265. unit.set_component_property_bool (component_id, "data.visible", visible);
  266. unit.set_component_property_string(component_id, "type", "mesh_renderer");
  267. _client.send_script(LevelEditorApi.set_mesh(unit_id, 0 /*instance_id*/, material, visible));
  268. }
  269. public void set_sprite(Guid unit_id, Guid component_id, double layer, double depth, string material, string sprite_resource, bool visible)
  270. {
  271. _db.add_restore_point((int)ActionType.SET_SPRITE, new Guid[] { unit_id });
  272. Unit unit = new Unit(_db, unit_id, _prefabs);
  273. unit.set_component_property_double(component_id, "data.layer", layer);
  274. unit.set_component_property_double(component_id, "data.depth", depth);
  275. unit.set_component_property_string(component_id, "data.material", material);
  276. unit.set_component_property_string(component_id, "data.sprite_resource", sprite_resource);
  277. unit.set_component_property_bool (component_id, "data.visible", visible);
  278. unit.set_component_property_string(component_id, "type", "sprite_renderer");
  279. _client.send_script(LevelEditorApi.set_sprite(unit_id, material, layer, depth, visible));
  280. }
  281. public void set_camera(Guid unit_id, Guid component_id, string projection, double fov, double near_range, double far_range)
  282. {
  283. _db.add_restore_point((int)ActionType.SET_CAMERA, new Guid[] { unit_id });
  284. Unit unit = new Unit(_db, unit_id, _prefabs);
  285. unit.set_component_property_string(component_id, "data.projection", projection);
  286. unit.set_component_property_double(component_id, "data.fov", fov);
  287. unit.set_component_property_double(component_id, "data.near_range", near_range);
  288. unit.set_component_property_double(component_id, "data.far_range", far_range);
  289. unit.set_component_property_string(component_id, "type", "camera");
  290. _client.send_script(LevelEditorApi.set_camera(unit_id, projection, fov, near_range, far_range));
  291. }
  292. public void set_collider(Guid unit_id, Guid component_id, string shape, string scene, string name)
  293. {
  294. _db.add_restore_point((int)ActionType.SET_COLLIDER, new Guid[] { unit_id });
  295. Unit unit = new Unit(_db, unit_id, _prefabs);
  296. unit.set_component_property_string(component_id, "data.shape", shape);
  297. unit.set_component_property_string(component_id, "data.scene", scene);
  298. unit.set_component_property_string(component_id, "data.name", name);
  299. unit.set_component_property_string(component_id, "type", "collider");
  300. // No synchronization
  301. }
  302. public void set_actor(Guid unit_id, Guid component_id, string class, string collision_filter, string material, double mass)
  303. {
  304. _db.add_restore_point((int)ActionType.SET_ACTOR, new Guid[] { unit_id });
  305. Unit unit = new Unit(_db, unit_id, _prefabs);
  306. unit.set_component_property_string(component_id, "data.class", class);
  307. unit.set_component_property_string(component_id, "data.collision_filter", collision_filter);
  308. unit.set_component_property_string(component_id, "data.material", material);
  309. unit.set_component_property_double(component_id, "data.mass", mass);
  310. unit.set_component_property_bool (component_id, "data.lock_rotation_x", (bool)unit.get_component_property_bool(component_id, "data.lock_rotation_x"));
  311. unit.set_component_property_bool (component_id, "data.lock_rotation_y", (bool)unit.get_component_property_bool(component_id, "data.lock_rotation_y"));
  312. unit.set_component_property_bool (component_id, "data.lock_rotation_z", (bool)unit.get_component_property_bool(component_id, "data.lock_rotation_z"));
  313. unit.set_component_property_bool (component_id, "data.lock_translation_x", (bool)unit.get_component_property_bool(component_id, "data.lock_translation_x"));
  314. unit.set_component_property_bool (component_id, "data.lock_translation_y", (bool)unit.get_component_property_bool(component_id, "data.lock_translation_y"));
  315. unit.set_component_property_bool (component_id, "data.lock_translation_z", (bool)unit.get_component_property_bool(component_id, "data.lock_translation_z"));
  316. unit.set_component_property_string(component_id, "type", "actor");
  317. // No synchronization
  318. }
  319. public void set_sound(Guid sound_id, string name, double range, double volume, bool loop)
  320. {
  321. _db.add_restore_point((int)ActionType.SET_SOUND, new Guid[] { sound_id });
  322. _db.set_property_string(sound_id, "name", name);
  323. _db.set_property_double(sound_id, "range", range);
  324. _db.set_property_double(sound_id, "volume", volume);
  325. _db.set_property_bool (sound_id, "loop", loop);
  326. _client.send_script(LevelEditorApi.set_sound_range(sound_id, range));
  327. }
  328. public string object_editor_name(Guid object_id)
  329. {
  330. if (_db.has_property(object_id, "editor.name"))
  331. return _db.get_property_string(object_id, "editor.name");
  332. else
  333. return "<unnamed>";
  334. }
  335. public void object_set_editor_name(Guid object_id, string name)
  336. {
  337. _db.add_restore_point((int)ActionType.OBJECT_SET_EDITOR_NAME, new Guid[] { object_id });
  338. _db.set_property_string(object_id, "editor.name", name);
  339. object_editor_name_changed(object_id, name);
  340. }
  341. private void send_spawn_units(Guid[] ids)
  342. {
  343. StringBuilder sb = new StringBuilder();
  344. generate_spawn_unit_commands(ids, sb);
  345. _client.send_script(sb.str);
  346. }
  347. private void send_spawn_sounds(Guid[] ids)
  348. {
  349. StringBuilder sb = new StringBuilder();
  350. generate_spawn_sound_commands(ids, sb);
  351. _client.send_script(sb.str);
  352. }
  353. private void send_spawn_objects(Guid[] ids)
  354. {
  355. StringBuilder sb = new StringBuilder();
  356. for (int i = 0; i < ids.length; ++i)
  357. {
  358. if (is_unit(ids[i]))
  359. {
  360. generate_spawn_unit_commands(new Guid[] { ids[i] }, sb);
  361. }
  362. else if (is_sound(ids[i]))
  363. {
  364. generate_spawn_sound_commands(new Guid[] { ids[i] }, sb);
  365. }
  366. }
  367. _client.send_script(sb.str);
  368. }
  369. private void send_destroy_objects(Guid[] ids)
  370. {
  371. StringBuilder sb = new StringBuilder();
  372. foreach (Guid id in ids)
  373. sb.append(LevelEditorApi.destroy(id));
  374. _client.send_script(sb.str);
  375. }
  376. private void send_move_objects(Guid[] ids, Vector3[] positions, Quaternion[] rotations, Vector3[] scales)
  377. {
  378. StringBuilder sb = new StringBuilder();
  379. for (int i = 0; i < ids.length; ++i)
  380. sb.append(LevelEditorApi.move_object(ids[i], positions[i], rotations[i], scales[i]));
  381. _client.send_script(sb.str);
  382. }
  383. public void send_level()
  384. {
  385. HashSet<Guid?> units = _db.get_property_set(GUID_ZERO, "units", new HashSet<Guid?>());
  386. HashSet<Guid?> sounds = _db.get_property_set(GUID_ZERO, "sounds", new HashSet<Guid?>());
  387. Guid[] unit_ids = new Guid[units.size];
  388. Guid[] sound_ids = new Guid[sounds.size];
  389. // FIXME
  390. {
  391. Guid?[] tmp = units.to_array();
  392. for (int i = 0; i < tmp.length; ++i)
  393. unit_ids[i] = tmp[i];
  394. }
  395. // FIXME
  396. {
  397. Guid?[] tmp = sounds.to_array();
  398. for (int i = 0; i < tmp.length; ++i)
  399. sound_ids[i] = tmp[i];
  400. }
  401. StringBuilder sb = new StringBuilder();
  402. sb.append(LevelEditorApi.reset());
  403. generate_spawn_unit_commands(unit_ids, sb);
  404. generate_spawn_sound_commands(sound_ids, sb);
  405. _client.send_script(sb.str);
  406. }
  407. /// <summary>
  408. /// Loads the prefab name into the database of prefabs.
  409. /// </summary>
  410. private void load_prefab(string name)
  411. {
  412. if (_loaded_prefabs.contains(name))
  413. return;
  414. Database prefab_db = new Database();
  415. // Try to load from toolchain directory first
  416. File file = File.new_for_path(Path.build_filename(_project.toolchain_dir(), name + ".unit"));
  417. if (file.query_exists())
  418. prefab_db.load(file.get_path());
  419. else
  420. prefab_db.load(Path.build_filename(_project.source_dir(), name + ".unit"));
  421. // Recursively load all sub-prefabs
  422. Value? prefab = prefab_db.get_property(GUID_ZERO, "prefab");
  423. if (prefab != null)
  424. load_prefab((string)prefab);
  425. prefab_db.copy_to(_prefabs, name);
  426. _loaded_prefabs.add(name);
  427. }
  428. private void generate_spawn_unit_commands(Guid[] unit_ids, StringBuilder sb)
  429. {
  430. foreach (Guid unit_id in unit_ids)
  431. {
  432. Unit unit = new Unit(_db, unit_id, _prefabs);
  433. if (unit.has_prefab())
  434. load_prefab(_db.get_property_string(unit_id, "prefab"));
  435. sb.append(LevelEditorApi.spawn_empty_unit(unit_id));
  436. Guid component_id = GUID_ZERO;
  437. if (unit.has_component("transform", ref component_id))
  438. {
  439. string s = LevelEditorApi.add_tranform_component(unit_id
  440. , component_id
  441. , unit.get_component_property_vector3 (component_id, "data.position")
  442. , unit.get_component_property_quaternion(component_id, "data.rotation")
  443. , unit.get_component_property_vector3 (component_id, "data.scale")
  444. );
  445. sb.append(s);
  446. }
  447. if (unit.has_component("mesh_renderer", ref component_id))
  448. {
  449. string s = LevelEditorApi.add_mesh_component(unit_id
  450. , component_id
  451. , unit.get_component_property_string(component_id, "data.mesh_resource")
  452. , unit.get_component_property_string(component_id, "data.geometry_name")
  453. , unit.get_component_property_string(component_id, "data.material")
  454. , unit.get_component_property_bool (component_id, "data.visible")
  455. );
  456. sb.append(s);
  457. }
  458. if (unit.has_component("sprite_renderer", ref component_id))
  459. {
  460. string s = LevelEditorApi.add_sprite_component(unit_id
  461. , component_id
  462. , unit.get_component_property_string(component_id, "data.sprite_resource")
  463. , unit.get_component_property_string(component_id, "data.material")
  464. , unit.get_component_property_double(component_id, "data.layer")
  465. , unit.get_component_property_double(component_id, "data.depth")
  466. , unit.get_component_property_bool (component_id, "data.visible")
  467. );
  468. sb.append(s);
  469. }
  470. if (unit.has_component("light", ref component_id))
  471. {
  472. string s = LevelEditorApi.add_light_component(unit_id
  473. , component_id
  474. , unit.get_component_property_string (component_id, "data.type")
  475. , unit.get_component_property_double (component_id, "data.range")
  476. , unit.get_component_property_double (component_id, "data.intensity")
  477. , unit.get_component_property_double (component_id, "data.spot_angle")
  478. , unit.get_component_property_vector3(component_id, "data.color")
  479. );
  480. sb.append(s);
  481. }
  482. if (unit.has_component("camera", ref component_id))
  483. {
  484. string s = LevelEditorApi.add_camera_component(unit_id
  485. , component_id
  486. , unit.get_component_property_string(component_id, "data.projection")
  487. , unit.get_component_property_double(component_id, "data.fov")
  488. , unit.get_component_property_double(component_id, "data.far_range")
  489. , unit.get_component_property_double(component_id, "data.near_range")
  490. );
  491. sb.append(s);
  492. }
  493. }
  494. }
  495. private void generate_spawn_sound_commands(Guid[] sound_ids, StringBuilder sb)
  496. {
  497. foreach (Guid sound_id in sound_ids)
  498. {
  499. string s = LevelEditorApi.spawn_sound(sound_id
  500. , _db.get_property_string (sound_id, "name")
  501. , _db.get_property_vector3 (sound_id, "position")
  502. , _db.get_property_quaternion(sound_id, "rotation")
  503. , _db.get_property_double (sound_id, "range")
  504. , _db.get_property_double (sound_id, "volume")
  505. , _db.get_property_bool (sound_id, "loop")
  506. );
  507. sb.append(s);
  508. }
  509. }
  510. private void undo_redo_action(bool undo, int id, Guid[] data)
  511. {
  512. switch (id)
  513. {
  514. case (int)ActionType.SPAWN_UNIT:
  515. {
  516. if (undo)
  517. send_destroy_objects(data);
  518. else
  519. send_spawn_units(data);
  520. }
  521. break;
  522. case (int)ActionType.DESTROY_UNIT:
  523. {
  524. if (undo)
  525. send_spawn_units(data);
  526. else
  527. send_destroy_objects(data);
  528. }
  529. break;
  530. case (int)ActionType.SPAWN_SOUND:
  531. {
  532. if (undo)
  533. send_destroy_objects(data);
  534. else
  535. send_spawn_sounds(data);
  536. }
  537. break;
  538. case (int)ActionType.DESTROY_SOUND:
  539. {
  540. if (undo)
  541. send_spawn_sounds(data);
  542. else
  543. send_destroy_objects(data);
  544. }
  545. break;
  546. case (int)ActionType.MOVE_OBJECTS:
  547. {
  548. Guid[] ids = data;
  549. Vector3[] positions = new Vector3[ids.length];
  550. Quaternion[] rotations = new Quaternion[ids.length];
  551. Vector3[] scales = new Vector3[ids.length];
  552. for (int i = 0; i < ids.length; ++i)
  553. {
  554. if (is_unit(ids[i]))
  555. {
  556. Guid unit_id = ids[i];
  557. Guid transform_id = GUID_ZERO;
  558. Unit unit = new Unit(_db, unit_id, _prefabs);
  559. if (unit.has_component("transform", ref transform_id))
  560. {
  561. positions[i] = unit.get_component_property_vector3 (transform_id, "data.position");
  562. rotations[i] = unit.get_component_property_quaternion(transform_id, "data.rotation");
  563. scales[i] = unit.get_component_property_vector3 (transform_id, "data.scale");
  564. }
  565. else
  566. {
  567. positions[i] = _db.get_property_vector3 (unit_id, "position");
  568. rotations[i] = _db.get_property_quaternion(unit_id, "rotation");
  569. scales[i] = _db.get_property_vector3 (unit_id, "scale");
  570. }
  571. }
  572. else if (is_sound(ids[i]))
  573. {
  574. Guid sound_id = ids[i];
  575. positions[i] = _db.get_property_vector3 (sound_id, "position");
  576. rotations[i] = _db.get_property_quaternion(sound_id, "rotation");
  577. scales[i] = Vector3(1.0, 1.0, 1.0);
  578. }
  579. else
  580. {
  581. assert(false);
  582. }
  583. }
  584. send_move_objects(ids, positions, rotations, scales);
  585. // FIXME: Hack to force update the properties view
  586. selection_changed(_selection);
  587. }
  588. break;
  589. case (int)ActionType.DUPLICATE_OBJECTS:
  590. {
  591. Guid[] new_ids = data;
  592. if (undo)
  593. send_destroy_objects(new_ids);
  594. else
  595. send_spawn_objects(new_ids);
  596. }
  597. break;
  598. case (int)ActionType.OBJECT_SET_EDITOR_NAME:
  599. object_editor_name_changed(data[0], object_editor_name(data[0]));
  600. break;
  601. case (int)ActionType.SET_LIGHT:
  602. {
  603. Guid unit_id = data[0];
  604. Guid component_id = GUID_ZERO;
  605. Unit unit = new Unit(_db, unit_id, _prefabs);
  606. unit.has_component("light", ref component_id);
  607. _client.send_script(LevelEditorApi.set_light(unit_id
  608. , unit.get_component_property_string (component_id, "data.type")
  609. , unit.get_component_property_double (component_id, "data.range")
  610. , unit.get_component_property_double (component_id, "data.intensity")
  611. , unit.get_component_property_double (component_id, "data.spot_angle")
  612. , unit.get_component_property_vector3(component_id, "data.color")
  613. ));
  614. // FIXME: Hack to force update the properties view
  615. selection_changed(_selection);
  616. }
  617. break;
  618. case (int)ActionType.SET_MESH:
  619. {
  620. Guid unit_id = data[0];
  621. Guid component_id = GUID_ZERO;
  622. Unit unit = new Unit(_db, unit_id, _prefabs);
  623. unit.has_component("mesh_renderer", ref component_id);
  624. _client.send_script(LevelEditorApi.set_mesh(unit_id
  625. , 0/*instance_id*/
  626. , unit.get_component_property_string(component_id, "data.material")
  627. , unit.get_component_property_bool (component_id, "data.visible")
  628. ));
  629. // FIXME: Hack to force update the properties view
  630. selection_changed(_selection);
  631. }
  632. break;
  633. case (int)ActionType.SET_SPRITE:
  634. {
  635. Guid unit_id = data[0];
  636. Guid component_id = GUID_ZERO;
  637. Unit unit = new Unit(_db, unit_id, _prefabs);
  638. unit.has_component("sprite_renderer", ref component_id);
  639. _client.send_script(LevelEditorApi.set_sprite(unit_id
  640. , unit.get_component_property_string(component_id, "data.material")
  641. , unit.get_component_property_double(component_id, "data.layer")
  642. , unit.get_component_property_double(component_id, "data.depth")
  643. , unit.get_component_property_bool (component_id, "data.visible")
  644. ));
  645. // FIXME: Hack to force update the properties view
  646. selection_changed(_selection);
  647. }
  648. break;
  649. case (int)ActionType.SET_CAMERA:
  650. {
  651. Guid unit_id = data[0];
  652. Guid component_id = GUID_ZERO;
  653. Unit unit = new Unit(_db, unit_id, _prefabs);
  654. unit.has_component("camera", ref component_id);
  655. _client.send_script(LevelEditorApi.set_camera(unit_id
  656. , unit.get_component_property_string(component_id, "data.projection")
  657. , unit.get_component_property_double(component_id, "data.fov")
  658. , unit.get_component_property_double(component_id, "data.near_range")
  659. , unit.get_component_property_double(component_id, "data.far_range")
  660. ));
  661. // FIXME: Hack to force update the properties view
  662. selection_changed(_selection);
  663. }
  664. break;
  665. case (int)ActionType.SET_COLLIDER:
  666. {
  667. // FIXME: Hack to force update the properties view
  668. selection_changed(_selection);
  669. }
  670. break;
  671. case (int)ActionType.SET_ACTOR:
  672. {
  673. // FIXME: Hack to force update the properties view
  674. selection_changed(_selection);
  675. }
  676. break;
  677. case (int)ActionType.SET_SOUND:
  678. {
  679. Guid sound_id = data[0];
  680. _client.send_script(LevelEditorApi.set_sound_range(sound_id
  681. , _db.get_property_double(sound_id, "range")
  682. ));
  683. // FIXME: Hack to force update the properties view
  684. selection_changed(_selection);
  685. }
  686. break;
  687. default:
  688. stdout.printf("Unknown undo/redo action: %d\n", id);
  689. assert(false);
  690. break;
  691. }
  692. }
  693. public bool is_unit(Guid id)
  694. {
  695. return _db.get_property_set(GUID_ZERO, "units", new HashSet<Guid?>()).contains(id);
  696. }
  697. public bool is_sound(Guid id)
  698. {
  699. return _db.get_property_set(GUID_ZERO, "sounds", new HashSet<Guid?>()).contains(id);
  700. }
  701. }
  702. }