mirror of https://github.com/H-uru/korman.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
477 lines
19 KiB
477 lines
19 KiB
# This file is part of Korman. |
|
# |
|
# Korman 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. |
|
# |
|
# Korman 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 Korman. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
import bpy |
|
from bpy.props import * |
|
from collections import OrderedDict |
|
from PyHSPlasma import * |
|
|
|
from .node_core import PlasmaNodeBase, PlasmaNodeSocketBase |
|
from ..properties.modifiers.avatar import sitting_approach_flags |
|
from ..exporter.explosions import ExportError |
|
|
|
class PlasmaSittingBehaviorNode(PlasmaNodeBase, bpy.types.Node): |
|
bl_category = "AVATAR" |
|
bl_idname = "PlasmaSittingBehaviorNode" |
|
bl_label = "Sitting Behavior" |
|
bl_width_default = 120 |
|
|
|
pl_attrib = {"ptAttribActivator", "ptAttribActivatorList", "ptAttribNamedActivator"} |
|
|
|
approach = EnumProperty(name="Approach", |
|
description="Directions an avatar can approach the seat from", |
|
items=sitting_approach_flags, |
|
default={"kApproachFront", "kApproachLeft", "kApproachRight"}, |
|
options={"ENUM_FLAG"}) |
|
|
|
input_sockets = OrderedDict([ |
|
("condition", { |
|
"text": "Condition", |
|
"type": "PlasmaConditionSocket", |
|
}), |
|
]) |
|
|
|
output_sockets = OrderedDict([ |
|
("satisfies", { |
|
"text": "Satisfies", |
|
"type": "PlasmaConditionSocket", |
|
"valid_link_sockets": {"PlasmaConditionSocket", "PlasmaPythonFileNodeSocket"}, |
|
}), |
|
]) |
|
|
|
def draw_buttons(self, context, layout): |
|
col = layout.column() |
|
col.label("Approach:") |
|
col.prop(self, "approach") |
|
|
|
def draw_buttons_ext(self, context, layout): |
|
layout.prop_menu_enum(self, "approach") |
|
|
|
def get_key(self, exporter, so): |
|
return self._find_create_key(plSittingModifier, exporter, so=so) |
|
|
|
def export(self, exporter, bo, so): |
|
sitmod = self._find_create_object(plSittingModifier, exporter, so=so) |
|
for flag in self.approach: |
|
sitmod.miscFlags |= getattr(plSittingModifier, flag) |
|
for i in self.find_outputs("satisfies"): |
|
if i is not None: |
|
sitmod.addNotifyKey(i.get_key(exporter, so)) |
|
else: |
|
exporter.report.warn("'{}' Node '{}' doesn't expose a key. It won't be triggered by '{}'!".format(i.bl_idname, i.name, self.name), indent=3) |
|
|
|
@property |
|
def requires_actor(self): |
|
return True |
|
|
|
|
|
class PlasmaAnimStageAdvanceSocketIn(PlasmaNodeSocketBase, bpy.types.NodeSocket): |
|
bl_color = (0.412, 0.2, 0.055, 1.0) |
|
|
|
auto_advance = BoolProperty(name="Advance to Next Stage", |
|
description="Automatically advance to the next stage when the animation completes instead of halting", |
|
default=True) |
|
|
|
def draw_content(self, context, layout, node, text): |
|
if not self.is_linked: |
|
layout.prop(self, "auto_advance") |
|
else: |
|
if self.links[0].from_node.stage_id is not None: |
|
layout.label("{} {}".format(text, self.links[0].from_node.stage_id)) |
|
else: |
|
layout.label(text) |
|
|
|
|
|
class PlasmaAnimStageRegressSocketIn(PlasmaNodeSocketBase, bpy.types.NodeSocket): |
|
bl_color = (0.412, 0.2, 0.055, 1.0) |
|
|
|
auto_regress = BoolProperty(name="Regress to Previous Stage", |
|
description="Automatically regress to the previous stage when the animation completes instead of halting", |
|
default=True) |
|
|
|
def draw_content(self, context, layout, node, text): |
|
if not self.is_linked: |
|
layout.prop(self, "auto_regress") |
|
else: |
|
if self.links[0].from_node.stage_id is not None: |
|
layout.label("{} {}".format(text, self.links[0].from_node.stage_id)) |
|
else: |
|
layout.label(text) |
|
|
|
|
|
class PlasmaAnimStageOrderSocketOut(PlasmaNodeSocketBase, bpy.types.NodeSocket): |
|
bl_color = (0.412, 0.2, 0.055, 1.0) |
|
|
|
|
|
anim_play_flags = [("kPlayNone", "None", "Play stage only when directed by a message"), |
|
("kPlayKey", "Keyboard", "Play stage when the user presses the forward/backward key"), |
|
("kPlayAuto", "Automatic", "Play stage automatically")] |
|
anim_stage_adv_flags = [("kAdvanceNone", "None", "Advance to the next stage only when directed by a message"), |
|
("kAdvanceOnMove", "Movement", "Advance to the next stage when the user presses a movement key"), |
|
("kAdvanceAuto", "Automatic", "Advance to the next stage automatically when this one completes"), |
|
("kAdvanceOnAnyKey", "Any Keypress", "Advance to the next stage when the user presses any key")] |
|
anim_stage_rgr_flags = [("kAdvanceNone", "None", "Regress to the previous stage only when directed by a message"), |
|
("kAdvanceOnMove", "Movement", "Regress to the previous stage when the user presses a movement key"), |
|
("kAdvanceAuto", "Automatic", "Regress to the previous stage automatically when this one completes"), |
|
("kAdvanceOnAnyKey", "Any Keypress", "Regress to the previous stage when the user presses any key")] |
|
|
|
|
|
class PlasmaAnimStageSettingsSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket): |
|
bl_color = (0.412, 0.0, 0.055, 1.0) |
|
|
|
|
|
class PlasmaAnimStageSettingsNode(PlasmaNodeBase, bpy.types.Node): |
|
bl_category = "AVATAR" |
|
bl_idname = "PlasmaAnimStageSettingsNode" |
|
bl_label = "Animation Stage Settings" |
|
bl_width_default = 325 |
|
|
|
forward = EnumProperty(name="Forward", |
|
description="Selects which events cause this stage to play forward", |
|
items=anim_play_flags, |
|
default="kPlayNone") |
|
backward = EnumProperty(name="Backward", |
|
description="Selects which events cause this stage to play backward", |
|
items=anim_play_flags, |
|
default="kPlayNone") |
|
stage_advance = EnumProperty(name="Stage Advance", |
|
description="Selects which events cause this stage to advance to the next stage", |
|
items=anim_stage_adv_flags, |
|
default="kAdvanceNone") |
|
stage_regress = EnumProperty(name="Stage Regress", |
|
description="Selects which events cause this stage to regress to the previous stage", |
|
items=anim_stage_rgr_flags, |
|
default="kAdvanceNone") |
|
|
|
notify_on = EnumProperty(name="Notify", |
|
description="Which events should send notifications", |
|
items=[ |
|
("kNotifyEnter", "Enter", |
|
"Send notification when animation first begins to play"), |
|
("kNotifyLoop", "Loop", |
|
"Send notification when animation starts a loop"), |
|
("kNotifyAdvance", "Advance", |
|
"Send notification when animation is advanced"), |
|
("kNotifyRegress", "Regress", |
|
"Send notification when animation is regressed") |
|
], |
|
default={"kNotifyEnter"}, |
|
options={"ENUM_FLAG"}) |
|
|
|
input_sockets = OrderedDict([ |
|
("advance_to", { |
|
"text": "Advance to Stage", |
|
"type": "PlasmaAnimStageAdvanceSocketIn", |
|
"valid_link_nodes": "PlasmaAnimStageNode", |
|
"valid_link_sockets": "PlasmaAnimStageOrderSocketOut", |
|
"link_limit": 1, |
|
}), |
|
("regress_to", { |
|
"text": "Regress to Stage", |
|
"type": "PlasmaAnimStageRegressSocketIn", |
|
"valid_link_nodes": "PlasmaAnimStageNode", |
|
"valid_link_sockets": "PlasmaAnimStageOrderSocketOut", |
|
"link_limit": 1, |
|
}), |
|
]) |
|
|
|
output_sockets = OrderedDict([ |
|
("stage", { |
|
"text": "Stage", |
|
"type": "PlasmaAnimStageSettingsSocket", |
|
"valid_link_nodes": "PlasmaAnimStageNode", |
|
"valid_link_sockets": "PlasmaAnimStageSettingsSocket", |
|
}), |
|
]) |
|
|
|
def draw_buttons(self, context, layout): |
|
layout.prop(self, "forward") |
|
layout.prop(self, "backward") |
|
layout.prop(self, "stage_advance") |
|
layout.prop(self, "stage_regress") |
|
layout.separator() |
|
|
|
layout.label("Notify On:") |
|
row = layout.row() |
|
row.prop(self, "notify_on") |
|
layout.separator() |
|
|
|
|
|
class PlasmaAnimStageNode(PlasmaNodeBase, bpy.types.Node): |
|
bl_category = "AVATAR" |
|
bl_idname = "PlasmaAnimStageNode" |
|
bl_label = "Animation Stage" |
|
bl_width_default = 325 |
|
|
|
pl_attrib = ("ptAttribAnimation") |
|
|
|
anim_name = StringProperty(name="Animation Name", |
|
description="Name of animation to play") |
|
|
|
loop_option = EnumProperty(name="Looping", |
|
description="Loop options for animation playback", |
|
items=[("kDontLoop", "Don't Loop", "Don't loop the animation"), |
|
("kLoop", "Loop", "Loop the animation a finite number of times"), |
|
("kLoopForever", "Loop Forever", "Continue playing animation indefinitely")], |
|
default="kDontLoop") |
|
num_loops = IntProperty(name="Num Loops", |
|
description="Number of times to loop animation", |
|
default=0) |
|
|
|
input_sockets = OrderedDict([ |
|
("stage_settings", { |
|
"text": "Stage Settings", |
|
"type": "PlasmaAnimStageSettingsSocket", |
|
"valid_link_nodes": "PlasmaAnimStageSettingsNode", |
|
"valid_link_sockets": "PlasmaAnimStageSettingsSocket", |
|
"link_limit": 1, |
|
}), |
|
]) |
|
|
|
output_sockets = OrderedDict([ |
|
("stage", { |
|
"text": "Behavior", |
|
"type": "PlasmaAnimStageRefSocket", |
|
"valid_link_nodes": "PlasmaMultiStageBehaviorNode", |
|
"valid_link_sockets": "PlasmaAnimStageRefSocket", |
|
}), |
|
("stage_reference", { |
|
"text": "Stage Progression", |
|
"type": "PlasmaAnimStageOrderSocketOut", |
|
"valid_link_nodes": "PlasmaAnimStageSettingsNode", |
|
"valid_link_sockets": {"PlasmaAnimStageAdvanceSocketIn", "PlasmaAnimStageRegressSocketIn"} , |
|
}), |
|
]) |
|
|
|
def draw_buttons(self, context, layout): |
|
layout.prop(self, "anim_name") |
|
|
|
row = layout.row() |
|
row.prop(self, "loop_option") |
|
if self.loop_option == "kLoop": |
|
row = layout.row() |
|
row.prop(self, "num_loops") |
|
|
|
@property |
|
def stage_id(self): |
|
idx = None |
|
stage_socket = self.find_output_socket("stage") |
|
if stage_socket.is_linked: |
|
msbmod = stage_socket.links[0].to_node |
|
idx = next((idx for idx, socket in enumerate(msbmod.find_input_sockets("stage_refs")) if socket.is_linked and socket.links[0].from_node == self)) |
|
return idx |
|
|
|
|
|
class PlasmaBehaviorSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket): |
|
bl_color = (0.348, 0.186, 0.349, 1.0) |
|
|
|
|
|
class PlasmaMultiStageBehaviorNode(PlasmaNodeBase, bpy.types.Node): |
|
bl_category = "AVATAR" |
|
bl_idname = "PlasmaMultiStageBehaviorNode" |
|
bl_label = "Multistage Behavior" |
|
bl_width_default = 200 |
|
|
|
pl_attrib = ("ptAttribBehavior") |
|
|
|
freeze_phys = BoolProperty(name="Freeze Physical", |
|
description="Freeze physical at end", |
|
default=False) |
|
reverse_control = BoolProperty(name="Reverse Controls", |
|
description="Reverse forward/back controls at end", |
|
default=False) |
|
|
|
input_sockets = OrderedDict([ |
|
("seek_target", { |
|
"text": "Seek Target", |
|
"type": "PlasmaSeekTargetSocketIn", |
|
"valid_link_sockets": "PlasmaSeekTargetSocketOut", |
|
}), |
|
("stage_refs", { |
|
"text": "Stage", |
|
"type": "PlasmaAnimStageRefSocket", |
|
"valid_link_nodes": "PlasmaAnimStageNode", |
|
"valid_link_sockets": "PlasmaAnimStageRefSocket", |
|
"link_limit": 1, |
|
"spawn_empty": True, |
|
}), |
|
("condition", { |
|
"text": "Triggered By", |
|
"type": "PlasmaConditionSocket", |
|
"spawn_empty": True, |
|
}), |
|
]) |
|
|
|
output_sockets = OrderedDict([ |
|
("hosts", { |
|
"text": "Host Script", |
|
"type": "PlasmaBehaviorSocket", |
|
"valid_link_nodes": {"PlasmaPythonFileNode"}, |
|
"spawn_empty": True, |
|
}), |
|
("satisfies", { |
|
"text": "Trigger", |
|
"type": "PlasmaConditionSocket", |
|
}) |
|
]) |
|
|
|
def draw_buttons(self, context, layout): |
|
layout.prop(self, "freeze_phys") |
|
layout.prop(self, "reverse_control") |
|
|
|
def get_key(self, exporter, so): |
|
seek_socket = self.find_input_socket("seek_target") |
|
|
|
if seek_socket.is_linked: |
|
seek_target = seek_socket.links[0].from_node.target |
|
if seek_target is not None: |
|
seek_object = exporter.mgr.find_create_object(plSceneObject, bl=seek_target) |
|
else: |
|
self.raise_error("MultiStage Behavior's seek point object is invalid") |
|
else: |
|
seek_object = so |
|
|
|
return self._find_create_key(plMultistageBehMod, exporter, so=seek_object) |
|
|
|
def export(self, exporter, bo, so): |
|
seek_socket = self.find_input_socket("seek_target") |
|
msbmod = self.get_key(exporter, so).object |
|
|
|
msbmod.smartSeek = True if seek_socket.is_linked or seek_socket.auto_target else False |
|
msbmod.freezePhys = self.freeze_phys |
|
msbmod.reverseFBControlsOnRelease = self.reverse_control |
|
|
|
for stage in self.find_inputs("stage_refs"): |
|
animstage = plAnimStage() |
|
animstage.animName = stage.anim_name |
|
if stage.loop_option == "kLoopForever": |
|
animstage.loops = -1 |
|
elif stage.loop_option == "kLoop": |
|
animstage.loops = stage.num_loops |
|
|
|
# Harvest additional AnimStage Settings, if available |
|
settings = stage.find_input("stage_settings") |
|
if settings: |
|
animstage.forwardType = getattr(plAnimStage, settings.forward) |
|
animstage.backType =getattr(plAnimStage, settings.backward) |
|
animstage.advanceType = getattr(plAnimStage, settings.stage_advance) |
|
animstage.regressType = getattr(plAnimStage, settings.stage_regress) |
|
for flag in settings.notify_on: |
|
animstage.notify |= getattr(plAnimStage, flag) |
|
|
|
advance_to = settings.find_input_socket("advance_to") |
|
if advance_to.is_linked: |
|
# Auto-Advance to specific stage |
|
animstage.advanceTo = advance_to.links[0].from_node.stage_id |
|
elif advance_to.auto_advance: |
|
# Auto-Advance |
|
animstage.advanceTo = None |
|
else: |
|
# Don't Auto-Advance, just stop! |
|
animstage.advanceTo = -1 |
|
|
|
regress_to = settings.find_input_socket("regress_to") |
|
if regress_to.is_linked: |
|
# Auto-Regress to specific stage |
|
animstage.regressTo = regress_to.links[0].from_node.stage_id |
|
elif regress_to.auto_regress: |
|
# Auto-Regress |
|
animstage.regressTo = None |
|
else: |
|
# Don't Auto-Regress, just stop! |
|
animstage.regressTo = -1 |
|
|
|
msbmod.addStage(animstage) |
|
|
|
receivers = ((i, i.get_key(exporter, so)) for i in self.find_outputs("satisfies")) |
|
for node, key in receivers: |
|
if key is not None: |
|
msbmod.addReceiver(key) |
|
else: |
|
exporter.report.warn("'{}' Node '{}' doesn't expose a key. It won't be triggered by '{}'!", |
|
node.bl_idname, node.name, self.name, indent=3) |
|
|
|
@property |
|
def requires_actor(self): |
|
return not self.find_input_socket("seek_target").is_linked |
|
|
|
@property |
|
def export_once(self): |
|
return self.find_input_socket("seek_target").is_linked |
|
|
|
def harvest_actors(self): |
|
seek_socket = self.find_input_socket("seek_target") |
|
if seek_socket.is_linked and seek_socket.links[0].from_node.target is not None: |
|
yield seek_socket.links[0].from_node.target.name |
|
|
|
|
|
class PlasmaAnimStageRefSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket): |
|
bl_color = (0.188, 0.186, 0.349, 1.0) |
|
|
|
def draw_content(self, context, layout, node, text): |
|
if isinstance(node, PlasmaMultiStageBehaviorNode): |
|
try: |
|
idx = next((idx for idx, socket in enumerate(node.find_input_sockets("stage_refs")) if socket == self)) |
|
except StopIteration: |
|
layout.label(text) |
|
else: |
|
layout.label("Stage (ID: {})".format(idx)) |
|
else: |
|
layout.label(text) |
|
|
|
|
|
class PlasmaSeekTargetSocketIn(PlasmaNodeSocketBase, bpy.types.NodeSocket): |
|
bl_color = (0.180, 0.350, 0.180, 1.0) |
|
auto_target = BoolProperty(name="Auto Smart Seek", |
|
description="Smart Seek causes the avatar to seek to the provided position before starting the behavior ('auto' will use the current object as the seek point)", |
|
default=False) |
|
|
|
def draw_content(self, context, layout, node, text): |
|
if not self.is_linked: |
|
layout.prop(self, "auto_target") |
|
else: |
|
target = self.links[0].from_node.target |
|
if target: |
|
layout.label("Smart Seek Target: {}".format(target.name)) |
|
else: |
|
layout.label("Smart Seek Target") |
|
|
|
|
|
class PlasmaSeekTargetSocketOut(PlasmaNodeSocketBase, bpy.types.NodeSocket): |
|
bl_color = (0.180, 0.350, 0.180, 1.0) |
|
|
|
|
|
class PlasmaSeekTargetNode(PlasmaNodeBase, bpy.types.Node): |
|
bl_category = "AVATAR" |
|
bl_idname = "PlasmaSeekTargetNode" |
|
bl_label = "Seek Target" |
|
bl_width_default = 200 |
|
|
|
target = PointerProperty(name="Position", |
|
description="Object defining the Seek Point's position", |
|
type=bpy.types.Object) |
|
|
|
output_sockets = OrderedDict([ |
|
("seekers", { |
|
"text": "Seekers", |
|
"type": "PlasmaSeekTargetSocketOut", |
|
"valid_link_nodes": {"PlasmaMultiStageBehaviorNode", "PlasmaOneShotMsgNode"}, |
|
"valid_link_sockets": {"PlasmaSeekTargetSocketIn"}, |
|
}) |
|
]) |
|
|
|
def draw_buttons(self, context, layout): |
|
col = layout.column() |
|
col.prop(self, "target", icon="EMPTY_DATA")
|
|
|