From 405cc32cf7b929c3d034f65c8ab79c3bcfb79c75 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Sat, 8 Jul 2023 17:56:22 -0400 Subject: [PATCH] Add SFX support to GUI buttons. This also adds the scaffolding for other GUI control types to easily export sound effects without requiring too much boilerplate. --- korman/properties/modifiers/game_gui.py | 76 ++++++++++++++++++++++++- korman/ui/modifiers/game_gui.py | 13 ++++- 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/korman/properties/modifiers/game_gui.py b/korman/properties/modifiers/game_gui.py index d872605..a8a6c9a 100644 --- a/korman/properties/modifiers/game_gui.py +++ b/korman/properties/modifiers/game_gui.py @@ -33,6 +33,13 @@ if TYPE_CHECKING: from ..prop_world import PlasmaAge, PlasmaPage class _GameGuiMixin: + @property + def gui_sounds(self) -> Iterable[Tuple[str, int]]: + """Overload to automatically export GUI sounds on the control. This should return an iterable + of tuple attribute name and sound index. + """ + return [] + def get_control(self, exporter: Exporter, bo: Optional[bpy.types.Object] = None, so: Optional[plSceneObject] = None) -> Optional[pfGUIControlMod]: return None @@ -82,6 +89,16 @@ class _GameGuiMixin: if num_controls > 1: raise ExportError(f"'{self.id_data.name}': Only 1 GUI Control modifier is allowed per object. We found {num_controls}.") + # Blow up on invalid sounds + soundemit = self.id_data.plasma_modifiers.soundemit + for attr_name, _ in self.gui_sounds: + sound_name = getattr(self, attr_name) + if not sound_name: + continue + sound = next((i for i in soundemit.sounds if i.name == sound_name), None) + if sound is None: + raise ExportError(f"'{self.id_data.name}': Invalid '{attr_name}' GUI Sound '{sound_name}'") + class PlasmaGameGuiControlModifier(PlasmaModifierProperties, _GameGuiMixin): pl_id = "gui_control" @@ -130,13 +147,37 @@ class PlasmaGameGuiControlModifier(PlasmaModifierProperties, _GameGuiMixin): handler.command = self.console_command ctrl.handler = handler + def convert_gui_sounds(self, exporter: Exporter, ctrl: pfGUIControlMod, ctrl_mod: _GameGuiMixin): + soundemit = ctrl_mod.id_data.plasma_modifiers.soundemit + if not ctrl_mod.gui_sounds or not soundemit.enabled: + return + + # This is a lot like the plPhysicalSndGroup where we have a vector behaving as a lookup table. + # NOTE that zero is a special value here meaning no sound, so we need to offset the sounds + # that we get from the emitter modifier by +1. + sound_indices = {} + for attr_name, gui_sound_idx in ctrl_mod.gui_sounds: + sound_name = getattr(ctrl_mod, attr_name) + if not sound_name: + continue + sound_keys = soundemit.get_sound_keys(exporter, sound_name) + sound_key, soundemit_index = next(sound_keys, (None, -1)) + if sound_key is not None: + sound_indices[gui_sound_idx] = soundemit_index + 1 + + # Compress the list to include only the highest entry we need. + if sound_indices: + ctrl.soundIndices = [sound_indices.get(i, 0) for i in range(max(sound_indices) + 1)] + def export(self, exporter: Exporter, bo: bpy.types.Object, so: plSceneObject): ctrl_mods = list(self.iterate_control_modifiers()) if not ctrl_mods: exporter.report.msg(str(list(self.iterate_control_subclasses()))) exporter.report.warn("This modifier has no effect because no GUI control modifiers are present!") for ctrl_mod in ctrl_mods: - self.convert_gui_control(exporter, ctrl_mod.get_control(exporter, bo, so), bo, so) + ctrl_obj = ctrl_mod.get_control(exporter, bo, so) + self.convert_gui_control(exporter, ctrl_obj, bo, so) + self.convert_gui_sounds(exporter, ctrl_obj, ctrl_mod) @property def has_gui_proc(self) -> bool: @@ -176,6 +217,39 @@ class PlasmaGameGuiButtonModifier(PlasmaModifierProperties, _GameGuiMixin): update=_update_notify_type ) + mouse_down_sound: str = StringProperty( + name="Mouse Down SFX", + description="Sound played when the mouse button is down", + options=set() + ) + + mouse_up_sound: str = StringProperty( + name="Mouse Up SFX", + description="Sound played when the mouse button is released", + options=set() + ) + + mouse_over_sound: str = StringProperty( + name="Mouse Over SFX", + description="Sound played when the mouse moves over the GUI button", + options=set() + ) + + mouse_off_sound: str = StringProperty( + name="Mouse Off SFX", + description="Sound played when the mouse moves off of the GUI button", + options=set() + ) + + @property + def gui_sounds(self): + return ( + ("mouse_down_sound", pfGUIButtonMod.kMouseDown), + ("mouse_up_sound", pfGUIButtonMod.kMouseUp), + ("mouse_over_sound", pfGUIButtonMod.kMouseOver), + ("mouse_off_sound", pfGUIButtonMod.kMouseOff), + ) + def get_control(self, exporter: Exporter, bo: Optional[bpy.types.Object] = None, so: Optional[plSceneObject] = None) -> pfGUIButtonMod: return exporter.mgr.find_create_object(pfGUIButtonMod, bl=bo, so=so) diff --git a/korman/ui/modifiers/game_gui.py b/korman/ui/modifiers/game_gui.py index 83c3c8f..bf17c1e 100644 --- a/korman/ui/modifiers/game_gui.py +++ b/korman/ui/modifiers/game_gui.py @@ -14,7 +14,18 @@ # along with Korman. If not, see . def gui_button(modifier, layout, context): - layout.prop(modifier, "notify_type") + row = layout.row() + row.label("Notify On:") + row.prop(modifier, "notify_type") + + soundemit = modifier.id_data.plasma_modifiers.soundemit + col = layout.column() + col.label("Sound Effects:") + col.active = soundemit.enabled + col.prop_search(modifier, "mouse_down_sound", soundemit, "sounds", text="Mouse Down", icon="SPEAKER") + col.prop_search(modifier, "mouse_up_sound", soundemit, "sounds", text="Mouse Up", icon="SPEAKER") + col.prop_search(modifier, "mouse_over_sound", soundemit, "sounds", text="Mouse Over", icon="SPEAKER") + col.prop_search(modifier, "mouse_off_sound", soundemit, "sounds", text="Mouse Off", icon="SPEAKER") def gui_control(modifier, layout, context): split = layout.split()