diff --git a/combined.yaml b/combined.yaml index 2cd5a10..e69de29 100644 --- a/combined.yaml +++ b/combined.yaml @@ -1,33 +0,0 @@ -Components: - Agent: fife_rpg.components.agent - MoveAgent: fife_rpg.components.move_agent - CharacterStatistics: fife_rpg.components.character_statistics - Containable: fife_rpg.components.containable - Container: fife_rpg.components.container - Description: fife_rpg.components.description - Dialogue: fife_rpg.components.dialogue - Equip: fife_rpg.components.equip - Equipable: fife_rpg.components.equipable - FifeAgent: fife_rpg.components.fifeagent - General: fife_rpg.components.general - Lockable: fife_rpg.components.lockable - RPGEquip: fife_rpg.components.equip - Readable: fife_rpg.components.readable - Moving: fife_rpg.components.moving -Systems: - CharacterStatisticSystem: fife_rpg.systems.character_statistics - GameVariables: fife_rpg.systems.game_variables - ScriptingSystem: fife_rpg.systems.scriptingsystem -Actions: - MoveAgent: fife_rpg.actions.move_agent - Close: fife_rpg.actions.close - Lock: fife_rpg.actions.lock - Look: fife_rpg.actions.look - Open: fife_rpg.actions.open - PickUp: fife_rpg.actions.pick_up - Read: fife_rpg.actions.read - Unlock: fife_rpg.actions.unlock - MoveCamera: fife_rpg.actions.move_camera - SetGlobalLight: fife_rpg.actions.set_global_light -Behaviours: - Base: fife_rpg.behaviours.base \ No newline at end of file diff --git a/editor/common.py b/editor/common.py index 9a1e468..76ea1b0 100644 --- a/editor/common.py +++ b/editor/common.py @@ -137,3 +137,12 @@ def ask_create_path(path): os.makedirs(path) return True return False + + +def clear_text(text): + """Remove special tags from a text""" + lindex = text.lower().find("[colour=") + if lindex >= 0: + rindex = text.find("]", lindex) + text = text[:lindex] + text[rindex + 1:] + return text diff --git a/editor/components.py b/editor/components.py new file mode 100644 index 0000000..6bd97af --- /dev/null +++ b/editor/components.py @@ -0,0 +1,390 @@ +# -*- coding: utf-8 -*- +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Contains classes and functions for the components dialog""" + +from importlib import import_module + +import PyCEGUI +from fife_rpg.components.base import Base as BaseComponent + +from .dialog import Dialog +from .common import clear_text + + +class Components(Dialog): + + """Class that displays a components dialog""" + + MANDATORY_COMPONENTS = ["Agent", "FifeAgent", "General"] + + def __init__(self, app): + """Constructor""" + Dialog.__init__(self, app) + self.available_list = None + self.current_list = None + self.current_items = [] + self.available_items = [] + self.project_components = set() + self.used_bases = set() + self.components_in_use = set() + self.move_left = None + self.move_right = None + + def setup_dialog(self, root): + """Sets up the dialog windows + + Args: + + root: The root window to which the windows should be added + """ + font = root.getFont() + btn_height = font.getFontHeight() + 2 + list_width = PyCEGUI.UDim(0.45, 0) + list_height = PyCEGUI.UDim(0.99, - (2 * btn_height)) + list_buttons_width = PyCEGUI.UDim(0.1, 0) + + self.window.setArea(PyCEGUI.UDim(0, 3), PyCEGUI.UDim(0, 4), + PyCEGUI.UDim(0.4, 3), PyCEGUI.UDim(0.55, 4)) + self.window.setMinSize(PyCEGUI.USize(PyCEGUI.UDim(0.4, 3), + PyCEGUI.UDim(0.55, 4))) + self.window.setText(_("Project Settings")) + lists_container = root.createChild("HorizontalLayoutContainer") + lists_container.setHeight(list_height) + available_list = lists_container.createChild("TaharezLook/ItemListbox", + "AvailableComponents") + available_list.setWidth(list_width) + available_list.setHeight(list_height) + evt_selection_changed = PyCEGUI.ItemListbox.EventSelectionChanged + available_list.subscribeEvent(evt_selection_changed, + self.cb_list_changed) + available_list.setMultiSelectEnabled(True) + self.available_list = available_list + + lists_button_container = lists_container.createChild("VerticalLayout" + "Container") + lists_button_container.setWidth(list_buttons_width) + lists_button_container.setHeight(list_height) + lists_button_container.setVerticalAlignment(PyCEGUI.VA_CENTRE) + move_left = lists_button_container.createChild("TaharezLook/Button", + "MoveLeft") + move_left.setText("<") + move_left.setWidth(list_buttons_width) + move_left.subscribeEvent(PyCEGUI.ButtonBase.EventMouseClick, + self.cb_move_left_clicked) + move_left.setEnabled(False) + self.move_left = move_left + + move_right = lists_button_container.createChild("TaharezLook/Button", + "MoveRight") + move_right.setText(">") + move_right.setWidth(list_buttons_width) + move_right.subscribeEvent(PyCEGUI.ButtonBase.EventMouseClick, + self.cb_move_right_clicked) + move_left.setEnabled(True) + self.move_right = move_right + + current_list = lists_container.createChild("TaharezLook/ItemListbox", + "CurrentComponents") + current_list.setWidth(list_width) + current_list.setHeight(list_height) + current_list.subscribeEvent(evt_selection_changed, + self.cb_list_changed) + current_list.setMultiSelectEnabled(True) + self.current_list = current_list + + layout = root.createChild("HorizontalLayoutContainer") + layout.setHorizontalAlignment(PyCEGUI.HA_CENTRE) + layout.setHeight(PyCEGUI.UDim(0, btn_height)) + ed_available = layout.createChild("TaharezLook/Button", + "EditAvaible") + ed_available.setText(_("Edit Available components")) + ed_available.setHeight(PyCEGUI.UDim(0, btn_height)) + text_width = font.getTextExtent(ed_available.getText()) + ed_available.setWidth(PyCEGUI.UDim(0, text_width + 30)) + ed_available.subscribeEvent(PyCEGUI.ButtonBase.EventMouseClick, + self.cb_edit_available_components_clicked) + + self.project_components = set(self.app.project.get("fife-rpg", + "Components", [])) + self.update_lists() + + def update_lists(self): + """Updates the lists to the current state""" + self.available_list.resetList() + self.current_list.resetList() + self.current_items = [] + self.available_items = [] + self.used_bases = set() + self.components_in_use = set() + current_components = set(self.MANDATORY_COMPONENTS) + + for component in self.project_components: + component_class = self.app.get_component_data(component)[0] + for dependency in component_class.dependencies: + current_components.add(dependency.__name__) + for base in component_class.__bases__: + if base is BaseComponent: + continue + self.used_bases.add(base.__name__) + entities = self.app.world[...] + try: + ent_comp = getattr(entities, component) + if ent_comp: + self.components_in_use.add(component) + except AttributeError: + pass + + for component in self.project_components.copy(): + if component in self.used_bases: + self.project_components.remove(component) + continue + item = self.current_list.createChild("TaharezLook/ListboxItem") + text = component + if component in self.components_in_use: + text = "[colour='FFFF0000']" + text + item.setText(text) + self.current_items.append(item) + current_components.add(component) + + all_components = set(self.app.components.iterkeys()) + for component in all_components - self.project_components: + item = self.available_list.createChild("TaharezLook/ListboxItem") + text = component + if component in self.used_bases: + text = "[colour='FF0000FF']" + text + elif component in current_components: + text = "[colour='FFC0C0C0']" + text + item.setText(text) + self.available_items.append(item) + self.current_list.performChildWindowLayout() + self.available_list.performChildWindowLayout() + + def get_values(self): + """Returns the values of the dialog fields""" + current_items = set() + for x in xrange(self.current_list.getItemCount()): + text = self.current_list.getItemFromIndex(x).getText() + text = clear_text(text) + current_items.add(text) + return {"current_items": current_items} + + def validate(self): + """Check if the current state of the dialog fields is valid""" + return True + + def cb_move_left_clicked(self, args): + """Callback for a click on the 'Move Left' button""" + items = [] + item = self.current_list.getFirstSelectedItem() + + while item is not None: + items.append(item) + item = self.current_list.getNextSelectedItem() + + for item in items: + text = item.getText() + text = clear_text(text) + if text in self.components_in_use: + continue + index = self.current_list.getItemIndex(item) + self.current_list.removeItem(item) + del self.current_items[index] + item = self.available_list.createChild("TaharezLook/ListboxItem") + item.setText(text) + self.project_components.remove(text) + self.available_items.append(item) + self.current_list.performChildWindowLayout() + self.available_list.performChildWindowLayout() + self.update_lists() + self.cb_list_changed(None) + + def cb_move_right_clicked(self, args): + """Callback for a click on the 'Move Right' button""" + items = [] + item = self.available_list.getFirstSelectedItem() + + while item is not None: + items.append(item) + item = self.available_list.getNextSelectedItem() + + for item in items: + text = item.getText() + if text.lower().startswith("[colour"): + text = text[text.find("]") + 1:] + if text in self.used_bases: + continue + index = self.available_list.getItemIndex(item) + self.available_list.removeItem(item) + del self.available_items[index] + item = self.current_list.createChild("TaharezLook/ListboxItem") + item.setText(text) + self.project_components.add(text) + self.current_items.append(item) + self.current_list.performChildWindowLayout() + self.available_list.performChildWindowLayout() + self.update_lists() + self.cb_list_changed(None) + + def cb_list_changed(self, args): + """Called when the selected items of a list wer changed""" + self.move_left.setEnabled(self.current_list.getSelectedCount() > 0) + self.move_right.setEnabled(self.available_list.getSelectedCount() > 0) + + def cb_edit_available_components_clicked(self, args): + """Callback for a click on the 'Edit available components' button""" + self.app.edit_available_components() + self.app.current_dialog = self + self.update_lists() + + +class AvailableComponents(Dialog): + + """Dialog for editing the available components""" + + def __init__(self, app): + """Constructor""" + Dialog.__init__(self, app) + self.edit_set = None + self.text_input = None + self.add_button = None + self.delete_button = None + self.items = [] + self.values = dict() + + def setup_dialog(self, root): + """Sets up the dialog windows + + Args: + + root: The root window to which the windows should be added + """ + font = root.getFont() + text_height = font.getFontHeight() + 2 + self.window.setArea(PyCEGUI.UDim(0, 3), PyCEGUI.UDim(0, 4), + PyCEGUI.UDim(0.4, 3), PyCEGUI.UDim(0.5, 4)) + self.window.setMinSize(PyCEGUI.USize(PyCEGUI.UDim(0.4, 3), + PyCEGUI.UDim(0.5, 4))) + self.window.setText(_("Available Components")) + + set_size = PyCEGUI.USize(PyCEGUI.UDim(1.0, 0), PyCEGUI.UDim(0.8, 0)) + edit_set = root.createChild("TaharezLook/ItemListbox", + "EditComponents") + edit_set.setSize(set_size) + edit_set.subscribeEvent(PyCEGUI.ItemListbox.EventSelectionChanged, + self.cb_edit_set_changed) + edit_set.resetList() + self.items = [] + self.values = dict() + for name, path in self.app.components.iteritems(): + item = edit_set.createChild("TaharezLook/ListboxItem") + item.setText(name) + self.items.append(item) + self.values[name] = path + edit_set.performChildWindowLayout() + self.edit_set = edit_set + text_input = root.createChild("TaharezLook/Editbox", "text_input") + text_input.setHeight(PyCEGUI.UDim(0.0, text_height)) + text_input.setWidth(PyCEGUI.UDim(1.0, 0)) + text_input.subscribeEvent(PyCEGUI.Editbox.EventTextChanged, + self.cb_text_changed) + self.text_input = text_input + + buttons_layout = root.createChild("HorizontalLayoutContainer", + "buttons_layout") + buttons_layout.setHorizontalAlignment(PyCEGUI.HA_CENTRE) + add_button = buttons_layout.createChild("TaharezLook/Button", + "add_button") + add_button.setText("Add") + add_button.setHeight(PyCEGUI.UDim(0.0, text_height)) + add_button.setEnabled(False) + add_button.subscribeEvent(PyCEGUI.ButtonBase.EventMouseClick, + self.cb_add_clicked) + self.add_button = add_button + delete_button = buttons_layout.createChild("TaharezLook/Button", + "delete_button") + delete_button.setText("Delete") + delete_button.setHeight(PyCEGUI.UDim(0.0, text_height)) + delete_button.setEnabled(False) + delete_button.subscribeEvent(PyCEGUI.ButtonBase.EventMouseClick, + self.cb_delete_clicked) + self.delete_button = delete_button + + def cb_edit_set_changed(self, args): + """Called when something in the set was changed + + Args: + + args: PyCEGUI.WindowEventArgs + """ + self.delete_button.setEnabled(args.window.getSelectedCount() > 0) + + def cb_text_changed(self, args): + """Called when the editbox was changed + + Args: + + args: PyCEGUI.WindowEventArgs + """ + text = args.window.getText() + self.add_button.setEnabled(len(text) > 0 and text not in self.values) + + def cb_add_clicked(self, args): + """Called when the add button was clicked + + Args: + + args: PyCEGUI.WindowEventArgs + """ + module_path = self.text_input.getText() + path_split = module_path.split(".") + module_name = ".%s" % path_split[-1] + base_path = ".".join(path_split[:-1]) + module = import_module(module_name, base_path) + for member in dir(module): + component = getattr(module, member) + try: + if (issubclass(component, BaseComponent) and + component is not BaseComponent): + component_name = component.__name__ + if ("." in component.__module__ and + component.__module__ != module_path): + continue + if component_name not in self.values: + self.values[component_name] = module_path + item = self.edit_set.createChild("TaharezLook/" + "ListboxItem") + item.setText(component_name) + self.items.append(item) + except TypeError: + pass + self.edit_set.performChildWindowLayout() + + def cb_delete_clicked(self, args): + """Called when the delete button was clicked + + Args: + + args: PyCEGUI.WindowEventArgs + """ + item = self.edit_set.getFirstSelectedItem() + del self.values[item.getText()] + self.edit_set.removeItem(item) + + def get_values(self): + return {"components": self.values.copy()} + + def validate(self): + """Check if the current state of the dialog fields is valid""" + return True diff --git a/editor/editor_gui.py b/editor/editor_gui.py index 2ff61fe..95f7ad9 100644 --- a/editor/editor_gui.py +++ b/editor/editor_gui.py @@ -77,6 +77,7 @@ def __init__(self, app): self.import_popup = None self.edit_add = None self.add_popup = None + self.edit_components = None self.app = app self.editor = app.editor @@ -314,6 +315,15 @@ def create_menu(self): self.edit_add = edit_add self.edit_add.setEnabled(False) + edit_components = edit_popup.createChild("TaharezLook/MenuItem", + "Edit/Add") + edit_components.setText(_("Components")) + edit_components.setAutoPopupTimeout(0.5) + edit_components.subscribeEvent(PyCEGUI.MenuItem.EventClicked, + self.cb_edit_components) + self.edit_components = edit_components + self.edit_components.setEnabled(False) + # View Menu self.view_menu = self.menubar.createChild("TaharezLook/MenuItem", "View") @@ -650,6 +660,7 @@ def cb_open(self, args): self.file_import.setEnabled(True) self.project_settings.setEnabled(True) self.edit_add.setEnabled(True) + self.edit_components.setEnabled(True) tkMessageBox.showinfo(_("Project loaded"), _("Project successfully loaded")) @@ -782,6 +793,10 @@ def cb_add_map(self, args): self.app.changed_maps.append(map_id) self.reset_maps_menu() + def cb_edit_components(self, args): + """Callback when Components was clicked in the edit menu""" + self.app.edit_components() + def cb_project_cleared(self): """Called when the project was cleared""" self.file_save.setEnabled(False) @@ -789,6 +804,7 @@ def cb_project_cleared(self): self.file_close.setEnabled(False) self.project_settings.setEnabled(False) self.edit_add.setEnabled(False) + self.edit_components.setEnabled(False) self.view_maps_menu.closePopupMenu() self.save_popup.closePopupMenu() self.import_popup.closePopupMenu() diff --git a/fife_rpg_editor.py b/fife_rpg_editor.py index 4444ffa..68fdcf4 100755 --- a/fife_rpg_editor.py +++ b/fife_rpg_editor.py @@ -22,6 +22,7 @@ import os import sys +from StringIO import StringIO import yaml # pylint: disable=unused-import @@ -54,6 +55,7 @@ from editor.editor import Editor from editor.editor_scene import EditorController from editor.project_settings import ProjectSettings +from editor.components import Components, AvailableComponents BASIC_SETTINGS = """ @@ -439,17 +441,17 @@ def entity_constructor(self, loader, node): self.entities[entity.identifier] = entity_dict return entity - def load_entities(self): - """Load and store the entities of the current project""" - if self.project is None: - return - entities_file_name = self.project.get("fife-rpg", "EntitiesFile", - "objects/entities.yaml") - vfs = self.engine.getVFS() + def parse_entities(self, entities_file): + """Parse the entities from a file + + Args: + + entities_file: A file object from where the entities are being + loaded. + """ yaml.add_constructor('!Entity', self.entity_constructor, yaml.SafeLoader) - entities_file = vfs.open(entities_file_name) entities = yaml.safe_load_all(entities_file) try: while entities.next(): @@ -457,6 +459,16 @@ def load_entities(self): except StopIteration: pass + def load_entities(self): + """Load and store the entities of the current project""" + if self.project is None: + return + entities_file_name = self.project.get("fife-rpg", "EntitiesFile", + "objects/entities.yaml") + vfs = self.engine.getVFS() + entities_file = vfs.open(entities_file_name) + self.parse_entities(entities_file) + def entity_representer(self, dumper, data): """Creates a yaml node representing an entity @@ -492,17 +504,27 @@ def entity_representer(self, dumper, data): entity_node = dumper.represent_mapping(u"!Entity", entity_dict) return entity_node + def dump_entities(self, entities_file): + """Dumps the projects entities to a file + + Args: + + entities_file: A file object to where the entities are written. + """ + entities = self.world[RPGEntity].entities + yaml.add_representer(RPGEntity, self.entity_representer, + yaml.SafeDumper) + helpers.dump_entities(entities, entities_file) + def save_entities(self): """Save all entities to the entity file""" - yaml.add_representer(RPGEntity, self.entity_representer) entities_file_name = self.project.get("fife-rpg", "EntitiesFile", "objects/entities.yaml") old_wd = os.getcwd() os.chdir(self.project_dir) try: entities_file = file(entities_file_name, "w") - entities = self.world[RPGEntity].entities - helpers.dump_entities(entities, entities_file) + self.dump_entities(entities_file) self.entity_changed = False finally: os.chdir(old_wd) @@ -521,6 +543,48 @@ def save_all_maps(self): for map_name in self.changed_maps: self.save_map(map_name) + def reset_world(self, entities_file=None): + """Create a new world and set its values + + Args: + + entities_file: An optional file object to load entities from + """ + self.create_world() + try: + self.world.read_object_db() + self.world.import_agent_objects() + if entities_file is not None: + self.parse_entities(entities_file) + else: + self.load_entities() + except: # pylint: disable=bare-except + pass + + def setup_project(self): + """Sets up the project""" + try: + self.load_combined() + except: # pylint: disable=bare-except + self.load_combined("combined.yaml") + try: + self.register_components() + except ValueError: + pass + try: + self.register_actions() + except ValueError: + pass + try: + self.register_systems() + except ValueError: + pass + try: + self.register_behaviours() + except ValueError: + pass + self.reset_world() + def try_load_project(self, file_name): """Try to load the specified file as a project @@ -534,35 +598,9 @@ def try_load_project(self, file_name): self.editor_gui.reset_maps_menu() old_dir = os.getcwd() os.chdir(self.project_dir) - sys.path.append(self.project_dir) + sys.path.insert(0, self.project_dir) try: - try: - self.load_combined() - except: # pylint: disable=bare-except - self.load_combined("combined.yaml") - try: - self.register_components() - except ValueError: - pass - try: - self.register_actions() - except ValueError: - pass - try: - self.register_systems() - except ValueError: - pass - try: - self.register_behaviours() - except ValueError: - pass - self.create_world() - try: - self.world.read_object_db() - self.world.import_agent_objects() - self.load_entities() - except: # pylint: disable=bare-except - pass + self.setup_project() finally: os.chdir(old_dir) return True @@ -586,6 +624,52 @@ def save_project(self): maps_file = file(maps_filename, "w") yaml.dump(save_data, maps_file, default_flow_style=False) maps_file.close() + combined_filename = self.settings.get("fife-rpg", "CombinedFile", None) + comp_filename = self.settings.get("fife-rpg", "ComponentsFile", None) + syst_filename = self.settings.get("fife-rpg", "ActionsFile", None) + act_filename = self.settings.get("fife-rpg", "SystemsFile", None) + beh_filename = self.settings.get("fife-rpg", "BehavioursFile", None) + project_dir = self.project_dir + if None in (comp_filename, syst_filename, act_filename, beh_filename): + combined_filename = "combined.yaml" + combined = {} + if comp_filename is not None: + data = {"Components": self._components} + filename = os.path.join(project_dir, comp_filename) + stream = file(filename, "w") + yaml.dump(data, stream, dumper=helpers.FRPGDumper) + stream.close() + else: + combined["Components"] = self._components + if syst_filename is not None: + data = {"Systems": self._systems} + filename = os.path.join(project_dir, syst_filename) + stream = file(filename, "w") + yaml.dump(data, stream, dumper=helpers.FRPGDumper) + stream.close() + else: + combined["Systems"] = self._systems + if act_filename is not None: + data = {"Actions": self._actions} + filename = os.path.join(project_dir, act_filename) + stream = file(filename, "w") + yaml.dump(data, stream, dumper=helpers.FRPGDumper) + stream.close() + else: + combined["Actions"] = self._actions + if beh_filename is not None: + data = {"Behaviours": self._behaviours} + filename = os.path.join(project_dir, beh_filename) + stream = file(filename, "w") + yaml.dump(data, stream, dumper=helpers.FRPGDumper) + stream.close() + else: + combined["Behaviours"] = self._behaviours + if combined_filename is not None: + filename = os.path.join(project_dir, combined_filename) + stream = file(filename, "w") + yaml.dump(combined, stream, Dumper=helpers.FRPGDumper) + stream.close() self.project_changed = False def highlight_selected_object(self): @@ -663,6 +747,7 @@ def show_map_entities(self, map_name): agent.map = map_name game_map.update_entities() self.update_agents(game_map) + self.map_entities = None def quit(self): """ @@ -673,6 +758,49 @@ def quit(self): if self.editor_gui.ask_save_changed(): self.quitRequested = True + def edit_components(self): + """Show the dialog to edit components""" + dialog = Components(self) + values = dialog.show_modal(self.editor_gui.editor_window, + self.engine.pump) + if not dialog.return_value: + return False + entities_hidden = self.map_entities is not None + if entities_hidden: + self.show_map_entities(self.current_map.name) + tmp_file = StringIO() + self.dump_entities(tmp_file) + for entity in tuple(self.world.entities): + entity.delete() + ComponentManager.clear_components() + ComponentManager.clear_checkers() + current_items = list(values["current_items"]) + self.project.set("fife-rpg", "Components", current_items) + self.world.register_mandatory_components() + self.register_components(current_items) + tmp_file.seek(0) + self.reset_world(tmp_file) + for game_map in self.maps.itervalues(): + game_map.update_entities() + self.update_agents(game_map) + if entities_hidden: + self.hide_map_entities(self.current_map.name) + + self.project_changed = True + + def edit_available_components(self): + """Show the dialog to edit components""" + dialog = AvailableComponents(self) + values = dialog.show_modal(self.editor_gui.editor_window, + self.engine.pump) + if not dialog.return_value: + return False + ComponentManager.clear_components() + ComponentManager.clear_checkers() + components = values["components"] + self._components = components + self.project_changed = True + def update_settings(project, values): """Update the fife-rpg settings of a project diff --git a/settings-dist.xml b/settings-dist.xml index 9599025..da6ee2a 100644 --- a/settings-dist.xml +++ b/settings-dist.xml @@ -29,9 +29,5 @@ camera1 objects/agents fife-rpg - Description ; Agent ; FifeAgent ; RPGEquip ; Lockable ; Dialogue ; Container ; Moving - PickUp ; Open ; Open ; Close ; Lock ; Unlock ; MoveAgent ; Read ; Look ; MoveCamera ; SetGlobalLight - ScriptingSystem ; CharacterStatisticSystem ; GameVariables - Base