isle-portable/LEGO1/lego/legoomni/src/common/legocharactermanager.cpp
foxtacles 7b114bbe59
Add multiplayer extension (#789)
* Add multiplayer extension

* Fix animation system to work when host is outside ISLE world

- Move TickHostSessions outside m_inIsleWorld gate so the host can
  coordinate animations from any world
- Load animation catalog early in HandleCreate so the host can
  coordinate before entering the ISLE world
- Use network-reported positions for remote player location detection
  instead of requiring spawned ROIs
- Always erase sessions at launch — the host's job ends when the
  animation starts; clients play and complete independently
- Replace BroadcastAnimComplete with locally-driven completion
  callbacks: host generates eventId at launch, clients cache
  completion JSON at start time, fire it when ScenePlayer finishes
- Make StopAnimation only do local cleanup (stop playback, cancel
  own interest, reset coordinator) without destroying the session
  host, so other players' sessions survive world transitions
- Broadcast state=0 in ResetAnimationState for full teardown paths
  (shutdown, reconnect, host migration) so clients aren't left with
  stale session state

* Fix use-after-free crash in ScenePlayer when remote player disconnects mid-animation

When a remote player's ROI is destroyed (disconnect, timeout, or respawn),
notify all active ScenePlayer instances to null out dangling references
before the ROI is freed. The animation engine already handles null ROI map
entries gracefully, so playback continues for remaining participants.

* Fix crash when performer's child ROIs are left dangling in ScenePlayer

NotifyROIDestroyed now walks the parent chain to also invalidate child
ROIs of the destroyed performer (head, limbs, etc.) that were placed
into the roiMap by BuildROIMap. The ancestor walk happens once; all
other fields are cleaned with simple pointer equality.

* Allow spectator to play click animation during scene playback

* Make PTATCAM track spectator ROI instead of camera in ScenePlayer

* Only regenerate emscripten version files when git state changes

Replace add_custom_target(ALL) with add_custom_command(OUTPUT) so the
version script only runs when .git/HEAD or the current branch ref file
changes, instead of on every build.

* Fix ROI name collision causing dangling pointers in NPC locomotion roiMaps

When ScenePlayer created cloned NPC ROIs for cooperative animations, it
renamed them to match the original character name and added them to the
ViewManager. This created a name collision: two ROIs with the same name.
The original game's AppendROIToScene searches by name and stops at the
first match, so if a locomotion BuildROIMap ran while the clone existed,
it could capture pointers to the clone's child ROIs. When the clone was
later destroyed (CleanupProps), those roiMap entries became dangling
pointers, crashing in AnimateWithTransform at roi.h:151 (SetVisibility).

Fix: use the alias mechanism (already supported by AnimUtils::BuildROIMap)
instead of renaming clones. Also unify all ROI name generation behind a
shared counter to prevent character manager key collisions.
2026-04-05 17:13:15 +02:00

1119 lines
27 KiB
C++

#include "legocharactermanager.h"
#include "3dmanager/lego3dmanager.h"
#include "extensions/multiplayer.h"
#include "extensions/thirdpersoncamera.h"
#include "legoactors.h"
#include "legoanimactor.h"
#include "legobuildingmanager.h"
#include "legoextraactor.h"
#include "legogamestate.h"
#include "legoplantmanager.h"
#include "legovideomanager.h"
#include "misc.h"
#include "misc/legocontainer.h"
#include "misc/legostorage.h"
#include "mxmisc.h"
#include "mxvariabletable.h"
#include "realtime/realtime.h"
#include "roi/legolod.h"
#include "viewmanager/viewmanager.h"
#include <SDL3/SDL_stdinc.h>
#include <assert.h>
#include <stdio.h>
#include <vec.h>
using namespace Extensions;
DECOMP_SIZE_ASSERT(LegoCharacter, 0x08)
DECOMP_SIZE_ASSERT(LegoCharacterManager, 0x08)
DECOMP_SIZE_ASSERT(CustomizeAnimFileVariable, 0x24)
// GLOBAL: LEGO1 0x100fc4d0
MxU32 LegoCharacterManager::g_maxMove = 4;
// GLOBAL: LEGO1 0x100fc4d4
MxU32 LegoCharacterManager::g_maxSound = 9;
// GLOBAL: LEGO1 0x100fc4e0
MxU32 g_characterAnimationId = 10;
// GLOBAL: LEGO1 0x100fc4e4
char* LegoCharacterManager::g_customizeAnimFile = NULL;
// GLOBAL: LEGO1 0x100fc4d8
MxU32 g_characterSoundIdOffset = 50;
// GLOBAL: LEGO1 0x100fc4dc
MxU32 g_characterSoundIdMoodOffset = 66;
// GLOBAL: LEGO1 0x100fc4e8
MxU32 g_headTextureCounter = 0;
// GLOBAL: LEGO1 0x100fc4ec
MxU32 g_infohatVariantCounter = 2;
// GLOBAL: LEGO1 0x100fc4f0
MxU32 g_autoRoiCounter = 0;
// GLOBAL: LEGO1 0x10104f20
LegoActorInfo g_actorInfo[66];
// FUNCTION: LEGO1 0x10082a20
// FUNCTION: BETA10 0x10073c60
LegoCharacterManager::LegoCharacterManager()
{
m_characters = new LegoCharacterMap();
Init(); // DECOMP: inlined here in BETA10
m_customizeAnimFile = new CustomizeAnimFileVariable("CUSTOMIZE_ANIM_FILE");
VariableTable()->SetVariable(m_customizeAnimFile);
}
// FUNCTION: LEGO1 0x10083180
// FUNCTION: BETA10 0x10073dad
LegoCharacterManager::~LegoCharacterManager()
{
LegoCharacter* character = NULL;
LegoCharacterMap::iterator it;
for (it = m_characters->begin(); it != m_characters->end(); it++) {
character = (*it).second;
RemoveROI(character->m_roi);
delete[] (*it).first;
delete (*it).second;
}
delete m_characters;
delete[] g_customizeAnimFile;
}
// FUNCTION: LEGO1 0x10083270
void LegoCharacterManager::Init()
{
for (MxS32 i = 0; i < sizeOfArray(g_actorInfo); i++) {
g_actorInfo[i] = g_actorInfoInit[i];
}
}
// FUNCTION: LEGO1 0x100832a0
void LegoCharacterManager::ReleaseAllActors()
{
for (MxS32 i = 0; i < sizeOfArray(g_actorInfo); i++) {
LegoActorInfo* info = GetActorInfo(g_actorInfo[i].m_name);
if (info != NULL) {
LegoExtraActor* actor = info->m_actor;
if (actor != NULL && actor->IsA("LegoExtraActor")) {
LegoROI* roi = g_actorInfo[i].m_roi;
MxU32 refCount = GetRefCount(roi);
while (refCount != 0) {
ReleaseActor(roi);
refCount = GetRefCount(roi);
}
}
}
}
}
// FUNCTION: LEGO1 0x10083310
MxResult LegoCharacterManager::Write(LegoStorage* p_storage)
{
MxResult result = FAILURE;
for (MxS32 i = 0; i < sizeOfArray(g_actorInfo); i++) {
LegoActorInfo* info = &g_actorInfo[i];
if (p_storage->Write(&info->m_sound, sizeof(info->m_sound)) != SUCCESS) {
goto done;
}
if (p_storage->Write(&info->m_move, sizeof(info->m_move)) != SUCCESS) {
goto done;
}
if (p_storage->Write(&info->m_mood, sizeof(info->m_mood)) != SUCCESS) {
goto done;
}
if (p_storage->Write(
&info->m_parts[c_infohatPart].m_partNameIndex,
sizeof(info->m_parts[c_infohatPart].m_partNameIndex)
) != SUCCESS) {
goto done;
}
if (p_storage->Write(
&info->m_parts[c_infohatPart].m_nameIndex,
sizeof(info->m_parts[c_infohatPart].m_nameIndex)
) != SUCCESS) {
goto done;
}
if (p_storage->Write(
&info->m_parts[c_infogronPart].m_nameIndex,
sizeof(info->m_parts[c_infogronPart].m_nameIndex)
) != SUCCESS) {
goto done;
}
if (p_storage->Write(
&info->m_parts[c_armlftPart].m_nameIndex,
sizeof(info->m_parts[c_armlftPart].m_nameIndex)
) != SUCCESS) {
goto done;
}
if (p_storage->Write(&info->m_parts[c_armrtPart].m_nameIndex, sizeof(info->m_parts[c_armrtPart].m_nameIndex)) !=
SUCCESS) {
goto done;
}
if (p_storage->Write(
&info->m_parts[c_leglftPart].m_nameIndex,
sizeof(info->m_parts[c_leglftPart].m_nameIndex)
) != SUCCESS) {
goto done;
}
if (p_storage->Write(&info->m_parts[c_legrtPart].m_nameIndex, sizeof(info->m_parts[c_legrtPart].m_nameIndex)) !=
SUCCESS) {
goto done;
}
}
result = SUCCESS;
done:
return result;
}
// FUNCTION: LEGO1 0x100833f0
MxResult LegoCharacterManager::Read(LegoStorage* p_storage)
{
MxResult result = FAILURE;
for (MxS32 i = 0; i < sizeOfArray(g_actorInfo); i++) {
LegoActorInfo* info = &g_actorInfo[i];
if (p_storage->Read(&info->m_sound, sizeof(MxS32)) != SUCCESS) {
goto done;
}
if (p_storage->Read(&info->m_move, sizeof(MxS32)) != SUCCESS) {
goto done;
}
if (p_storage->Read(&info->m_mood, sizeof(MxU8)) != SUCCESS) {
goto done;
}
if (p_storage->Read(&info->m_parts[c_infohatPart].m_partNameIndex, sizeof(MxU8)) != SUCCESS) {
goto done;
}
if (p_storage->Read(&info->m_parts[c_infohatPart].m_nameIndex, sizeof(MxU8)) != SUCCESS) {
goto done;
}
if (p_storage->Read(&info->m_parts[c_infogronPart].m_nameIndex, sizeof(MxU8)) != SUCCESS) {
goto done;
}
if (p_storage->Read(&info->m_parts[c_armlftPart].m_nameIndex, sizeof(MxU8)) != SUCCESS) {
goto done;
}
if (p_storage->Read(&info->m_parts[c_armrtPart].m_nameIndex, sizeof(MxU8)) != SUCCESS) {
goto done;
}
if (p_storage->Read(&info->m_parts[c_leglftPart].m_nameIndex, sizeof(MxU8)) != SUCCESS) {
goto done;
}
if (p_storage->Read(&info->m_parts[c_legrtPart].m_nameIndex, sizeof(MxU8)) != SUCCESS) {
goto done;
}
}
result = SUCCESS;
done:
return result;
}
// FUNCTION: LEGO1 0x100834d0
// FUNCTION: BETA10 0x100742eb
const char* LegoCharacterManager::GetActorName(MxS32 p_index)
{
if (p_index < sizeOfArray(g_actorInfo)) {
return g_actorInfo[p_index].m_name;
}
else {
return NULL;
}
}
// FUNCTION: LEGO1 0x100834f0
// FUNCTION: BETA10 0x1007432a
MxU32 LegoCharacterManager::GetNumActors()
{
return sizeOfArray(g_actorInfo);
}
// FUNCTION: LEGO1 0x10083500
// FUNCTION: BETA10 0x10074345
LegoROI* LegoCharacterManager::GetActorROI(const char* p_name, MxBool p_createEntity)
{
LegoCharacter* character = NULL;
LegoCharacterMap::const_iterator it = m_characters->find(const_cast<char*>(p_name));
if (!(it == m_characters->end())) {
character = (*it).second;
character->AddRef();
}
if (character == NULL) {
LegoROI* roi = CreateActorROI(p_name);
if (roi != NULL) {
roi->SetVisibility(FALSE);
character = new LegoCharacter(roi);
char* name = new char[strlen(p_name) + 1];
if (name != NULL) {
strcpy(name, p_name);
(*m_characters)[name] = character;
VideoManager()->Get3DManager()->Add(*roi);
}
}
}
else {
VideoManager()->Get3DManager()->Remove(*character->m_roi);
VideoManager()->Get3DManager()->Add(*character->m_roi);
}
if (character != NULL) {
if (p_createEntity && character->m_roi->GetEntity() == NULL &&
!Extension<ThirdPersonCameraExt>::Call(TP::IsClonedCharacter, p_name).value_or(FALSE) &&
!Extension<MultiplayerExt>::Call(MP::IsClonedCharacter, p_name).value_or(FALSE)) {
LegoExtraActor* actor = new LegoExtraActor();
actor->SetROI(character->m_roi, FALSE, FALSE);
actor->SetType(LegoEntity::e_actor);
actor->SetFlag(LegoEntity::c_managerOwned);
GetActorInfo(p_name)->m_actor = actor;
}
return character->m_roi;
}
else {
return NULL;
}
}
// FUNCTION: LEGO1 0x10083b20
// FUNCTION: BETA10 0x10074608
MxBool LegoCharacterManager::Exists(const char* p_name)
{
LegoCharacterMap::iterator it = m_characters->find(const_cast<char*>(p_name));
if (it != m_characters->end()) {
return TRUE;
}
return FALSE;
}
// FUNCTION: LEGO1 0x10083bc0
MxU32 LegoCharacterManager::GetRefCount(LegoROI* p_roi)
{
LegoCharacterMap::iterator it;
for (it = m_characters->begin(); it != m_characters->end(); it++) {
LegoCharacter* character = (*it).second;
LegoROI* roi = character->m_roi;
if (roi == p_roi) {
return character->m_refCount;
}
}
return 0;
}
// FUNCTION: LEGO1 0x10083c30
// FUNCTION: BETA10 0x10074701
void LegoCharacterManager::ReleaseActor(const char* p_name)
{
LegoCharacter* character = NULL;
LegoCharacterMap::iterator it = m_characters->find(const_cast<char*>(p_name));
if (it != m_characters->end()) {
character = (*it).second;
if (character->RemoveRef() == 0) {
LegoActorInfo* info = GetActorInfo(p_name);
LegoEntity* entity = character->m_roi->GetEntity();
if (entity != NULL) {
entity->SetROI(NULL, FALSE, FALSE);
}
RemoveROI(character->m_roi);
delete[] (*it).first;
delete (*it).second;
m_characters->erase(it);
if (info != NULL) {
if (info->m_actor != NULL) {
info->m_actor->ClearFlag(LegoEntity::c_managerOwned);
delete info->m_actor;
}
else if (entity != NULL && entity->GetFlagsIsSet(LegoEntity::c_managerOwned)) {
entity->ClearFlag(LegoEntity::c_managerOwned);
delete entity;
}
info->m_roi = NULL;
info->m_actor = NULL;
}
}
}
}
// FUNCTION: LEGO1 0x10083db0
void LegoCharacterManager::ReleaseActor(LegoROI* p_roi)
{
LegoCharacter* character = NULL;
LegoCharacterMap::iterator it;
for (it = m_characters->begin(); it != m_characters->end(); it++) {
character = (*it).second;
if (character->m_roi == p_roi) {
if (character->RemoveRef() == 0) {
LegoActorInfo* info = GetActorInfo(character->m_roi->GetName());
LegoEntity* entity = character->m_roi->GetEntity();
if (entity != NULL) {
entity->SetROI(NULL, FALSE, FALSE);
}
RemoveROI(character->m_roi);
delete[] (*it).first;
delete (*it).second;
m_characters->erase(it);
if (info != NULL) {
if (info->m_actor != NULL) {
info->m_actor->ClearFlag(LegoEntity::c_managerOwned);
delete info->m_actor;
}
else if (entity != NULL && entity->GetFlagsIsSet(LegoEntity::c_managerOwned)) {
entity->ClearFlag(LegoEntity::c_managerOwned);
delete entity;
}
info->m_roi = NULL;
info->m_actor = NULL;
}
}
return;
}
}
}
// FUNCTION: LEGO1 0x10083f10
void LegoCharacterManager::ReleaseAutoROI(LegoROI* p_roi)
{
LegoCharacter* character = NULL;
LegoCharacterMap::iterator it;
for (it = m_characters->begin(); it != m_characters->end(); it++) {
character = (*it).second;
if (character->m_roi == p_roi) {
if (character->RemoveRef() == 0) {
LegoEntity* entity = character->m_roi->GetEntity();
if (entity != NULL) {
entity->SetROI(NULL, FALSE, FALSE);
}
RemoveROI(character->m_roi);
delete[] (*it).first;
delete (*it).second;
m_characters->erase(it);
if (entity != NULL && entity->GetFlagsIsSet(LegoEntity::c_managerOwned)) {
entity->ClearFlag(LegoEntity::c_managerOwned);
delete entity;
}
}
return;
}
}
}
// FUNCTION: LEGO1 0x10084010
// FUNCTION: BETA10 0x10074e20
void LegoCharacterManager::RemoveROI(LegoROI* p_roi)
{
VideoManager()->Get3DManager()->Remove(*p_roi);
}
// FUNCTION: LEGO1 0x10084030
// FUNCTION: BETA10 0x10074e4f
LegoROI* LegoCharacterManager::CreateActorROI(const char* p_key)
{
MxBool success = FALSE;
LegoROI* roi = NULL;
BoundingSphere boundingSphere;
BoundingBox boundingBox;
MxMatrix mat;
CompoundObject* comp;
MxS32 i;
Tgl::Renderer* renderer = VideoManager()->GetRenderer();
ViewLODListManager* lodManager = GetViewLODListManager();
LegoTextureContainer* textureContainer = TextureContainer();
LegoActorInfo* info = GetActorInfo(p_key);
if (info == NULL) {
goto done;
}
if (!SDL_strcasecmp(p_key, "pep")) {
LegoActorInfo* pepper = GetActorInfo("pepper");
info->m_sound = pepper->m_sound;
info->m_move = pepper->m_move;
info->m_mood = pepper->m_mood;
for (i = 0; i < sizeOfArray(info->m_parts); i++) {
info->m_parts[i] = pepper->m_parts[i];
}
}
roi = new LegoROI(renderer);
roi->SetName(p_key);
boundingSphere.Center()[0] = g_actorLODs[c_topLOD].m_boundingSphere[0];
boundingSphere.Center()[1] = g_actorLODs[c_topLOD].m_boundingSphere[1];
boundingSphere.Center()[2] = g_actorLODs[c_topLOD].m_boundingSphere[2];
boundingSphere.Radius() = g_actorLODs[c_topLOD].m_boundingSphere[3];
roi->SetBoundingSphere(boundingSphere);
boundingBox.Min()[0] = g_actorLODs[c_topLOD].m_boundingBox[0];
boundingBox.Min()[1] = g_actorLODs[c_topLOD].m_boundingBox[1];
boundingBox.Min()[2] = g_actorLODs[c_topLOD].m_boundingBox[2];
boundingBox.Max()[0] = g_actorLODs[c_topLOD].m_boundingBox[3];
boundingBox.Max()[1] = g_actorLODs[c_topLOD].m_boundingBox[4];
boundingBox.Max()[2] = g_actorLODs[c_topLOD].m_boundingBox[5];
roi->SetBoundingBox(boundingBox);
comp = new CompoundObject();
roi->SetComp(comp);
for (i = 0; i < sizeOfArray(g_actorLODs) - 1; i++) {
char lodName[256];
LegoActorInfo::Part& part = info->m_parts[i];
const char* parentName;
if (i == 0 || i == 1) {
parentName = part.m_partName[part.m_partNameIndices[part.m_partNameIndex]];
}
else {
parentName = g_actorLODs[i + 1].m_parentName;
}
ViewLODList* lodList = lodManager->Lookup(parentName);
MxS32 lodSize = lodList->Size();
sprintf(lodName, "%s%d", p_key, i);
ViewLODList* dupLodList = lodManager->Create(lodName, lodSize);
for (MxS32 j = 0; j < lodSize; j++) {
LegoLOD* lod = (LegoLOD*) (*lodList)[j];
LegoLOD* clone = lod->Clone(renderer);
dupLodList->PushBack(clone);
}
lodList->Release();
lodList = dupLodList;
LegoROI* childROI = new LegoROI(renderer, lodList);
lodList->Release();
childROI->SetName(g_actorLODs[i + 1].m_name);
childROI->SetParentROI(roi);
BoundingSphere childBoundingSphere;
childBoundingSphere.Center()[0] = g_actorLODs[i + 1].m_boundingSphere[0];
childBoundingSphere.Center()[1] = g_actorLODs[i + 1].m_boundingSphere[1];
childBoundingSphere.Center()[2] = g_actorLODs[i + 1].m_boundingSphere[2];
childBoundingSphere.Radius() = g_actorLODs[i + 1].m_boundingSphere[3];
childROI->SetBoundingSphere(childBoundingSphere);
BoundingBox childBoundingBox;
childBoundingBox.Min()[0] = g_actorLODs[i + 1].m_boundingBox[0];
childBoundingBox.Min()[1] = g_actorLODs[i + 1].m_boundingBox[1];
childBoundingBox.Min()[2] = g_actorLODs[i + 1].m_boundingBox[2];
childBoundingBox.Max()[0] = g_actorLODs[i + 1].m_boundingBox[3];
childBoundingBox.Max()[1] = g_actorLODs[i + 1].m_boundingBox[4];
childBoundingBox.Max()[2] = g_actorLODs[i + 1].m_boundingBox[5];
childROI->SetBoundingBox(childBoundingBox);
CalcLocalTransform(
Mx3DPointFloat(g_actorLODs[i + 1].m_position),
Mx3DPointFloat(g_actorLODs[i + 1].m_direction),
Mx3DPointFloat(g_actorLODs[i + 1].m_up),
mat
);
childROI->WrappedSetLocal2WorldWithWorldDataUpdate(mat);
if (g_actorLODs[i + 1].m_flags & LegoActorLOD::c_useTexture &&
(i != 0 || part.m_partNameIndices[part.m_partNameIndex] != 0)) {
LegoTextureInfo* textureInfo = textureContainer->Get(part.m_names[part.m_nameIndices[part.m_nameIndex]]);
if (textureInfo != NULL) {
childROI->SetTextureInfo(textureInfo);
childROI->SetLodColor(1.0F, 1.0F, 1.0F, 0.0F);
}
}
else if (g_actorLODs[i + 1].m_flags & LegoActorLOD::c_useColor || (i == 0 && part.m_partNameIndices[part.m_partNameIndex] == 0)) {
LegoFloat red, green, blue, alpha;
childROI->GetRGBAColor(part.m_names[part.m_nameIndices[part.m_nameIndex]], red, green, blue, alpha);
childROI->SetLodColor(red, green, blue, alpha);
}
comp->push_back(childROI);
}
CalcLocalTransform(
Mx3DPointFloat(g_actorLODs[c_topLOD].m_position),
Mx3DPointFloat(g_actorLODs[c_topLOD].m_direction),
Mx3DPointFloat(g_actorLODs[c_topLOD].m_up),
mat
);
roi->WrappedSetLocal2WorldWithWorldDataUpdate(mat);
info->m_roi = roi;
success = TRUE;
done:
if (!success && roi != NULL) {
delete roi;
roi = NULL;
}
return roi;
}
// FUNCTION: LEGO1 0x100849a0
// FUNCTION: BETA10 0x10075b51
MxBool LegoCharacterManager::SetHeadTexture(LegoROI* p_roi, LegoTextureInfo* p_texture)
{
LegoResult result = SUCCESS;
LegoROI* head = FindChildROI(p_roi, g_actorLODs[c_headLOD].m_name);
if (head != NULL) {
char lodName[256];
ViewLODList* lodList = GetViewLODListManager()->Lookup(g_actorLODs[c_headLOD].m_parentName);
assert(lodList);
MxS32 lodSize = lodList->Size();
sprintf(lodName, "%s%s%d", p_roi->GetName(), "head", g_headTextureCounter++);
ViewLODList* dupLodList = GetViewLODListManager()->Create(lodName, lodSize);
assert(dupLodList);
Tgl::Renderer* renderer = VideoManager()->GetRenderer();
if (p_texture == NULL) {
LegoActorInfo* info = GetActorInfo(p_roi->GetName());
assert(info);
LegoActorInfo::Part& part = info->m_parts[c_headPart];
p_texture = TextureContainer()->Get(part.m_names[part.m_nameIndices[part.m_nameIndex]]);
assert(p_texture);
}
for (MxS32 i = 0; i < lodSize; i++) {
LegoLOD* lod = (LegoLOD*) (*lodList)[i];
LegoLOD* clone = lod->Clone(renderer);
if (p_texture != NULL) {
clone->UpdateTextureInfo(p_texture);
}
dupLodList->PushBack(clone);
}
lodList->Release();
lodList = dupLodList;
if (head->GetLodLevel() >= 0) {
VideoManager()->Get3DManager()->GetLego3DView()->GetViewManager()->RemoveROIDetailFromScene(head);
}
head->SetLODList(lodList);
lodList->Release();
}
return head != NULL;
}
// FUNCTION: LEGO1 0x10084c00
MxBool LegoCharacterManager::IsActor(const char* p_name)
{
for (MxU32 i = 0; i < sizeOfArray(g_actorInfo); i++) {
if (!SDL_strcasecmp(g_actorInfo[i].m_name, p_name)) {
return TRUE;
}
}
return FALSE;
}
// FUNCTION: LEGO1 0x10084c40
LegoExtraActor* LegoCharacterManager::GetExtraActor(const char* p_name)
{
LegoActorInfo* info = GetActorInfo(p_name);
if (info != NULL) {
return info->m_actor;
}
return NULL;
}
// FUNCTION: LEGO1 0x10084c60
// FUNCTION: BETA10 0x10075ede
LegoActorInfo* LegoCharacterManager::GetActorInfo(const char* p_name)
{
MxU32 i;
for (i = 0; i < sizeOfArray(g_actorInfo); i++) {
if (!SDL_strcasecmp(g_actorInfo[i].m_name, p_name)) {
break;
}
}
if (i < sizeOfArray(g_actorInfo)) {
return &g_actorInfo[i];
}
else {
return NULL;
}
}
// FUNCTION: LEGO1 0x10084cb0
// FUNCTION: BETA10 0x10075f66
LegoActorInfo* LegoCharacterManager::GetActorInfo(LegoROI* p_roi)
{
MxU32 i;
for (i = 0; i < sizeOfArray(g_actorInfo); i++) {
if (g_actorInfo[i].m_roi == p_roi) {
break;
}
}
if (i < sizeOfArray(g_actorInfo)) {
return &g_actorInfo[i];
}
else {
return NULL;
}
}
// FUNCTION: LEGO1 0x10084cf0
// FUNCTION: BETA10 0x10075fe2
LegoROI* LegoCharacterManager::FindChildROI(LegoROI* p_roi, const char* p_name)
{
#ifdef COMPAT_MODE
CompoundObject::const_iterator it;
#else
CompoundObject::iterator it;
#endif
const CompoundObject* comp = p_roi->GetComp();
for (it = comp->begin(); it != comp->end(); it++) {
LegoROI* roi = (LegoROI*) *it;
if (!SDL_strcasecmp(p_name, roi->GetName())) {
return roi;
}
}
return NULL;
}
// FUNCTION: LEGO1 0x10084d50
// FUNCTION: BETA10 0x10076223
MxBool LegoCharacterManager::SwitchColor(LegoROI* p_roi, LegoROI* p_targetROI)
{
MxS32 numParts = 10;
const char* targetName = p_targetROI->GetName();
MxS32 partIndex;
for (partIndex = 0; partIndex < numParts; partIndex++) {
if (!strcmp(targetName, g_actorLODs[partIndex + 1].m_name)) {
break;
}
}
assert(partIndex < numParts);
MxBool findChild = TRUE;
if (partIndex == c_clawlftPart) {
partIndex = c_armlftPart;
}
else if (partIndex == c_clawrtPart) {
partIndex = c_armrtPart;
}
else if (partIndex == c_headPart) {
partIndex = c_infohatPart;
}
else if (partIndex == c_bodyPart) {
partIndex = c_infogronPart;
}
else {
findChild = FALSE;
}
if (!(g_actorLODs[partIndex + 1].m_flags & LegoActorLOD::c_useColor)) {
return FALSE;
}
LegoActorInfo* info = GetActorInfo(p_roi->GetName());
if (info == NULL) {
return FALSE;
}
if (findChild) {
p_targetROI = FindChildROI(p_roi, g_actorLODs[partIndex + 1].m_name);
}
LegoActorInfo::Part& part = info->m_parts[partIndex];
part.m_nameIndex++;
if (part.m_nameIndices[part.m_nameIndex] == 0xff) {
part.m_nameIndex = 0;
}
LegoFloat red, green, blue, alpha;
LegoROI::GetRGBAColor(part.m_names[part.m_nameIndices[part.m_nameIndex]], red, green, blue, alpha);
p_targetROI->SetLodColor(red, green, blue, alpha);
return TRUE;
}
// FUNCTION: LEGO1 0x10084ec0
MxBool LegoCharacterManager::SwitchVariant(LegoROI* p_roi)
{
LegoActorInfo* info = GetActorInfo(p_roi->GetName());
if (info == NULL) {
return FALSE;
}
LegoActorInfo::Part& part = info->m_parts[c_infohatPart];
part.m_partNameIndex++;
MxU8 partNameIndex = part.m_partNameIndices[part.m_partNameIndex];
if (partNameIndex == 0xff) {
part.m_partNameIndex = 0;
partNameIndex = part.m_partNameIndices[part.m_partNameIndex];
}
LegoROI* childROI = FindChildROI(p_roi, g_actorLODs[c_infohatLOD].m_name);
if (childROI != NULL) {
char lodName[256];
ViewLODList* lodList = GetViewLODListManager()->Lookup(part.m_partName[partNameIndex]);
MxS32 lodSize = lodList->Size();
sprintf(lodName, "%s%d", p_roi->GetName(), g_infohatVariantCounter++);
ViewLODList* dupLodList = GetViewLODListManager()->Create(lodName, lodSize);
Tgl::Renderer* renderer = VideoManager()->GetRenderer();
LegoFloat red, green, blue, alpha;
LegoROI::GetRGBAColor(part.m_names[part.m_nameIndices[part.m_nameIndex]], red, green, blue, alpha);
for (MxS32 i = 0; i < lodSize; i++) {
LegoLOD* lod = (LegoLOD*) (*lodList)[i];
LegoLOD* clone = lod->Clone(renderer);
clone->SetColor(red, green, blue, alpha);
dupLodList->PushBack(clone);
}
lodList->Release();
lodList = dupLodList;
if (childROI->GetLodLevel() >= 0) {
VideoManager()->Get3DManager()->GetLego3DView()->GetViewManager()->RemoveROIDetailFromScene(childROI);
}
childROI->SetLODList(lodList);
lodList->Release();
}
return TRUE;
}
// FUNCTION: LEGO1 0x10085090
// FUNCTION: BETA10 0x100766f6
MxBool LegoCharacterManager::SwitchSound(LegoROI* p_roi)
{
MxBool result = FALSE;
LegoActorInfo* info = GetActorInfo(p_roi);
if (info != NULL) {
info->m_sound++;
if (info->m_sound >= g_maxSound) {
info->m_sound = 0;
}
result = TRUE;
}
return result;
}
// FUNCTION: LEGO1 0x100850c0
// FUNCTION: BETA10 0x10076754
MxBool LegoCharacterManager::SwitchMove(LegoROI* p_roi)
{
MxBool result = FALSE;
LegoActorInfo* info = GetActorInfo(p_roi);
if (info != NULL) {
info->m_move++;
if (info->m_move >= g_maxMove) {
info->m_move = 0;
}
result = TRUE;
}
return result;
}
// FUNCTION: LEGO1 0x100850f0
// FUNCTION: BETA10 0x100767b2
MxBool LegoCharacterManager::SwitchMood(LegoROI* p_roi)
{
MxBool result = FALSE;
LegoActorInfo* info = GetActorInfo(p_roi);
if (info != NULL) {
info->m_mood++;
if (info->m_mood > 3) {
info->m_mood = 0;
}
result = TRUE;
}
return result;
}
// FUNCTION: LEGO1 0x10085120
// FUNCTION: BETA10 0x1007680c
MxU32 LegoCharacterManager::GetAnimationId(LegoROI* p_roi)
{
LegoActorInfo* info = GetActorInfo(p_roi);
if (info != NULL) {
return info->m_move + g_characterAnimationId;
}
else {
return 0;
}
}
// FUNCTION: LEGO1 0x10085140
// FUNCTION: BETA10 0x10076855
MxU32 LegoCharacterManager::GetSoundId(LegoROI* p_roi, MxBool p_basedOnMood)
{
LegoActorInfo* info = GetActorInfo(p_roi);
if (p_basedOnMood) {
return info->m_mood + g_characterSoundIdMoodOffset;
}
if (info != NULL) {
return info->m_sound + g_characterSoundIdOffset;
}
else {
return 0;
}
}
// FUNCTION: LEGO1 0x10085180
// FUNCTION: BETA10 0x100768c5
MxU8 LegoCharacterManager::GetMood(LegoROI* p_roi)
{
LegoActorInfo* info = GetActorInfo(p_roi);
if (info != NULL) {
return info->m_mood;
}
else {
return 0;
}
}
// FUNCTION: LEGO1 0x100851a0
void LegoCharacterManager::SetCustomizeAnimFile(const char* p_value)
{
if (g_customizeAnimFile != NULL) {
delete[] g_customizeAnimFile;
}
if (p_value != NULL) {
g_customizeAnimFile = new char[strlen(p_value) + 1];
if (g_customizeAnimFile != NULL) {
strcpy(g_customizeAnimFile, p_value);
}
}
else {
g_customizeAnimFile = NULL;
}
}
// FUNCTION: LEGO1 0x10085210
// FUNCTION: BETA10 0x10076995
LegoROI* LegoCharacterManager::CreateAutoROI(const char* p_name, const char* p_lodName, MxBool p_createEntity)
{
LegoROI* roi = NULL;
MxMatrix mat;
Tgl::Renderer* renderer = VideoManager()->GetRenderer();
ViewLODListManager* lodManager = GetViewLODListManager();
LegoTextureContainer* textureContainer = TextureContainer();
ViewLODList* lodList = lodManager->Lookup(p_lodName);
if (lodList == NULL || lodList->Size() == 0) {
return NULL;
}
roi = new LegoROI(renderer, lodList);
const char* name;
char buf[20];
if (p_name != NULL) {
name = p_name;
}
else {
sprintf(buf, "autoROI_%d", g_autoRoiCounter++);
name = buf;
}
roi->SetName(name);
lodList->Release();
if (roi != NULL && UpdateBoundingSphereAndBox(roi) != SUCCESS) {
delete roi;
roi = NULL;
}
if (roi != NULL) {
roi->SetVisibility(FALSE);
LegoCharacter* character = new LegoCharacter(roi);
char* key = new char[strlen(name) + 1];
if (key != NULL) {
strcpy(key, name);
(*m_characters)[key] = character;
VideoManager()->Get3DManager()->Add(*roi);
if (p_createEntity && roi->GetEntity() == NULL) {
LegoEntity* entity = new LegoEntity();
entity->SetROI(roi, FALSE, FALSE);
entity->SetType(LegoEntity::e_autoROI);
entity->SetFlag(LegoEntity::c_managerOwned);
}
}
}
return roi;
}
// FUNCTION: LEGO1 0x10085870
MxResult LegoCharacterManager::UpdateBoundingSphereAndBox(LegoROI* p_roi)
{
MxResult result = FAILURE;
BoundingSphere boundingSphere;
BoundingBox boundingBox;
const Tgl::MeshBuilder* meshBuilder = ((ViewLOD*) p_roi->GetLOD(0))->GetMeshBuilder();
if (meshBuilder != NULL) {
float min[3], max[3];
FILLVEC3(min, 88888.0);
FILLVEC3(max, -88888.0);
meshBuilder->GetBoundingBox(min, max);
float center[3];
center[0] = (min[0] + max[0]) / 2.0f;
center[1] = (min[1] + max[1]) / 2.0f;
center[2] = (min[2] + max[2]) / 2.0f;
SET3(boundingSphere.Center(), center);
float radius[3];
VMV3(radius, max, min);
boundingSphere.Radius() = sqrt(NORMSQRD3(radius)) / 2.0;
p_roi->SetBoundingSphere(boundingSphere);
SET3(boundingBox.Min(), min);
SET3(boundingBox.Max(), max);
p_roi->SetBoundingBox(boundingBox);
p_roi->WrappedUpdateWorldData();
result = SUCCESS;
}
return result;
}
// FUNCTION: LEGO1 0x10085a80
LegoROI* LegoCharacterManager::FUN_10085a80(const char* p_name, const char* p_lodName, MxBool p_createEntity)
{
return CreateAutoROI(p_name, p_lodName, p_createEntity);
}
// FUNCTION: LEGO1 0x10085aa0
// FUNCTION: BETA10 0x1007703d
CustomizeAnimFileVariable::CustomizeAnimFileVariable(const char* p_key)
{
m_key = p_key;
m_key.ToUpperCase();
}
// FUNCTION: LEGO1 0x10085b50
// FUNCTION: BETA10 0x100770c8
void CustomizeAnimFileVariable::SetValue(const char* p_value)
{
// STRING: LEGO1 0x100fc4f4
if (strcmp(m_key.GetData(), "CUSTOMIZE_ANIM_FILE") == 0) {
CharacterManager()->SetCustomizeAnimFile(p_value);
PlantManager()->SetCustomizeAnimFile(p_value);
BuildingManager()->SetCustomizeAnimFile(p_value);
}
}