Mod SDK: Actor service (#2270)

* Mod SDK: Actor Service

* Modding doc fix

* Update naming for actor service. Begin custom actor example mod

* Example Mod Updates, On unregister: Only delete requested actors of the right type. Update docs

* Cleanup & fixes

* Rename to custom_actor_demo

* tweaks

* Finish mine demo actor

---------

Co-authored-by: MelonSpeedruns <melonspeedruns@stratobox.net>
Co-authored-by: Luke Street <luke@street.dev>
This commit is contained in:
jdflyer
2026-09-03 10:02:15 -06:00
committed by GitHub
parent fb4142c7ef
commit 5a92780d42
21 changed files with 1271 additions and 4 deletions
+1
View File
@@ -571,6 +571,7 @@ if (DUSK_ENABLE_CODE_MODS AND CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR
add_subdirectory(mods/shadow_mod)
add_subdirectory(mods/window_demo)
add_subdirectory(mods/flow_demo)
add_subdirectory(mods/custom_actor_demo)
add_subdirectory(mods/cosmetics)
add_subdirectory(mods/randomizer)
endif ()
+76
View File
@@ -1136,6 +1136,82 @@ svc_game_mode->register_game_mode(mod_ctx, &gameModeDesc);
```
### ActorService (`mods/svc/actor.h`)
A service that manages registering and creating custom actors. These actors will be run by the game as if they are part of the engine. These actors can be created by the game either by its 16-bit actor name, or a 7-character long name that can
be loaded by a stage.
```cpp
#include "mods/svc/actor.h"
IMPORT_SERVICE(ActorService, svc_actor);
class myActor_c : public fopAc_ac_c {};
int myActor_Create(void* i_this) {
// Ran several times, until it returns cPhs_COMPLEATE_e to allow for async loading
return cPhs_COMPLEATE_e;
}
int myActor_Delete(void* i_this) {
// Free resources here
return 1;
}
int myActor_Execute(void* i_this) {
// Ran once per game tick
return 1;
}
int myActor_IsDelete(void* i_this) {
// Returns 1 when the actor can be deleted
return 1;
}
int myActor_Draw(void* i_this) {
// Code to draw the actor
return 1;
}
s16 actor_name; // The process name that can be used by the game to load the actor
ActorHandle actor_handle;
ActorProfileDesc profDesc = {
.name = "AUnique", // The name used by the stage loader to load the actor with.
// It has a character limit of 7 and must be unique among active
// mod actors. Matching a game actor name overrides stage lookup.
.priority_group = 7, // When, relative to other actors _Execute should run
// See: mods/svc/actor.h
.process_size = sizeof(myActor_c),
.draw_priority = fpcDwPi_OBJ_LBOX_e, // Defines when the actor should be drawn relative
// to other actors (see f_pc_draw_priority.h)
.status = fopAcStts_CULL_e | fopAcStts_UNK_0x4000_e | fopAcStts_UNK_0x40000_e,
.group = fopAc_ACTOR_e, // Can be fopAc_ACTOR_e, fopAc_PLAYER_e, fopAc_ENEMY_e, or fopAc_NPC_e
.cull_type = fopAc_CULLBOX_CUSTOM_e,
.create_function = myActor_Create,
.delete_function = myActor_Delete,
.execute_function = myActor_Execute,
.is_delete_function = myActor_IsDelete,
.draw_function = myActor_Draw,
};
svc_actor->register_actor(mod_ctx, &profDesc, &actor_name, &actor_handle);
// Spawn the actor at the player's position
fopAc_ac_c* plr = dComIfGp_getPlayer(0);
if (plr) {
ActorSpawnParams spawnParams = {
.parameters = 0,
.argument = 0,
.room_num = fopAcM_GetRoomNo(plr),
.position = {plr->current.pos.x, plr->current.pos.y, plr->current.pos.z},
.angle = {plr->current.angle.x, plr->current.angle.y, plr->current.angle.z},
.scale = {1.0f, 1.0f, 1.0f}
};
ActorId created_actor_id;
svc_actor->create_actor(mod_ctx, actor_name, &spawnParams, &created_actor_id);
}
```
See `mods/custom_actor_demo` for a more complete example.
---
## Hooking Game Functions
+1
View File
@@ -1490,6 +1490,7 @@ set(DUSK_FILES
src/dusk/mods/log_buffer.hpp
src/dusk/mods/manifest.cpp
src/dusk/mods/manifest.hpp
src/dusk/mods/svc/actor.cpp
src/dusk/mods/svc/camera.cpp
src/dusk/mods/svc/config.cpp
src/dusk/mods/svc/config.hpp
+1 -3
View File
@@ -23,12 +23,10 @@
#endif
#define fopAcM_ct(ptr, ClassName) \
if ((ptr)->layer_tag.layer == NULL) { OSPanic(__FILE__, __LINE__, "UH OH"); } \
if (!fopAcM_CheckCondition(ptr, fopAcCnd_INIT_e)) { \
fopAcM_ct_placement(ptr, ClassName); \
fopAcM_OnCondition(ptr, fopAcCnd_INIT_e); \
} \
if ((ptr)->layer_tag.layer == NULL) { OSPanic(__FILE__, __LINE__, "Oh come on"); }
}
#define fopAcM_RegisterDeleteID(i_this, actor_name_str) \
+1
View File
@@ -7,6 +7,7 @@
typedef struct base_process_class base_process_class;
BOOL fpcDt_IsComplete();
int fpcDt_deleteMethod(base_process_class* i_proc);
int fpcDt_ToDeleteQ(base_process_class* i_proc);
int fpcDt_ToQueue(base_process_class* i_proc);
void fpcDt_Handler();
+21
View File
@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.25)
project(custom_actor_demo CXX)
set(CMAKE_CXX_STANDARD 20)
if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(DUSK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../.." CACHE PATH "Path to dusk source root")
option(DUSK_MOD_USE_FULL_TREE "Use full build instead of the minimal mod SDK" OFF)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if (DUSK_MOD_USE_FULL_TREE)
add_subdirectory("${DUSK_DIR}" dusk EXCLUDE_FROM_ALL)
else ()
add_subdirectory("${DUSK_DIR}/sdk" dusk-sdk EXCLUDE_FROM_ALL)
endif ()
endif ()
add_mod(custom_actor_demo
FEATURES game fmt
SOURCES src/mod.cpp src/m_a_obj_wrock.cpp src/m_a_mine.cpp
MOD_JSON mod.json
RES_DIR res
)
+7
View File
@@ -0,0 +1,7 @@
{
"id": "dev.twilitrealm.custom_actor_demo",
"name": "[Demo] Custom Actor",
"version": "1.0.0",
"author": "Twilit Realm",
"description": "A mod to demonstrate how to create and use custom actors. Spawns a \"wrock\" in South Faron Woods."
}
View File
+227
View File
@@ -0,0 +1,227 @@
#include "m_a_mine.hpp"
#include "d/d_com_inf_game.h"
#include "res/Object/O_mD_jira.h"
// The name of the archive in /res/Object/
static const char* l_resName = "O_mD_jira";
// The actor's heap (for resources) should be enough to hold the data for the model and collision
static constexpr u32 heap_size = ALIGN_NEXT(16832, 0x20);
ma_Mine_c::~ma_Mine_c() {
// Called every time the actor is deleted
// Delete the sound object
mSound.deleteObject();
// Request to unload the archive (the data acts as a shared pointer, and only gets deleted when
// the reference counter goes to zero)
dComIfG_resDelete(&mPhase, l_resName);
}
cPhs_Step ma_Mine_c::create() {
// Because of how the actor system works, an actor's constructor doesn't get called when an
// actor is created. We need to manually do it here with the following ma:
fopAcM_ct(this, ma_Mine_c);
// The create function gets called until we return cPhs_COMPLEATE_e while an actor is loading.
// We request to load the archive here, and wait until the dvd completes loading it
cPhs_Step step = dComIfG_resLoad(&mPhase, l_resName);
if (step == cPhs_COMPLEATE_e) {
// Initialize the solid heap for the actor's resources, if needed
if (!fopAcM_entrySolidHeap(this, createHeapCallBack, heap_size)) {
return cPhs_ERROR_e;
}
// Setup our collision sphere and register it to the world
// Set a circle "wall" of 30 units around the actor
mAcchCir.SetWall(30.0f, 30.0f);
mAcch.Set(this, 1, &mAcchCir);
mAcch.ClrWaterNone();
mAcch.SetRoofCrrHeight(60.0f);
mAcch.SetWaterCheckOffset(10000.0f);
mAcch.SetWtrChkMode(2);
mAcch.OnLineCheck();
mCcStts.Init(30, 0xFF, this);
// Collision Sphere for the actor (copied from Bomb Actor)
static const dCcD_SrcSph
l_sphSrc = {.mObjInf =
{
.mObj = {.mFlags = 0x0,
.mSrcObjHitInf = {.mObjAt = {.mType = AT_TYPE_BOMB,
.mAtp = 0x4,
.mBase = {.mSPrm = 0x1e}},
.mObjTg = {.mType = 0xd8fbffef, .mBase = {.mSPrm = 0x11}},
.mObjCo = {.mBase = {.mSPrm = 0x79}}}},
.mGObjAt{.mSe = dCcD_SE_NONE,
.mHitMark = 0x0,
.mSpl = 0x1,
.mMtrl = 0x0,
.mBase = {.mGFlag = 0x0}},
.mGObjTg{.mSe = dCcD_SE_NONE,
.mHitMark = 0x0,
.mSpl = 0x0,
.mMtrl = 0x0,
.mBase = {.mGFlag = 0x4}},
.mGObjCo{.mBase = {.mGFlag = 0x0}},
},
.mSphAttr = {.mSph = {.mCenter = {0.0f, 0.0f, 0.0f}, .mRadius = 80.0f}}};
mCollisionSphere.Set(l_sphSrc);
mCollisionSphere.SetStts(&mCcStts);
// We register a callback anytime the actor is hit (both attacks and is hit)
mCollisionSphere.SetAtHitCallback(atHitCallback);
mCollisionSphere.SetTgHitCallback(atHitCallback);
mCollisionSphere.OffTgSetBit();
mCollisionSphere.OffCoSetBit();
mCollisionSphere.OnAtSetBit(); // Enable the attack sphere
// Set the initial matrix and cull box for the actor
fopAcM_SetMtx(this, mpModel->getBaseTRMtx());
fopAcM_SetMin(this, -36.0f, 0.0f, -36.0f);
fopAcM_SetMax(this, 36.0f, 66.0f, 36.0f);
// Call execute so the actor's information in the world can be updated
Execute();
}
return step;
}
// Initializes the heap that all instances of this actor will use for resources
// Gets called from the callback in fopAcM_entrySolidHeap
int ma_Mine_c::CreateHeap() {
// Get the bmd data from the archive and initialize it
J3DModelData* model_data =
(J3DModelData*)dComIfG_getObjectRes(l_resName, dRes_INDEX_O_MD_JIRA_BMD_O_MD_JIRAI_e);
if (model_data == NULL) {
return 0;
}
mpModel = mDoExt_J3DModel__create(model_data, 0x80000, 0x11000084);
if (mpModel == NULL) {
return 0;
}
// Create the sound object the actor will use
mSound.init(&current.pos, 1);
return 1;
}
int ma_Mine_c::createHeapCallBack(fopAc_ac_c* i_this) {
return static_cast<ma_Mine_c*>(i_this)->CreateHeap();
}
int ma_Mine_c::Delete() {
// Call the destructor anytime we delete the actor
this->~ma_Mine_c();
return 1;
}
int ma_Mine_c::Execute() {
// Update collision with the world
mAcch.CrrPos(dComIfG_Bgsp());
mCollisionSphere.SetC(attention_info.position);
dComIfG_Ccsp()->Set(&mCollisionSphere);
// Get the collision below the actor
cBgS_GndChk groundChunk = mAcch.m_gnd;
f32 groundH = mAcch.GetGroundH();
if (groundH != -G_CM3D_F_INF) {
// Set the actor's environment colors to match the room's current ones
int roomNo = dComIfG_Bgsp().GetRoomId(groundChunk);
tevStr.YukaCol = dComIfG_Bgsp().GetPolyColor(groundChunk);
tevStr.room_no = roomNo;
// Set the actor's room to be where it is sitting
mCcStts.SetRoomId(roomNo);
fopAcM_SetRoomNo(this, roomNo);
// Get the reverb info here that the actor can use when exploding
mReverb = dComIfGp_getReverb(roomNo);
}
// Update the model's transformation matrix to match the actor's transform
mDoMtx_stack_c::transS(current.pos.x, current.pos.y, current.pos.z);
mDoMtx_stack_c::ZXYrotM(shape_angle);
mDoMtx_stack_c::scaleM(scale);
mpModel->setBaseTRMtx(mDoMtx_stack_c::get());
// Set any attention flags (if needed)
eyePos = attention_info.position = current.pos;
attention_info.flags = 0;
return 1;
}
int ma_Mine_c::Draw() {
// Update the model's lighting with the scene
g_env_light.settingTevStruct(0x20, &current.pos, &tevStr);
g_env_light.setLightTevColorType_MAJI(mpModel, &tevStr);
// Set the bmd model to be drawn when the display list is executed
mDoExt_modelUpdateDL(mpModel);
return 1;
}
void ma_Mine_c::atHit(dCcD_GObjInf* i_atObjInf) {
// Create particles with these IDs at the actor's position
static const u16 normalNameID[] = {
0x161, 0x162, 0x163, 0x164, 0x165, 0x166, 0x167, 0x168, 0x1EC};
for (int i = 0; i < ARRAY_SIZE(normalNameID); i++) {
dComIfGp_particle_setColor(normalNameID[i], &current.pos, &tevStr, NULL, NULL, 0.0f, 0xFF,
&shape_angle, &scale, NULL, -1, NULL);
}
// Create an explosion sound
mSound.startSound(Z2SE_OBJ_BOMB_EXPLODE, 0, mReverb);
// Vibrate the controller
dComIfGp_getVibration().StartShock(4, 31, cXyz(0.0f, 1.0f, 0.0f));
// Request to delete the actor so it disappears
fopAcM_delete(this);
}
void ma_Mine_c::atHitCallback(fopAc_ac_c* i_tgActor, dCcD_GObjInf* i_tgObjInf,
fopAc_ac_c* i_atActor, dCcD_GObjInf* i_atObjInf) {
// This callback gets triggered anytime an intersection happens with the object's collision sphere
((ma_Mine_c*)i_tgActor)->atHit(i_atObjInf);
}
static cPhs_Step ma_Mine_create(void* i_this) {
return static_cast<ma_Mine_c*>(i_this)->create();
}
static int maMine_Delete(void* i_this) {
return static_cast<ma_Mine_c*>(i_this)->Delete();
}
static int maMine_Execute(void* i_this) {
return static_cast<ma_Mine_c*>(i_this)->Execute();
}
static int maMine_Draw(void* i_this) {
return static_cast<ma_Mine_c*>(i_this)->Draw();
}
static int maMine_IsDelete(void*) {
return 1;
}
s16 ma_Mine_c::sProcName = -1;
ActorHandle ma_Mine_c::sActorHandle = -1;
const ActorProfileDesc ma_Mine_c::sProfile = {.name = MA_MINE_NAME,
.priority_group = 7,
.process_size = sizeof(ma_Mine_c),
.draw_priority = fpcDwPi_OBJ_LBOX_e, // An unused draw priority
.status = fopAcStts_UNK_0x40000_e | fopAcStts_UNK_0x4000_e | fopAcStts_CULL_e,
.group = fopAc_ACTOR_e,
.cull_type = fopAc_CULLBOX_CUSTOM_e,
.create_function = ma_Mine_create,
.delete_function = maMine_Delete,
.execute_function = maMine_Execute,
.is_delete_function = maMine_IsDelete,
.draw_function = maMine_Draw};
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include "mods/svc/actor.h"
// Base actor class definitions
#include "f_op/f_op_actor.h"
// Definitions for request_of_phase_process_class and cPhs_Step
#include "SSystem/SComponent/c_phase.h"
// Definitions for collision
#include "d/d_bg_s_acch.h"
#include "d/d_bg_w.h"
#include "d/d_cc_d.h"
#define MA_MINE_NAME "m_mine"
class ma_Mine_c : public fopAc_ac_c {
public:
request_of_phase_process_class mPhase;
J3DModel* mpModel;
dBgS_ObjAcch mAcch;
dBgS_AcchCir mAcchCir;
Mtx mColliderMtx;
dCcD_Stts mCcStts;
dCcD_Sph mCollisionSphere;
Z2SoundObjSimple mSound;
s8 mReverb;
virtual ~ma_Mine_c();
cPhs_Step create();
int CreateHeap();
int Delete();
int Execute();
int Draw();
void atHit(dCcD_GObjInf* i_atObjInf);
static int createHeapCallBack(fopAc_ac_c*);
static void atHitCallback(fopAc_ac_c* i_tgActor, dCcD_GObjInf* i_tgObjInf,
fopAc_ac_c* i_atActor, dCcD_GObjInf* i_atObjInf);
static s16 sProcName;
static ActorHandle sActorHandle;
static const ActorProfileDesc sProfile;
};
@@ -0,0 +1,195 @@
/*
* m_a_obj_wrock.cpp
* An example actor for a rock that can be placed in the world.
*/
#include "m_a_obj_wrock.hpp"
#include "d/d_com_inf_game.h"
#include "res/Object/WRock.h"
// The name of the archive in /res/Object/
static const char* l_resName = "Wrock";
// The actor's heap (for resources) should be enough to hold the data for the model and collision
static constexpr u32 heap_size = ALIGN_NEXT(13952, 0x20) + ALIGN_NEXT(1920, 0x20);
maObj_Wrock_c::~maObj_Wrock_c() {
// Called every time the actor is deleted
// Remove the collider from the world's collision
if (mpCollider != NULL) {
dComIfG_Bgsp().Release(mpCollider);
}
// Request to unload the archive (the data acts as a shared pointer, and only gets deleted when
// the reference counter goes to zero)
dComIfG_resDelete(&mPhase, l_resName);
}
cPhs_Step maObj_Wrock_c::create() {
// Because of how the actor system works, an actor's constructor doesn't get called when an
// actor is created. We need to manually do it here with the following macro:
fopAcM_ct(this, maObj_Wrock_c);
// The create function gets called until we return cPhs_COMPLEATE_e while an actor is loading.
// We request to load the archive here, and wait until the dvd completes loading it
cPhs_Step step = dComIfG_resLoad(&mPhase, l_resName);
if (step == cPhs_COMPLEATE_e) {
// Initialize the solid heap for the actor's resources, if needed
if (!fopAcM_entrySolidHeap(this, createHeapCallBack, heap_size)) {
return cPhs_ERROR_e;
}
// Register the actor's collider to the current world's collision
if (mpCollider != NULL) {
if (dComIfG_Bgsp().Regist(mpCollider, this) == true) {
return cPhs_ERROR_e;
}
}
// Set the initial matrix and cull box for the actor
fopAcM_SetMtx(this, mpModel->getBaseTRMtx());
fopAcM_setCullSizeBox(this, -400.0f, -400.0f, -400.0f, 400.0f, 400.0f, 400.0f);
// Setup collision info (will be used in execute)
mAcch.Set(&current.pos, &old.pos, this, 1, &mAcchCir, &speed, &current.angle, &shape_angle);
// Call execute so the actor's information in the world can be updated
Execute();
}
return step;
}
// Initializes the heap that all instances of this actor will use for resources
// Gets called from the callback in fopAcM_entrySolidHeap
int maObj_Wrock_c::CreateHeap() {
// Get the bmd data from the archive and initialize it
J3DModelData* model_data =
(J3DModelData*)dComIfG_getObjectRes(l_resName, dRes_INDEX_WROCK_BMD_WROCK_e);
if (model_data == NULL) {
return 0;
}
mpModel = mDoExt_J3DModel__create(model_data, 0x80000, 0x11000084);
if (mpModel == NULL) {
return 0;
}
// Get the dzb collision data from the archive and initialize it
mpCollider = JKR_NEW dBgW();
if (mpCollider == NULL) {
return 0;
}
cBgD_t* dzb = (cBgD_t*)dComIfG_getObjectRes(l_resName, dRes_INDEX_WROCK_DZB_WROCK_e);
if (mpCollider->Set(dzb, 1, &mColliderMtx) == true) {
return 0;
}
mpCollider->SetCrrFunc(dBgS_MoveBGProc_Typical);
return 1;
}
int maObj_Wrock_c::createHeapCallBack(fopAc_ac_c* i_this) {
return static_cast<maObj_Wrock_c*>(i_this)->CreateHeap();
}
int maObj_Wrock_c::Delete() {
// Call the destructor anytime we delete the actor
this->~maObj_Wrock_c();
return 1;
}
int maObj_Wrock_c::Execute() {
// Update collision with the world
mAcch.CrrPos(dComIfG_Bgsp());
// Get the collision below the actor
mGndChk = mAcch.m_gnd;
mGroundH = mAcch.GetGroundH();
if (mGroundH != -G_CM3D_F_INF) {
// Set the actor's environment colors to match the room's current ones
tevStr.YukaCol = dComIfG_Bgsp().GetPolyColor(mGndChk);
tevStr.room_no = dComIfG_Bgsp().GetRoomId(mGndChk);
// Set the actor's room to be where it is sitting
fopAcM_SetRoomNo(this, dComIfG_Bgsp().GetRoomId(mGndChk));
}
// Update the model's transformation matrix to match the actor's transform
mDoMtx_stack_c::transS(current.pos.x, current.pos.y, current.pos.z);
mDoMtx_stack_c::ZXYrotM(shape_angle);
mDoMtx_stack_c::scaleM(scale);
mpModel->setBaseTRMtx(mDoMtx_stack_c::get());
// Copy the model's transformation matrix to the collider's transformation matrix and update the
// collider
if (mpCollider != NULL) {
PSMTXCopy(mpModel->getBaseTRMtx(), mColliderMtx);
mpCollider->Move();
}
// Set any attention flags (if needed)
eyePos = attention_info.position = current.pos;
attention_info.flags = 0;
return 1;
}
int maObj_Wrock_c::Draw() {
// Update the model's lighting with the scene
g_env_light.settingTevStruct(0x20, &current.pos, &tevStr);
g_env_light.setLightTevColorType_MAJI(mpModel, &tevStr);
// Set the current dlist to BG Which means that shadows can be cast on it
// Because of the messy collider, the shadows don't look great, but this is here as an example
dComIfGd_setListBG();
// Set the bmd model to be drawn when the display list is executed
mDoExt_modelUpdateDL(mpModel);
// Cast a shadow for the actor onto the ground.
// We can only do this if we are drawing to the normal dlist, not the BG dlist
// if (mGroundH != -G_CM3D_F_INF) {
// mShadow = dComIfGd_setShadow(mShadow, 1, mpModel, &current.pos,
// 2000.0f, 0.0f,
// current.pos.y, mGroundH, mGndChk, &tevStr, 0,
// 1.0f, &dDlst_shadowControl_c::mSimpleTexObj);
// }
// Reset the active dlist
dComIfGd_setList();
return 1;
}
static cPhs_Step maObj_Wrock_Create(void* i_this) {
return static_cast<maObj_Wrock_c*>(i_this)->create();
}
static int maObj_Wrock_Delete(void* i_this) {
return static_cast<maObj_Wrock_c*>(i_this)->Delete();
}
static int maObj_Wrock_Execute(void* i_this) {
return static_cast<maObj_Wrock_c*>(i_this)->Execute();
}
static int maObj_Wrock_Draw(void* i_this) {
return static_cast<maObj_Wrock_c*>(i_this)->Draw();
}
static int maObj_Wrock_IsDelete(void*) {
return 1;
}
s16 maObj_Wrock_c::sProcName = -1;
ActorHandle maObj_Wrock_c::sActorHandle = -1;
const ActorProfileDesc maObj_Wrock_c::sProfile = {.name = MAOBJ_WROCK_NAME,
.priority_group = 7,
.process_size = sizeof(maObj_Wrock_c),
.draw_priority = fpcDwPi_OBJ_LBOX_e, // An unused draw priority
.status = fopAcStts_UNK_0x40000_e | fopAcStts_UNK_0x4000_e | fopAcStts_CULL_e,
.group = fopAc_ACTOR_e,
.cull_type = fopAc_CULLBOX_CUSTOM_e,
.create_function = maObj_Wrock_Create,
.delete_function = maObj_Wrock_Delete,
.execute_function = maObj_Wrock_Execute,
.is_delete_function = maObj_Wrock_IsDelete,
.draw_function = maObj_Wrock_Draw};
@@ -0,0 +1,39 @@
#pragma once
#include "mods/svc/actor.h"
// Base actor class definitions
#include "f_op/f_op_actor.h"
// Definitions for request_of_phase_process_class and cPhs_Step
#include "SSystem/SComponent/c_phase.h"
// Definitions for collision
#include "d/d_bg_s_acch.h"
#include "d/d_bg_w.h"
#define MAOBJ_WROCK_NAME "wrock"
class maObj_Wrock_c : public fopAc_ac_c {
public:
request_of_phase_process_class mPhase;
J3DModel* mpModel;
dBgS_ObjAcch mAcch;
cBgS_GndChk mGndChk;
dBgS_AcchCir mAcchCir;
Mtx mColliderMtx;
dBgW* mpCollider;
f32 mGroundH;
int mShadow;
virtual ~maObj_Wrock_c();
cPhs_Step create();
int CreateHeap();
int Delete();
int Execute();
int Draw();
static int createHeapCallBack(fopAc_ac_c*);
static s16 sProcName;
static ActorHandle sActorHandle;
static const ActorProfileDesc sProfile;
};
+85
View File
@@ -0,0 +1,85 @@
#include "mods/service.hpp"
#include "mods/svc/actor.h"
#include "mods/svc/log.hpp"
#include "mods/svc/stage.h"
#include "d/d_com_inf_game.h"
#include "m_a_mine.hpp"
#include "m_a_obj_wrock.hpp"
#include <array>
DEFINE_MOD();
IMPORT_SERVICE(LogService, svc_log);
IMPORT_SERVICE(ActorService, svc_actor);
IMPORT_SERVICE(StageService, svc_stage);
extern "C" {
MOD_EXPORT ModResult mod_initialize(ModError*) {
if (svc_actor->register_actor(mod_ctx, &maObj_Wrock_c::sProfile, &maObj_Wrock_c::sProcName,
&maObj_Wrock_c::sActorHandle) != MOD_OK)
{
mods::log::error("Failed to register actor wrock!");
return MOD_ERROR;
}
const stage_actor_data_class wrockParams{
.name = MAOBJ_WROCK_NAME,
.base =
{
.parameters = 0,
.position = {-14324.0f, 0.0f, 341.0f},
.angle = {0, -16595, 0},
.setID = 0xFFFF,
},
};
if (svc_stage->add_actor(
mod_ctx, "F_SP108", 0, -1, &wrockParams, sizeof(wrockParams), nullptr) != MOD_OK)
{
mods::log::error("Adding wrock to F_SP108 Failed!");
return MOD_ERROR;
}
if (svc_actor->register_actor(mod_ctx, &ma_Mine_c::sProfile, &ma_Mine_c::sProcName,
&ma_Mine_c::sActorHandle) != MOD_OK)
{
mods::log::error("Failed to register actor " MA_MINE_NAME);
return MOD_ERROR;
}
static const std::array<cXyz, 6> minePositions = {
{{-14445.0f, 11.0f, 1304.0f}, {-14557.0f, 7.0f, 990.0f}, {-14790.0f, 7.0f, 634.0f},
{-14829.0f, 0.0f, 144.0f}, {-14615.0f, 0.0f, -170.0f}, {-14283.0f, 10.0f, -420.0f}}};
for (const auto& pos : minePositions) {
const stage_actor_data_class mineParams{
.name = MA_MINE_NAME,
.base =
{
.parameters = 0,
.position = {pos.x, pos.y, pos.z},
.angle = {0x2000, (s16)(pos.x*100000), 0}, // Adjusted and seemingly random angle
.setID = 0xFFFF,
},
};
if (svc_stage->add_actor(
mod_ctx, "F_SP108", 0, -1, &mineParams, sizeof(mineParams), nullptr) != MOD_OK)
{
mods::log::error("Adding mine to F_SP108 Failed!");
return MOD_ERROR;
}
}
mods::log::info("custom_actor_demo initialized");
return MOD_OK;
}
MOD_EXPORT ModResult mod_update(ModError*) {
return MOD_OK;
}
MOD_EXPORT ModResult mod_shutdown(ModError*) {
mods::log::info("custom_actor_demo shutdown");
return MOD_OK;
}
}
+102
View File
@@ -0,0 +1,102 @@
#pragma once
#include <mods/api.h>
#include <mods/svc/config.h>
#define ACTOR_SERVICE_ID "dev.twilitrealm.dusklight.actor"
#define ACTOR_SERVICE_MAJOR 1u
#define ACTOR_SERVICE_MINOR 0u
typedef int16_t ProfileName;
typedef uint32_t ActorId;
typedef uint64_t ActorHandle;
typedef struct {
const char name[8]; // Canonical stage name. Must be unique among active mod actors. Matching a
// game actor name intentionally overrides stage lookup for that actor.
uint16_t priority_group; /* priorityGroup is the priority for when execute will be called on the
actor. Here are the main groups:
0: The room manager actor
1: Game scenes
2: Various room change actors
3: Most objects to be executed before Link
4: Some bosses, canoe, epona, spinner, chests
5: Link's actor
6: Boomerang, midna
7: Most objects, actors, bosses, triggers to be executed after link (most actors go here)
8: Various objects and enemies
9: Various actors
10: Timer, Scene Exit actor
11: Grass, Suspend Actors
*/
size_t process_size; // Size of the actor class (use sizeof(my_actor_class))
int16_t draw_priority; // an enum value that is prefixed with fpcDwPi. Select an existing value
// from the fpcDwPi to pick a draw priority matching the actor you wish
// to match priorities with.
uint32_t status; // Flags from fopAc_Status_e enum (all have fopAcStts_UNK_0x40000_e, a lot
// have fopAcStts_UNK_0x4000_e, add fopAcStts_CULL_e to enable culling)
uint8_t group; // The actor type. An enum value from fopAc_Group_e (fopAc_ACTOR_e,
// fopAc_ENEMY_e, fopAc_NPC_e)
uint8_t cull_type; // Enum value from fopAc_Cull_e
int (*create_function)(
void*); // Called after the actor is spawned, return type is a enum value of cPhs_Step
int (*delete_function)(void*); // Releases resources; returns 1 when deletion is complete
int (*execute_function)(void*); // Called once per game tick, the actor's priorityGroup
// determines when it will run relative to other actors
int (*is_delete_function)(void*); // Returns 1 when normal deletion may begin
int (*draw_function)(void*); // Called to draw the actor
} ActorProfileDesc;
typedef struct {
uint32_t parameters; // The parameters to be passed to the actor
int8_t argument; // The argument to be passed to the actor (acts as an extra byte for a parameter)
int8_t room_num; // The room to spawn the actor in
struct {
float x;
float y;
float z;
} position;
struct {
int16_t x;
int16_t y;
int16_t z;
} angle;
struct {
float x;
float y;
float z;
} scale;
int (*create_function)(void*); // Optional: A custom function to run when the actor is created.
} ActorSpawnParams;
typedef struct ActorService {
ServiceHeader header;
ModResult (*register_actor)(ModContext* ctx, const ActorProfileDesc* desc,
ProfileName* outProfileName, ActorHandle* outActorHandle);
ModResult (*unregister_actor)(ModContext* ctx, ActorHandle handle);
ModResult (*create_actor_from_name)(
ModContext* ctx, const char* name, const ActorSpawnParams* params, ActorId* outId);
ModResult (*create_actor)(
ModContext* ctx, ProfileName name, const ActorSpawnParams* params, ActorId* outId);
ModResult (*create_child_actor_from_name)(ModContext* ctx, const char* name, ActorId parentID,
const ActorSpawnParams* params, ActorId* outId);
ModResult (*create_child_actor)(ModContext* ctx, ProfileName name, ActorId parentID,
const ActorSpawnParams* params, ActorId* outId);
ModResult (*get_actor_id)(ModContext* ctx, ProfileName name, ActorId* outId);
ModResult (*get_actor_room_num)(ModContext* ctx, ActorId actorId, int8_t* outRoomNum);
/* Returns MOD_UNAVAILABLE if the actor cannot be queued for deletion immediately. */
ModResult (*delete_actor)(ModContext* ctx, ActorId actorId);
} ActorService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct mods::ServiceTraits<ActorService> {
static constexpr const char* id = ACTOR_SERVICE_ID;
static constexpr uint16_t major_version = ACTOR_SERVICE_MAJOR;
static constexpr uint16_t minor_version = ACTOR_SERVICE_MINOR;
};
#endif
+18
View File
@@ -28,6 +28,7 @@
#include "dusk/mods/svc/stage.hpp"
#include <format>
#include <fmt/ranges.h>
#include "dusk/mods/svc/actor.hpp"
#endif
void dStage_nextStage_c::set(const char* i_stage, s8 i_roomId, s16 i_point, s8 i_layer, s8 i_wipe,
@@ -1524,6 +1525,14 @@ static void dummy0() {
}
dStage_objectNameInf* dStage_searchName(char const* objName) {
#if TARGET_PC
dStage_objectNameInf* info =
dusk::mods::svc::actor_impl::get_stageinfo_from_full_name(objName);
if (info != nullptr) {
return info;
}
#endif
dStage_objectNameInf* obj = l_objectName;
for (u32 i = 0; i < ARRAY_SIZEU(l_objectName); i++) {
@@ -1561,6 +1570,15 @@ dStage_objectNameInf* dStage_searchNameCI(char const* objName) {
const char* dStage_getName(s16 procName, s8 argument) {
static char tmp_name[dStage_NAME_LENGTH];
#if TARGET_PC
const char* name = dusk::mods::svc::actor_impl::get_full_name_from_proc_name(procName);
if (name[0] != '\0') {
strncpy(tmp_name, name, sizeof(tmp_name) - 1);
tmp_name[sizeof(tmp_name) - 1] = '\0';
return tmp_name;
}
#endif
dStage_objectNameInf* obj = l_objectName;
char* tmp = NULL;
+404
View File
@@ -0,0 +1,404 @@
#include "mods/svc/actor.h"
#include "dusk/mods/svc/actor.hpp"
#include "config.hpp"
#include "registry.hpp"
#include "slot_map.hpp"
#include "aurora/lib/logging.hpp"
#include "dusk/mod_loader.hpp"
#include "dusk/mods/loader/loader.hpp"
#include <fmt/format.h>
#include "d/d_stage.h"
#include "f_op/f_op_actor_tag.h"
#include "f_pc/f_pc_deletor.h"
#include <cstring>
#include <vector>
namespace dusk::mods::svc::actor_impl {
namespace {
aurora::Module Log("dusk::mods::actor");
SlotMap<std::unique_ptr<ActorSlot>> s_slots;
std::unordered_map<s16, ActorHandle> procNameToHandle;
std::unordered_map<std::string, ActorHandle> fullNameToHandle;
ActorSlot* get_actor_slot(void* actorPtr) {
auto* actor = static_cast<fopAc_ac_c*>(actorPtr);
const auto handle = procNameToHandle.find(actor->name);
if (handle == procNameToHandle.end()) {
return nullptr;
}
auto* slot = s_slots.find(handle->second);
return slot != nullptr ? slot->value.get() : nullptr;
}
int actor_is_delete(void* actorPtr) {
auto* slot = get_actor_slot(actorPtr);
if (slot == nullptr || slot->forceDelete) {
return 1;
}
return slot->isDeleteFunction(actorPtr);
}
int actor_delete(void* actorPtr) {
auto* slot = get_actor_slot(actorPtr);
if (slot == nullptr) {
return 1;
}
const bool forceDelete = slot->forceDelete;
const int result = slot->deleteFunction(actorPtr);
return forceDelete ? 1 : result;
}
ModResult register_actor(ModContext* ctx, const ActorProfileDesc* desc, ProfileName* outProfileName,
ActorHandle* outActorHandle) {
auto* owner = mod_from_context(ctx);
if (owner == nullptr || desc == nullptr || outProfileName == nullptr ||
outActorHandle == nullptr)
{
return MOD_INVALID_ARGUMENT;
}
if (desc->name[0] == '\0' || std::memchr(desc->name, '\0', sizeof(desc->name)) == nullptr ||
desc->process_size < sizeof(fopAc_ac_c) || desc->create_function == nullptr ||
desc->delete_function == nullptr || desc->execute_function == nullptr ||
desc->is_delete_function == nullptr || desc->draw_function == nullptr)
{
return MOD_INVALID_ARGUMENT;
}
// If another mod has already registered an actor with this name, note a conflict
if (get_stageinfo_from_full_name(desc->name)) {
return MOD_CONFLICT;
}
const auto handle = s_slots.emplace(*owner,
std::make_unique<ActorSlot>(
ActorSlot{{desc->create_function, actor_delete, desc->execute_function, actor_is_delete,
desc->draw_function},
desc->delete_function, desc->is_delete_function, false,
{
{}, // Set after registered
0, // Set after registered to a slot
-1 // Use the default argument
},
{
/* Layer ID */ fpcLy_CURRENT_e,
/* List ID */ desc->priority_group,
/* List Prio */ fpcPi_CURRENT_e, // Always fpcPi_CURRENT_e
/* Proc Name */ 0,
/* Proc SubMtd */ &g_fpcLf_Method.base, // usually: &g_fpcLf_Method.base
/* Size */ (u32)desc->process_size,
/* Size Other */ 0, // Always 0
/* Parameters */ 0, // Always 0
/* Leaf SubMtd */ &g_fopAc_Method.base, // usually &g_fopAc_Method.base
/* Draw Prio */ desc->draw_priority,
/* Actor SubMtd */ nullptr, // set this later
/* Status */ desc->status,
/* Group */ desc->group,
/* Cull Type */ desc->cull_type,
}}));
s32 procNameFull = fpcNm_MAX_NUM + s_slots.index_of(handle);
if (procNameFull >= 0x7FFF) {
s_slots.erase(handle);
Log.error("Hit registered actor limit (0x7FFF) while registering actor {}", desc->name);
return MOD_ERROR;
}
ActorSlot& slot = *s_slots.find(handle)->value.get();
strncpy(slot.objNameInf.name, desc->name, sizeof(slot.objNameInf.name) - 1);
slot.profile.sub_method = &slot.methodTable;
s16 procName = (s16)procNameFull;
procNameToHandle[procName] = handle;
fullNameToHandle[std::string(slot.objNameInf.name)] = handle;
slot.objNameInf.procname = (s16)procName;
slot.profile.base.base.name = procName;
*outProfileName = procName;
*outActorHandle = handle;
return MOD_OK;
}
// Must be called before the slot associated with the handle is erased
void remove_handle_from_maps(ActorHandle handle) {
auto entry = s_slots.find(handle);
if (entry == nullptr) {
return;
}
std::string fullName = entry->value->objNameInf.name;
const auto it = fullNameToHandle.find(fullName);
if (it != fullNameToHandle.end()) {
fullNameToHandle.erase(it);
}
const auto it2 = procNameToHandle.find(entry->value->profile.base.base.name);
if (it2 != procNameToHandle.end()) {
procNameToHandle.erase(it2);
}
}
void request_delete_all_actors_of_name(s16 procName) {
node_class* node = g_fopAcTg_Queue.mpHead;
while (node != nullptr) {
node_class* next = NODE_GET_NEXT(node);
auto* actor =
static_cast<fopAc_ac_c*>(reinterpret_cast<create_tag_class*>(node)->mpTagData);
if (actor->name == procName) {
fopAcM_delete(actor);
}
node = next;
}
}
bool finish_delete_all_actors_of_name(s16 procName) {
bool complete = true;
node_class* node = g_fpcDtTg_Queue.mpHead;
while (node != nullptr) {
node_class* next = NODE_GET_NEXT(node);
auto* tag = reinterpret_cast<delete_tag_class*>(node);
auto* proc = static_cast<base_process_class*>(tag->base.mpTagData);
if (proc->name == procName) {
tag->timer = 0;
const int deleteStatus = fpcDtTg_Do(
tag, [](void* proc) { return fpcDt_deleteMethod((base_process_class*)proc); });
if (deleteStatus == 0) {
complete = false;
}
}
node = next;
}
return complete;
}
bool has_actor_of_name(s16 procName) {
node_class* node = g_fopAcTg_Queue.mpHead;
while (node != nullptr) {
auto* actor =
static_cast<fopAc_ac_c*>(reinterpret_cast<create_tag_class*>(node)->mpTagData);
if (actor->name == procName) {
return true;
}
node = NODE_GET_NEXT(node);
}
return false;
}
ModResult unregister_actor(ModContext* ctx, ActorHandle handle) {
auto* owner = mod_from_context(ctx);
if (owner == nullptr) {
return MOD_INVALID_ARGUMENT;
}
auto* slot = s_slots.find_owned(handle, *owner);
if (slot == nullptr) {
return MOD_INVALID_ARGUMENT;
}
const s16 actorProcName = slot->value->profile.base.base.name;
slot->value->forceDelete = true;
request_delete_all_actors_of_name(actorProcName);
const bool deletesFinished = finish_delete_all_actors_of_name(actorProcName);
if (!deletesFinished || has_actor_of_name(actorProcName)) {
slot->value->forceDelete = false;
Log.warn("Actor profile '{}' could not be unregistered synchronously",
slot->value->objNameInf.name);
return MOD_UNAVAILABLE;
}
remove_handle_from_maps(handle);
s_slots.erase_owned(handle, *owner);
return MOD_OK;
}
ModResult create_actor(
ModContext* ctx, ProfileName name, const ActorSpawnParams* params, ActorId* outId) {
cXyz pos = {params->position.x, params->position.y, params->position.z};
csXyz angle = {params->angle.x, params->angle.y, params->angle.z};
cXyz scale = {params->scale.x, params->scale.y, params->scale.z};
fpc_ProcID id = fopAcM_create(name, 0xFFFF, params->parameters, &pos, params->room_num, &angle,
&scale, params->argument, params->create_function);
if (id == fpcM_ERROR_PROCESS_ID_e) {
Log.error("Error Creating Actor with profile name {}", name);
return MOD_ERROR;
}
if (outId) {
*outId = id;
}
return MOD_OK;
}
ModResult create_actor_from_name(
ModContext* ctx, const char* name, const ActorSpawnParams* params, ActorId* outId) {
dStage_objectNameInf* objectName = dStage_searchName(name);
if (objectName == nullptr) {
Log.error("Attempted to create actor ({}) but it can't be found!", name);
return MOD_ERROR;
}
ActorSpawnParams copy = *params;
copy.argument = objectName->argument;
return create_actor(ctx, objectName->procname, &copy, outId);
}
ModResult create_child_actor(ModContext* ctx, ProfileName name, ActorId parentID,
const ActorSpawnParams* params, ActorId* outId) {
cXyz pos = {params->position.x, params->position.y, params->position.z};
csXyz angle = {params->angle.x, params->angle.y, params->angle.z};
cXyz scale = {params->scale.x, params->scale.y, params->scale.z};
fpc_ProcID id = fopAcM_createChild(name, parentID, params->parameters, &pos, params->room_num,
&angle, &scale, params->argument, params->create_function);
if (id == fpcM_ERROR_PROCESS_ID_e) {
Log.error("Error Creating Actor with profile name {} as child of actor with id {}", name,
parentID);
return MOD_ERROR;
}
if (outId) {
*outId = id;
}
return MOD_OK;
}
ModResult create_child_actor_from_name(ModContext* ctx, const char* name, ActorId parentID,
const ActorSpawnParams* params, ActorId* outId) {
dStage_objectNameInf* objectName = dStage_searchName(name);
if (objectName == nullptr) {
Log.error("Attempted to create actor ({}) but it can't be found!", name);
return MOD_ERROR;
}
ActorSpawnParams copy = *params;
copy.argument = objectName->argument;
return create_child_actor(ctx, objectName->procname, parentID, &copy, outId);
}
ModResult get_actor_id(ModContext* ctx, ProfileName name, ActorId* outId) {
fopAc_ac_c* actor = fopAcM_SearchByName(name);
if (actor == nullptr) {
Log.error("Attempting to get actor by profile name ({}) but it doesn't exist!", name);
return MOD_ERROR;
}
*outId = fopAcM_GetID(actor);
return MOD_OK;
}
ModResult get_actor_room_num(ModContext* ctx, ActorId actorId, int8_t* outRoomNum) {
fopAc_ac_c* actor = fopAcM_SearchByID(actorId);
if (actor == nullptr) {
Log.error("Attempted to get room number of actor Id ({}) but it doesn't exist!", actorId);
return MOD_ERROR;
}
*outRoomNum = fopAcM_GetRoomNo(actor);
return MOD_OK;
}
ModResult delete_actor(ModContext* ctx, ActorId actorId) {
if (mod_from_context(ctx) == nullptr) {
return MOD_INVALID_ARGUMENT;
}
fopAc_ac_c* actor = fopAcM_SearchByID(actorId);
if (actor == nullptr) {
Log.warn("Attempted to delete actor with ID ({}) but it doesn't exist!", actorId);
return MOD_OK;
}
return fopAcM_delete(actor) != 0 ? MOD_OK : MOD_UNAVAILABLE;
}
} // namespace
process_profile_definition* get_profile_from_proc_name(s16 name) {
const auto& it = procNameToHandle.find(name);
if (it == procNameToHandle.end()) {
return nullptr;
}
auto entry = s_slots.find(it->second);
if (entry) {
return &entry->value->profile.base.base;
}
return nullptr;
}
const char* get_full_name_from_proc_name(s16 name) {
const auto& it = procNameToHandle.find(name);
if (it == procNameToHandle.end()) {
return "";
}
auto entry = s_slots.find(it->second);
if (entry) {
return entry->value->objNameInf.name;
}
return "";
}
dStage_objectNameInf* get_stageinfo_from_full_name(const std::string& name) {
const auto& it = fullNameToHandle.find(name);
if (it == fullNameToHandle.end()) {
return nullptr;
}
auto entry = s_slots.find(it->second);
if (entry) {
return &entry->value->objNameInf;
}
return nullptr;
}
void actor_remove_mod(LoadedMod& mod) {
std::vector<ActorHandle> handles;
s_slots.for_each([&](const ActorHandle handle, const auto& slot) {
if (slot.owner == &mod) {
handles.push_back(handle);
}
});
for (const auto handle : handles) {
const auto result = unregister_actor(mod.context.get(), handle);
if (result != MOD_OK) {
Log.error("Actor profile could not be removed during mod deactivation");
}
}
}
} // namespace dusk::mods::svc::actor_impl
namespace dusk::mods::svc {
namespace {
constexpr ActorService s_actorService{
.header = SERVICE_HEADER(ActorService, ACTOR_SERVICE_MAJOR, ACTOR_SERVICE_MINOR),
.register_actor = actor_impl::register_actor,
.unregister_actor = actor_impl::unregister_actor,
.create_actor_from_name = actor_impl::create_actor_from_name,
.create_actor = actor_impl::create_actor,
.create_child_actor_from_name = actor_impl::create_child_actor_from_name,
.create_child_actor = actor_impl::create_child_actor,
.get_actor_id = actor_impl::get_actor_id,
.get_actor_room_num = actor_impl::get_actor_room_num,
.delete_actor = actor_impl::delete_actor,
};
}
constinit const ServiceModule g_actorModule{
.id = ACTOR_SERVICE_ID,
.majorVersion = ACTOR_SERVICE_MAJOR,
.minorVersion = ACTOR_SERVICE_MINOR,
.service = &s_actorService,
.modDeactivating = actor_impl::actor_remove_mod,
};
} // namespace dusk::mods::svc
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <memory>
#include <string>
#include <unordered_map>
#include "f_op/f_op_actor_mng.h"
namespace dusk::mods::svc::actor_impl {
struct ActorSlot {
actor_method_class methodTable;
int (*deleteFunction)(void*);
int (*isDeleteFunction)(void*);
bool forceDelete;
dStage_objectNameInf objNameInf;
actor_process_profile_definition profile;
};
process_profile_definition* get_profile_from_proc_name(s16 name);
dStage_objectNameInf* get_stageinfo_from_full_name(const std::string& name);
const char* get_full_name_from_proc_name(s16 name); // Returns "" when not found
}; // namespace dusk::mods::svc::actor_impl
+1
View File
@@ -231,6 +231,7 @@ void ModLoader::init_services() {
&svc::g_flowModule,
&svc::g_messageModule,
&svc::g_gamemodeModule,
&svc::g_actorModule,
})
{
svc::register_module(*module);
+1
View File
@@ -88,5 +88,6 @@ extern const ServiceModule g_itemModule;
extern const ServiceModule g_flowModule;
extern const ServiceModule g_messageModule;
extern const ServiceModule g_gamemodeModule;
extern const ServiceModule g_actorModule;
} // namespace dusk::mods::svc
+5
View File
@@ -122,6 +122,11 @@ public:
}
}
// Returns the index of the handle within the slot map.
static constexpr uint32_t index_of(Handle handle) {
return handle_index(handle);
}
private:
struct Slot {
uint32_t generation = 1;
+19 -1
View File
@@ -5,18 +5,36 @@
#include "f_pc/f_pc_profile.h"
#if TARGET_PC
#include "dusk/mods/svc/actor.hpp"
#include "f_pc/f_pc_name.h"
#endif
#ifndef __MWERKS__
// Forward declare the static list from f_pc_profile_lst.cpp
DUSK_GAME_EXTERN process_profile_definition DUSK_CONST* DUSK_CONST g_fpcPfLst_ProfileList[];
// On PC: Direct pointer to static array
DUSK_GAME_DATA process_profile_definition DUSK_CONST* DUSK_CONST* DUSK_CONST g_fpcPf_ProfileList_p = g_fpcPfLst_ProfileList;
DUSK_GAME_DATA process_profile_definition DUSK_CONST* DUSK_CONST* DUSK_CONST g_fpcPf_ProfileList_p =
g_fpcPfLst_ProfileList;
#else
// On Console: Pointer initialized by REL module prolog
process_profile_definition** g_fpcPf_ProfileList_p;
#endif
process_profile_definition DUSK_CONST* fpcPf_Get(s16 i_profname) {
#if TARGET_PC
// Check if a mod has registered an actor with i_profname. Fallback to the profile list if it
// doesn't exist.
process_profile_definition* profile =
dusk::mods::svc::actor_impl::get_profile_from_proc_name(i_profname);
if (profile != nullptr) {
return profile;
}
if (i_profname < 0 || i_profname >= fpcNm_MAX_NUM) {
return nullptr;
}
#endif
int index = i_profname;
return g_fpcPf_ProfileList_p[index];
}