/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include "precompiled.h" #include "ScriptEventDefinition.h" #include "ScriptEventsBus.h" #include namespace ScriptEvents { void ScriptEvent::RegisterInternal() { // Register the bus with the system component ScriptEventBus::Broadcast(&ScriptEventRequests::RegisterScriptEventFromDefinition, *this); } void ScriptEvent::Register(AZ::ScriptDataContext&) { RegisterInternal(); } void ScriptEvent::Release(AZ::ScriptDataContext&) { } void ScriptEvent::Reflect(AZ::ReflectContext* context) { if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) { serializeContext->Class() ->Version(1) ->Field("m_version", &ScriptEvent::m_version) ->Field("m_name", &ScriptEvent::m_name) ->Field("m_category", &ScriptEvent::m_category) ->Field("m_tooltip", &ScriptEvent::m_tooltip) ->Field("m_addressType", &ScriptEvent::m_addressType) ->Field("m_methods", &ScriptEvent::m_methods) ; if (AZ::EditContext* editContext = serializeContext->GetEditContext()) { editContext->Class("Script Event Definition", "Data driven script event definition") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::ChildNameLabelOverride, &ScriptEvent::GetLabel) ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ->DataElement(AZ::Edit::UIHandlers::Default, &ScriptEvent::m_name, "Name", "Name of the Script Event") ->DataElement(AZ::Edit::UIHandlers::Default, &ScriptEvent::m_tooltip, "Tooltip", "The name of this Script Event") ->DataElement(AZ::Edit::UIHandlers::Default, &ScriptEvent::m_category, "Category", "The category that the Event will be put into") ->DataElement(AZ::Edit::UIHandlers::ComboBox, &ScriptEvent::m_addressType, "Address Type", "If required, this defines the address type for this event") ->Attribute(AZ::Edit::Attributes::GenericValueList, &Types::GetValidAddressTypes) ->DataElement(AZ::Edit::UIHandlers::Default, &ScriptEvent::m_methods, "Events", "The list of events available.") ; } } if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) { behaviorContext->Class("ScriptEvent") ->Constructor() ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) ->Method("AddMethod", &ScriptEvent::AddMethod) ->Method("Register", &ScriptEvent::Register) ->Property("Name", BehaviorValueProperty(&ScriptEvent::m_name)) ->Property("AddressType", BehaviorValueProperty(&ScriptEvent::m_addressType)) ->Property("Events", BehaviorValueProperty(&ScriptEvent::m_methods)) ; } } AZ::Outcome ScriptEvent::Validate() const { const AZStd::string& name = GetName(); const AZ::Uuid& addressType = GetAddressType(); AZ::BehaviorContext* behaviorContext = nullptr; AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationBus::Events::GetBehaviorContext); AZ_Assert(behaviorContext, "A valid Behavior Context is expected"); if (m_version == 0 && behaviorContext->m_ebuses.find(name.c_str()) != behaviorContext->m_ebuses.end()) { // An EBus with the same name already is registered, this is not allowed. return AZ::Failure(AZStd::string::format("A Script Event with the name \"%s\" already exist, consider renaming this Script Event as duplicate names are not supported", name.c_str())); } // Validate address type if (!Types::ValidateAddressType(addressType)) { return AZ::Failure(AZStd::string::format("The specified type %s is not valid as an address for Script Events: %s", addressType.ToString().c_str(), name.c_str())); } // Definition name cannot be empty if (name.empty()) { return AZ::Failure(AZStd::string("Event name cannot be empty")); } // Name cannot start with a number if (isdigit(name.at(0))) { return AZ::Failure(AZStd::string::format("%s, names cannot start with a number", name.c_str())); } AZStd::smatch match; // Ascii-only AZStd::regex asciionly_regex("[^\x0A\x0D\x20-\x7E]"); AZStd::regex_match(name, match, asciionly_regex); if (!match.empty()) { return AZ::Failure(AZStd::string::format("%s, invalid name, names may only contain ASCII characters", name.c_str())); } // No whitespace AZStd::regex nowhitespace_regex("[^\\S]"); AZStd::regex_match(name, match, nowhitespace_regex); if (!match.empty()) { return AZ::Failure(AZStd::string::format("%s, invalid name, event names should not contain white space", name.c_str())); } // Conform to valid function names AZStd::regex validate_regex("[_[:alpha:]][_[:alnum:]]*"); AZStd::regex_match(name, match, validate_regex); if (match.empty()) { return AZ::Failure(AZStd::string::format("%s, invalid name specified, event name must only have alpha numeric characters, may not start with a number and may not have white space", name.c_str())); } if (m_methods.empty()) { return AZ::Failure(AZStd::string::format("Script Events (%s) must provide at least one event otherwise they are unusable, be sure to add an event before saving.", name.c_str())); } // Validate each method AZStd::string methodName; int methodIndex = 0; for (const Method& method : m_methods) { auto outcome = method.Validate(); if (!outcome.IsSuccess()) { return outcome; } if (method.GetName().compare(methodName) == 0) { return AZ::Failure(AZStd::string::format("Cannot have duplicate method names (%d: %s) make sure each method name is unique", methodIndex, methodName.c_str())); } methodName = method.GetName(); ++methodIndex; } return AZ::Success(true); } }