diff --git a/scripts/Callback/Events/EntityBuilderEvent.reds b/scripts/Callback/Events/EntityBuilderEvent.reds new file mode 100644 index 00000000..53e30275 --- /dev/null +++ b/scripts/Callback/Events/EntityBuilderEvent.reds @@ -0,0 +1,3 @@ +public native class EntityBuilderEvent extends CallbackSystemEvent { + public native func GetEntityBuilder() -> ref +} diff --git a/scripts/Callback/Targets/EntityTarget.reds b/scripts/Callback/Targets/EntityTarget.reds index 0ed76545..0150d959 100644 --- a/scripts/Callback/Targets/EntityTarget.reds +++ b/scripts/Callback/Targets/EntityTarget.reds @@ -4,4 +4,5 @@ public native class EntityTarget extends CallbackSystemTarget { public static native func RecordID(recordID: TweakDBID) -> ref public static native func Template(templatePath: ResRef) -> ref public static native func Appearance(appearanceName: CName) -> ref + public static native func Definition(appearancePath: ResRef, opt definitionName: CName) -> ref } diff --git a/scripts/Entity/EntityBuilderWrapper.reds b/scripts/Entity/EntityBuilderWrapper.reds new file mode 100644 index 00000000..f4cb2a4f --- /dev/null +++ b/scripts/Entity/EntityBuilderWrapper.reds @@ -0,0 +1,29 @@ +public native class EntityBuilderWrapper { + public native func HasEntity() -> Bool + public native func HasAppearance() -> Bool + public native func HasCustomAppearances() -> Bool + public native func GetRecordID() -> TweakDBID + public native func GetTemplatePath() -> ResRef + public native func GetAppearanceName() -> CName + public native func GetEntityID() -> EntityID + public native func GetEntityType() -> CName + public native func GetEntityParams() -> ref + public native func GetTemplate() -> ref + public native func GetAppearance() -> ref + public native func GetCustomAppearances() -> array> +} + +public native class EntityBuilderTemplateWrapper { + public native func GetResource() -> ref + public native func GetAppearanceName() -> CName + public native func GetEntity() -> ref + public native func GetComponents() -> array> + public native func AddComponent(component: ref) +} + +public native class EntityBuilderAppearanceWrapper { + public native func GetResource() -> ref + public native func GetDefinition() -> ref + public native func GetComponents() -> array> + public native func AddComponent(component: ref) +} diff --git a/src/App/Callback/CallbackSystem.cpp b/src/App/Callback/CallbackSystem.cpp index b6d76cfc..1ec59063 100644 --- a/src/App/Callback/CallbackSystem.cpp +++ b/src/App/Callback/CallbackSystem.cpp @@ -2,6 +2,7 @@ #include "App/Callback/Controllers/EntityAssembleHook.hpp" #include "App/Callback/Controllers/EntityAttachHook.hpp" #include "App/Callback/Controllers/EntityDetachHook.hpp" +#include "App/Callback/Controllers/EntityExtractHook.hpp" #include "App/Callback/Controllers/EntityRequestComponentsHook.hpp" #include "App/Callback/Controllers/EntityUninitializeHook.hpp" #include "App/Callback/Controllers/PlayerSpawnedHook.hpp" @@ -17,6 +18,7 @@ App::CallbackSystem::CallbackSystem() : m_restored(false) , m_pregame(false) { + RegisterController(); RegisterController(); RegisterController(); RegisterController(); diff --git a/src/App/Callback/Controllers/EntityExtractHook.hpp b/src/App/Callback/Controllers/EntityExtractHook.hpp new file mode 100644 index 00000000..f41fa5fb --- /dev/null +++ b/src/App/Callback/Controllers/EntityExtractHook.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "App/Callback/CallbackSystem.hpp" +#include "App/Callback/CallbackSystemController.hpp" +#include "App/Callback/Events/EntityBuilderEvent.hpp" +#include "Core/Hooking/HookingAgent.hpp" +#include "Red/EntityBuilder.hpp" + +namespace App +{ +class EntityExtractHook + : public CallbackSystemController + , public Core::HookingAgent +{ +public: + constexpr static auto EventName = Red::CName("Entity/Extract"); + + Core::Map GetEvents() override + { + return {{EventName, Red::GetTypeName()}}; + } + +protected: + bool OnActivateHook() override + { + return IsHooked() + || HookBefore(&OnExtractComponentsJob); + } + + bool OnDeactivateHook() override + { + return !IsHooked() + || Unhook(); + } + + inline static void OnExtractComponentsJob(Red::EntityBuilderJobParams* aParams, void*) + { + if (!aParams->entityBuilderWeak.Expired()) + { + CallbackSystem::Get()->DispatchNativeEvent(EventName, aParams->entityBuilderWeak); + } + } +}; +} diff --git a/src/App/Callback/Events/EntityBuilderEvent.hpp b/src/App/Callback/Events/EntityBuilderEvent.hpp new file mode 100644 index 00000000..99bbb14c --- /dev/null +++ b/src/App/Callback/Events/EntityBuilderEvent.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "App/Callback/CallbackSystemEvent.hpp" +#include "App/Entity/EntityBuilderWrapper.hpp" + +namespace App +{ +struct EntityBuilderEvent : CallbackSystemEvent +{ + EntityBuilderEvent() = default; + + EntityBuilderEvent(Red::CName aName, const Red::WeakPtr& aEntityBuilder) + : CallbackSystemEvent(aName) + , entityBuilder(Red::MakeHandle(aEntityBuilder)) + { + } + + Red::Handle entityBuilder; + + RTTI_IMPL_TYPEINFO(App::EntityBuilderEvent); + RTTI_IMPL_ALLOCATOR(); +}; +} + +RTTI_DEFINE_CLASS(App::EntityBuilderEvent, { + RTTI_PARENT(App::CallbackSystemEvent); + RTTI_GETTER(entityBuilder); +}); diff --git a/src/App/Callback/Targets/EntityTarget.hpp b/src/App/Callback/Targets/EntityTarget.hpp index 4b8ab9da..9e3e0ff7 100644 --- a/src/App/Callback/Targets/EntityTarget.hpp +++ b/src/App/Callback/Targets/EntityTarget.hpp @@ -1,7 +1,9 @@ #pragma once #include "App/Callback/CallbackSystemTarget.hpp" +#include "App/Callback/Events/EntityBuilderEvent.hpp" #include "App/Callback/Events/EntityLifecycleEvent.hpp" +#include "App/Callback/Events/VehicleLightControlEvent.hpp" namespace App { @@ -11,30 +13,109 @@ struct EntityTarget : CallbackSystemTarget bool Matches(const Red::Handle& aEvent) override { - auto* entity = aEvent.GetPtr()->entity.instance; + switch (aEvent->GetType()->GetName()) + { + case Red::GetTypeName(): + { + auto* builder = aEvent.GetPtr()->entityBuilder->builder.instance; + + if (entityID && builder->request && entityID != builder->request->entityID) + return false; + + if (entityType) + { + if (!builder->entityExtractor) + return false; + + auto rootIndex = builder->entityTemplate->compiledDataHeader.rootIndex; + + if (rootIndex < 0) + return false; - if (entityID && entityID != entity->entityID) - return false; + if (!builder->entityExtractor->results[rootIndex]->GetType()->IsA(entityType)) + return false; + } - if (entityType && !entity->GetType()->IsA(entityType)) - return false; + if (templatePath && templatePath != builder->entityTemplate->path) + return false; - if (templatePath && templatePath != entity->templatePath) - return false; + if (appearanceName && builder->request && appearanceName != builder->request->appearanceName) + return false; - if (appearanceName && appearanceName != entity->appearanceName) - return false; + if (recordID && builder->request && recordID != builder->request->recordID) + return false; - if (recordID) + if (appearancePath) + { + if (builder->appearance.resource) + { + if (appearancePath != builder->appearance.resource->path) + return false; + + if (definitionName && definitionName != builder->appearance.definition->name) + return false; + } + else if (builder->appearances.size != 0) + { + auto match = false; + + for (const auto& appearance : builder->appearances) + { + if (appearancePath != appearance.resource->path) + continue; + + if (definitionName && definitionName != appearance.definition->name) + continue; + + match = true; + break; + } + + if (!match) + return false; + } + else + { + return false; + } + } + + break; + } + case Red::GetTypeName(): + case Red::GetTypeName(): { - auto gameObject = Red::Cast(entity); - if (!gameObject) + auto* entity = aEvent.GetPtr()->entity.instance; + + if (appearancePath || definitionName) + return false; + + if (entityID && entityID != entity->entityID) return false; - Red::TweakDBID objectID; - Red::CallGlobal("gameObject::GetTDBID;GameObject", objectID, Red::AsWeakHandle(gameObject)); - if (recordID != objectID) + if (entityType && !entity->GetType()->IsA(entityType)) return false; + + if (templatePath && templatePath != entity->templatePath) + return false; + + if (appearanceName && appearanceName != entity->appearanceName) + return false; + + if (recordID) + { + auto gameObject = Red::Cast(entity); + if (!gameObject) + return false; + + Red::TweakDBID objectID; + Red::CallGlobal("gameObject::GetTDBID;GameObject", objectID, Red::AsWeakHandle(gameObject)); + if (recordID != objectID) + return false; + } + + break; + } } return true; @@ -50,7 +131,9 @@ struct EntityTarget : CallbackSystemTarget bool Supports(Red::CName aEventType) override { - return Red::GetClass(aEventType)->IsA(Red::GetClass()); + return aEventType == Red::GetTypeName() + || aEventType == Red::GetTypeName() + || aEventType == Red::GetTypeName(); } static Red::Handle ID(Red::EntityID aEntityID) @@ -93,11 +176,22 @@ struct EntityTarget : CallbackSystemTarget return target; } + static Red::Handle Definition(const Red::RaRef<>& aResource, Red::Optional aName) + { + auto target = Red::MakeHandle(); + target->appearancePath = aResource.path; + target->definitionName = aName; + + return target; + } + Red::EntityID entityID{}; Red::CClass* entityType{}; Red::TweakDBID recordID{}; Red::ResourcePath templatePath{}; Red::CName appearanceName{}; + Red::ResourcePath appearancePath{}; + Red::CName definitionName{}; RTTI_IMPL_TYPEINFO(App::EntityTarget); RTTI_IMPL_ALLOCATOR(); @@ -111,4 +205,5 @@ RTTI_DEFINE_CLASS(App::EntityTarget, { RTTI_METHOD(RecordID); RTTI_METHOD(Template); RTTI_METHOD(Appearance); + RTTI_METHOD(Definition); }); diff --git a/src/App/Entity/EntityBuilderWrapper.hpp b/src/App/Entity/EntityBuilderWrapper.hpp new file mode 100644 index 00000000..bcf0bb2c --- /dev/null +++ b/src/App/Entity/EntityBuilderWrapper.hpp @@ -0,0 +1,263 @@ +#pragma once + +#include "Red/EntityBuilder.hpp" + +namespace App +{ +struct EntityBuilderTemplateWrapper : Red::IScriptable +{ + EntityBuilderTemplateWrapper() = default; + + EntityBuilderTemplateWrapper(Red::WeakPtr aBuilder) + : builder(std::move(aBuilder)) + { + } + + [[nodiscard]] Red::Handle GetResource() const + { + return builder.instance->entityTemplate; + } + + [[nodiscard]] Red::CName GetAppearanceName() const + { + if (!builder.instance->request) + return {}; + + return builder.instance->request->appearanceName; + } + + [[nodiscard]] Red::Handle GetEntity() const + { + if (!builder.instance->entityExtractor || builder.instance->entityExtractor->results.size == 0) + return {}; + + auto rootIndex = builder.instance->entityTemplate->compiledDataHeader.rootIndex; + + if (rootIndex < 0) + return {}; + + return Red::Cast(builder.instance->entityExtractor->results[rootIndex]); + } + + [[nodiscard]] Red::DynArray> GetComponents() const + { + if (!builder.instance->entityExtractor || builder.instance->entityExtractor->results.size == 0) + return {}; + + Red::DynArray> components; + + auto rootIndex = builder.instance->entityTemplate->compiledDataHeader.rootIndex; + for (auto i = rootIndex + 1; i < builder.instance->entityExtractor->results.size; ++i) + { + components.PushBack(Red::Cast(builder.instance->entityExtractor->results[i])); + } + + return components; + } + + void AddComponent(const Red::Handle& aComponent) const + { + if (!builder.instance->entityExtractor) + return; + + builder.instance->entityExtractor->results.PushBack(aComponent); + } + + Red::WeakPtr builder; + + RTTI_IMPL_TYPEINFO(App::EntityBuilderTemplateWrapper); + RTTI_IMPL_ALLOCATOR(); +}; + +struct EntityBuilderAppearanceWrapper : Red::IScriptable +{ + EntityBuilderAppearanceWrapper() = default; + + EntityBuilderAppearanceWrapper(Red::WeakPtr aBuilder, Red::EntityBuilderAppearance* aAppearance) + : builder(std::move(aBuilder)) + , appearance(aAppearance) + { + } + + [[nodiscard]] Red::Handle GetResource() const + { + if (!appearance) + return {}; + + return appearance->resource; + } + + [[nodiscard]] Red::Handle GetDefinition() const + { + if (!appearance) + return {}; + + return appearance->definition; + } + + [[nodiscard]] Red::DynArray> GetComponents() const + { + if (!appearance) + return {}; + + Red::DynArray> components; + + for (auto i = 0; i < appearance->extractor->results.size; ++i) + { + components.PushBack(Red::Cast(appearance->extractor->results[i])); + } + + return components; + } + + void AddComponent(const Red::Handle& aComponent) const + { + if (!appearance) + return; + + appearance->extractor->results.PushBack(aComponent); + } + + Red::WeakPtr builder; + Red::EntityBuilderAppearance* appearance{nullptr}; + + RTTI_IMPL_TYPEINFO(App::EntityBuilderAppearanceWrapper); + RTTI_IMPL_ALLOCATOR(); +}; + +struct EntityBuilderWrapper : Red::IScriptable +{ + EntityBuilderWrapper() = default; + + EntityBuilderWrapper(Red::WeakPtr aBuilder) + : builder(std::move(aBuilder)) + { + } + + [[nodiscard]] Red::TweakDBID GetRecordID() const + { + if (!builder.instance->request) + return {}; + + return builder.instance->request->recordID; + } + + [[nodiscard]] Red::ResourceAsyncReference<> GetTemplatePath() const + { + return builder.instance->entityTemplate->path; + } + + [[nodiscard]] Red::CName GetAppearanceName() const + { + if (!builder.instance->request) + return {}; + + return builder.instance->request->appearanceName; + } + + [[nodiscard]] Red::EntityID GetEntityID() const + { + if (!builder.instance->request) + return {}; + + return builder.instance->request->entityID; + } + + [[nodiscard]] Red::CName GetEntityType() const + { + if (!builder.instance->entityExtractor || builder.instance->entityExtractor->results.size == 0) + return {}; + + auto rootIndex = builder.instance->entityTemplate->compiledDataHeader.rootIndex; + + if (rootIndex < 0) + return {}; + + return builder.instance->entityExtractor->results[rootIndex]->GetType()->GetName(); + } + + [[nodiscard]] Red::Handle GetEntityParams() const + { + if (!builder.instance->request) + return {}; + + return builder.instance->request->entityParams; + } + + [[nodiscard]] Red::Handle GetTemplate() const + { + return Red::MakeHandle(builder); + } + + [[nodiscard]] Red::Handle GetAppearance() const + { + if (!builder.instance->appearance.resource) + return {}; + + return Red::MakeHandle(builder, &builder.instance->appearance); + } + + [[nodiscard]] Red::DynArray> GetCustomAppearances() const + { + Red::DynArray> appearances; + + for (auto i = 0; i < builder.instance->appearances.size; ++i) + { + appearances.PushBack(Red::MakeHandle(builder, + &builder.instance->appearances[i])); + } + + return appearances; + } + + [[nodiscard]] bool HasEntity() const + { + return builder.instance->entityExtractor; + } + + [[nodiscard]] bool HasAppearance() const + { + return builder.instance->appearance.resource; + } + + [[nodiscard]] bool HasCustomAppearances() const + { + return builder.instance->appearances.size != 0; + } + + Red::WeakPtr builder; + + RTTI_IMPL_TYPEINFO(App::EntityBuilderWrapper); + RTTI_IMPL_ALLOCATOR(); +}; +} + +RTTI_DEFINE_CLASS(App::EntityBuilderWrapper, { + RTTI_METHOD(GetRecordID); + RTTI_METHOD(GetTemplatePath); + RTTI_METHOD(GetAppearanceName); + RTTI_METHOD(GetEntityID); + RTTI_METHOD(GetEntityType); + RTTI_METHOD(GetEntityParams); + RTTI_METHOD(GetTemplate); + RTTI_METHOD(GetAppearance); + RTTI_METHOD(GetCustomAppearances); + RTTI_METHOD(HasEntity); + RTTI_METHOD(HasAppearance); + RTTI_METHOD(HasCustomAppearances); +}); + +RTTI_DEFINE_CLASS(App::EntityBuilderTemplateWrapper, { + RTTI_METHOD(GetResource); + RTTI_METHOD(GetAppearanceName); + RTTI_METHOD(GetEntity); + RTTI_METHOD(GetComponents); + RTTI_METHOD(AddComponent); +}); + +RTTI_DEFINE_CLASS(App::EntityBuilderAppearanceWrapper, { + RTTI_METHOD(GetResource); + RTTI_METHOD(GetDefinition); + RTTI_METHOD(GetComponents); + RTTI_METHOD(AddComponent); +}); diff --git a/src/Red/Addresses/Library.hpp b/src/Red/Addresses/Library.hpp index 8cbce0c0..acf628a3 100644 --- a/src/Red/Addresses/Library.hpp +++ b/src/Red/Addresses/Library.hpp @@ -26,6 +26,9 @@ constexpr uint32_t Entity_Reassemble = 1560690857; constexpr uint32_t Entity_Uninitialize = 3596356635; constexpr uint32_t Entity_RequestComponents = 2296260874; +constexpr uint32_t EntityBuilder_ExtractComponentsJob = 489494088; +constexpr uint32_t EntityBuilder_ScheduleExtractComponentsJob = 437791594; + constexpr uint32_t EntitySpawner_SpawnEntity = 3068279668; constexpr uint32_t EntitySpawnerToken_dtor = 552341017; diff --git a/src/Red/EntityBuilder.hpp b/src/Red/EntityBuilder.hpp new file mode 100644 index 00000000..1a4ac689 --- /dev/null +++ b/src/Red/EntityBuilder.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include "Red/Entity.hpp" + +namespace Red +{ +using ComponentPredicate = FlexCallback; + +struct EntityBuilderRequest : EntityInitializeRequest +{ + using AllocatorType = Memory::EngineAllocator; + + uint64_t unkD0; // D0 + WeakHandle instanceData; // D8 + ComponentPredicate componentPredicate; // E8 - Called during entity assemble to select components + uint64_t unk118; // 118 +}; +RED4EXT_ASSERT_SIZE(EntityBuilderRequest, 0x120); + +struct EntityBuilderAppearance +{ + Handle definition; // 00 + Handle resource; // 10 + SharedPtr extractor; // 20 +}; +RED4EXT_ASSERT_SIZE(EntityBuilderAppearance, 0x30); + +struct EntityBuilder +{ + using AllocatorType = Memory::EntityResourceAllocator; + + struct Flags + { + uint8_t ExtractEntity : 1; // 00 + uint8_t unk01 : 1; // 01 + uint8_t ExtractAppearance : 1; // 02 + uint8_t ExtractAppearances : 1; // 03 + uint8_t InitializeVisualComponents : 1; // 04 + }; + RED4EXT_ASSERT_SIZE(Flags, 0x1); + + WeakPtr self; // 00 + SharedPtr entityExtractor; // 10 + EntityBuilderAppearance appearance; // 20 + uint8_t unk50[0x30]; // 50 + DynArray appearances; // 80 + Handle entityTemplate; // 90 + SharedPtr request; // A0 + Handle entity; // B0 + DynArray> components; // C0 + DynArray unkD0; // D0 + DynArray unkE0; // E0 + SharedSpinLock lock; // F0 + redTagList visualTags; // F8 + uint64_t unk108; // 108 + Flags flags; // 110 +}; +RED4EXT_ASSERT_SIZE(EntityBuilder, 0x118); +RED4EXT_ASSERT_OFFSET(EntityBuilder, appearance, 0x20); +RED4EXT_ASSERT_OFFSET(EntityBuilder, appearances, 0x80); +RED4EXT_ASSERT_OFFSET(EntityBuilder, entityTemplate, 0x90); +RED4EXT_ASSERT_OFFSET(EntityBuilder, request, 0xA0); +RED4EXT_ASSERT_OFFSET(EntityBuilder, components, 0xC0); +RED4EXT_ASSERT_OFFSET(EntityBuilder, visualTags, 0xF8); +RED4EXT_ASSERT_OFFSET(EntityBuilder, flags, 0x110); + +struct EntityBuilderJobParams +{ + EntityBuilder* entityBuilder; + WeakPtr entityBuilderWeak; +}; +} + +namespace Raw::EntityBuilder +{ +constexpr auto ExtractComponentsJob = Core::RawFunc< + /* addr = */ Red::AddressLib::EntityBuilder_ExtractComponentsJob, + /* type = */ void (*)(Red::EntityBuilderJobParams* aParams, void* a2)>(); + +constexpr auto ScheduleExtractComponentsJob = Core::RawFunc< + /* addr = */ Red::AddressLib::EntityBuilder_ScheduleExtractComponentsJob, + /* type = */ void (*)(Red::JobQueue& aJobQueue, void* a2, Red::EntityBuilderJobParams* aParams)>(); +} diff --git a/src/pch.hpp b/src/pch.hpp index 1c64464b..9a55b6df 100644 --- a/src/pch.hpp +++ b/src/pch.hpp @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -38,12 +39,16 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include +#include #include #include #include @@ -52,15 +57,23 @@ #include #include #include +#include +#include +#include +#include +#include #include #include #include +#include +#include #include #include #include #include #include #include +#include #include #include #include diff --git a/src/rtti.cpp b/src/rtti.cpp index ec2b36b0..fbb54252 100644 --- a/src/rtti.cpp +++ b/src/rtti.cpp @@ -2,6 +2,7 @@ #include "App/Callback/CallbackSystemEvent.hpp" #include "App/Callback/CallbackSystemHandler.hpp" #include "App/Callback/CallbackSystemTarget.hpp" +#include "App/Callback/Events/EntityBuilderEvent.hpp" #include "App/Callback/Events/EntityLifecycleEvent.hpp" #include "App/Callback/Events/GameSessionEvent.hpp" #include "App/Callback/Events/KeyInputEvent.hpp" @@ -20,6 +21,7 @@ #include "App/Depot/ResourceToken.hpp" #include "App/Device/ResetSecuritySystemNetwork.hpp" #include "App/Entity/ComponentEx.hpp" +#include "App/Entity/EntityBuilderWrapper.hpp" #include "App/Entity/EntityEx.hpp" #include "App/Entity/EntityIDEx.hpp" #include "App/Entity/GameObjectEx.hpp" diff --git a/vendor/RED4ext.SDK b/vendor/RED4ext.SDK index 1171496a..dd8bee62 160000 --- a/vendor/RED4ext.SDK +++ b/vendor/RED4ext.SDK @@ -1 +1 @@ -Subproject commit 1171496a09dd409f62011a2d52da81627565e29f +Subproject commit dd8bee623fdf26463f7f378328e719fec9f7dc3a diff --git a/wiki/Home.md b/wiki/Home.md index 140faabc..67776209 100644 --- a/wiki/Home.md +++ b/wiki/Home.md @@ -78,7 +78,7 @@ It's safe to change persistent data structure at any time. Callback system allows you to listen to various game events and in some cases alter game behavior and/or modify related game objects: -| Event Name | Event Object Type | Description | +| Event Name | Event Type | Description | |:----------------------|:-----------------------|:---------------------------------------------------------------------------------------------------| | `Resource/Loaded` | `ResourceEvent` | Fired when resource and its dependencies have been loaded, but no post-processing is done yet. | | `Resource/Ready` | `ResourceEvent` | Fired when all post-processing is complete and resource is ready to be passed to the requester. | @@ -91,8 +91,9 @@ and in some cases alter game behavior and/or modify related game objects: | `Session/AfterSave` | `GameSessionEvent` | Fired right after saving. | | `Session/Pause` | `GameSessionEvent` | Fired when game is paused. | | `Session/Resume` | `GameSessionEvent` | Fired when game is resumed. | -| `Entity/Assemble` | `EntityLifecycleEvent` | The earliest phase of entity creation process. Entity ID is not assigned yet. | -| `Entity/Initialize` | `EntityLifecycleEvent` | Fired when entity is assembled and ready for use. Entity ID is assigned at this stage. | +| `Entity/Extract` | `EntityBuilderEvent` | Fired when all entity parts are extracted from `.ent` and `.app` to assemble the entity object. | +| `Entity/Assemble` | `EntityLifecycleEvent` | Fired when entity is assembled from extracted parts. Entity ID is not assigned yet. | +| `Entity/Initialize` | `EntityLifecycleEvent` | Fired when entity and its components are intializing. Entity ID is assigned at this stage. | | `Entity/Reassemble` | `EntityLifecycleEvent` | Fired when components dynamically added or removed from entity. For example, when you equip items. | | `Entity/Attach` | `EntityLifecycleEvent` | Fired when entity is added to the world. Can be fired multiple times during entity lifetime. | | `Entity/Attached` | `EntityLifecycleEvent` | Fired when attachment process is finished for entity. | @@ -101,22 +102,24 @@ and in some cases alter game behavior and/or modify related game objects: | `Input/Key` | `KeyInputEvent` | Catches keyboard, mouse and controller button inputs. | | `Input/Axis` | `AxisInputEvent` | Catches mouse movements and controller axis inputs. | -When defining a callback, you can specify the event target only for which the callback should be fired: - -| Target Definition | Compatible Event | Description | -|:-------------------------------------------|:-----------------------|:------------------------------------------------------------| -| `ResourceTarget.Path(ResRef)` | `ResourceEvent` | Selects resource by path. | -| `ResourceTarget.Type(CName)` | `ResourceEvent` | Selects resource by type name, e.g. `n"entEntityTemplate"`. | -| `EntityTarget.ID(EntityID)` | `EntityLifecycleEvent` | Selects entity by entity ID. | -| `EntityTarget.Type(CName)` | `EntityLifecycleEvent` | Selects entity by type name, e.g. `n"PlayerPuppet"`. | -| `EntityTarget.RecordID(TweakDBID)` | `EntityLifecycleEvent` | Selects entity by record ID, e.g. `t"Character.Panam"`. | -| `EntityTarget.Template(ResRef)` | `EntityLifecycleEvent` | Selects entity by template path. | -| `EntityTarget.Appearance(CName)` | `EntityLifecycleEvent` | Selects entity by appearance name. | -| `DynamicEntityTarget.Tag(CName)` | `EntityLifecycleEvent` | Selects entity created using dynamic entity system by tag. | -| `StaticEntityTarget.Tag(CName)` | `EntityLifecycleEvent` | Selects entity spawned using static entity system by tag. | -| `InputTarget.Key(EInputKey)` | `KeyInputEvent` | Selects input event by key. | -| `InputTarget.Key(EInputKey, EInputAction)` | `KeyInputEvent` | Selects input event by key in combination with action. | -| `InputTarget.Axis(EInputKey)` | `AxisInputEvent` | Selects input event by axis. | +When defining a callback, you can specify event targets for which the callback should be fired: + +| Target Definition | Compatible Event Types | Description | +|:-------------------------------------------|:---------------------------------------------|:------------------------------------------------------------| +| `ResourceTarget.Path(ResRef)` | `ResourceEvent` | Selects resource by path. | +| `ResourceTarget.Type(CName)` | `ResourceEvent` | Selects resource by type name, e.g. `n"entEntityTemplate"`. | +| `EntityTarget.ID(EntityID)` | `EntityBuilderEvent`, `EntityLifecycleEvent` | Selects entity by entity ID. | +| `EntityTarget.Type(CName)` | `EntityBuilderEvent`, `EntityLifecycleEvent` | Selects entity by type name, e.g. `n"PlayerPuppet"`. | +| `EntityTarget.RecordID(TweakDBID)` | `EntityBuilderEvent`, `EntityLifecycleEvent` | Selects entity by record ID, e.g. `t"Character.Panam"`. | +| `EntityTarget.Template(ResRef)` | `EntityBuilderEvent`, `EntityLifecycleEvent` | Selects entity by template path. | +| `EntityTarget.Appearance(CName)` | `EntityBuilderEvent`, `EntityLifecycleEvent` | Selects entity by appearance name. | +| `EntityTarget.Definition(ResRef)` | `EntityBuilderEvent` | Selects entity by appearance resource. | +| `EntityTarget.Definition(ResRef, CName)` | `EntityBuilderEvent` | Selects entity by appearance resource and definition name. | +| `DynamicEntityTarget.Tag(CName)` | `EntityLifecycleEvent` | Selects entity created using dynamic entity system by tag. | +| `StaticEntityTarget.Tag(CName)` | `EntityLifecycleEvent` | Selects entity created using static entity system by tag. | +| `InputTarget.Key(EInputKey)` | `KeyInputEvent` | Selects input event by key. | +| `InputTarget.Key(EInputKey, EInputAction)` | `KeyInputEvent` | Selects input event by key in combination with action. | +| `InputTarget.Axis(EInputKey)` | `AxisInputEvent` | Selects input event by axis. | This example injects custom menu scenario when `pregame_menu.inkmenu` is accessed by the game: diff --git a/xmake.lua b/xmake.lua index 7a584451..a0567a7c 100644 --- a/xmake.lua +++ b/xmake.lua @@ -1,7 +1,7 @@ set_xmakever("2.5.9") set_project("Codeware") -set_version("1.12.9", {build = "%y%m%d%H%M"}) +set_version("1.13.0", {build = "%y%m%d%H%M"}) set_arch("x64") set_languages("cxx2a")