mirror of
https://github.com/isledecomp/isle-portable.git
synced 2026-05-02 02:23:56 +00:00
Introduces a third person camera system with orbit camera, input handling (mouse/keyboard/touch/gamepad), display actor cloning, and camera-relative movement. Includes shared character utilities (animator, cloner, customizer) and an IExtraAnimHandler interface for optional animation extensions. Also includes generic base game fixes and extension system improvements.
371 lines
9.3 KiB
C++
371 lines
9.3 KiB
C++
#include "extensions/common/charactercustomizer.h"
|
|
|
|
#include "3dmanager/lego3dmanager.h"
|
|
#include "3dmanager/lego3dview.h"
|
|
#include "extensions/common/charactercloner.h"
|
|
#include "extensions/common/constants.h"
|
|
#include "extensions/common/customizestate.h"
|
|
#include "legoactor.h"
|
|
#include "legoactors.h"
|
|
#include "legocharactermanager.h"
|
|
#include "legogamestate.h"
|
|
#include "legovideomanager.h"
|
|
#include "misc.h"
|
|
#include "mxatom.h"
|
|
#include "mxdsaction.h"
|
|
#include "mxmisc.h"
|
|
#include "roi/legolod.h"
|
|
#include "roi/legoroi.h"
|
|
#include "viewmanager/viewlodlist.h"
|
|
#include "viewmanager/viewmanager.h"
|
|
|
|
#include <SDL3/SDL_stdinc.h>
|
|
|
|
using namespace Extensions::Common;
|
|
|
|
static const MxU32 g_characterSoundIdOffset = 50;
|
|
static const MxU32 g_characterSoundIdMoodOffset = 66;
|
|
static const MxU32 g_characterAnimationId = 10;
|
|
static const MxU32 g_maxSound = 9;
|
|
static const MxU32 g_maxMove = 4;
|
|
|
|
static constexpr int COLORABLE_PARTS_COUNT = 10;
|
|
static uint32_t s_variantCounter = 10000;
|
|
|
|
// MARK: Private helpers
|
|
|
|
LegoROI* CharacterCustomizer::FindChildROI(LegoROI* p_rootROI, const char* p_name)
|
|
{
|
|
const CompoundObject* comp = p_rootROI->GetComp();
|
|
|
|
for (CompoundObject::const_iterator it = comp->begin(); it != comp->end(); it++) {
|
|
LegoROI* roi = (LegoROI*) *it;
|
|
|
|
if (!SDL_strcasecmp(p_name, roi->GetName())) {
|
|
return roi;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// MARK: Public API
|
|
|
|
uint8_t CharacterCustomizer::ResolveActorInfoIndex(uint8_t p_displayActorIndex)
|
|
{
|
|
return p_displayActorIndex;
|
|
}
|
|
|
|
bool CharacterCustomizer::SwitchColor(
|
|
LegoROI* p_rootROI,
|
|
uint8_t p_actorInfoIndex,
|
|
CustomizeState& p_state,
|
|
int p_partIndex
|
|
)
|
|
{
|
|
if (p_partIndex < 0 || p_partIndex >= COLORABLE_PARTS_COUNT) {
|
|
return false;
|
|
}
|
|
|
|
// Remap derived parts to independent parts
|
|
if (p_partIndex == c_clawlftPart) {
|
|
p_partIndex = c_armlftPart;
|
|
}
|
|
else if (p_partIndex == c_clawrtPart) {
|
|
p_partIndex = c_armrtPart;
|
|
}
|
|
else if (p_partIndex == c_headPart) {
|
|
p_partIndex = c_infohatPart;
|
|
}
|
|
else if (p_partIndex == c_bodyPart) {
|
|
p_partIndex = c_infogronPart;
|
|
}
|
|
|
|
if (!(g_actorLODs[p_partIndex + 1].m_flags & LegoActorLOD::c_useColor)) {
|
|
return false;
|
|
}
|
|
|
|
if (p_actorInfoIndex >= sizeOfArray(g_actorInfoInit)) {
|
|
return false;
|
|
}
|
|
|
|
const LegoActorInfo::Part& part = g_actorInfoInit[p_actorInfoIndex].m_parts[p_partIndex];
|
|
|
|
p_state.colorIndices[p_partIndex]++;
|
|
if (part.m_nameIndices[p_state.colorIndices[p_partIndex]] == 0xff) {
|
|
p_state.colorIndices[p_partIndex] = 0;
|
|
}
|
|
|
|
if (!p_rootROI) {
|
|
return true;
|
|
}
|
|
|
|
LegoROI* targetROI = FindChildROI(p_rootROI, g_actorLODs[p_partIndex + 1].m_name);
|
|
if (!targetROI) {
|
|
return false;
|
|
}
|
|
|
|
LegoFloat red, green, blue, alpha;
|
|
LegoROI::GetRGBAColor(part.m_names[part.m_nameIndices[p_state.colorIndices[p_partIndex]]], red, green, blue, alpha);
|
|
targetROI->SetLodColor(red, green, blue, alpha);
|
|
return true;
|
|
}
|
|
|
|
bool CharacterCustomizer::SwitchVariant(LegoROI* p_rootROI, uint8_t p_actorInfoIndex, CustomizeState& p_state)
|
|
{
|
|
if (p_actorInfoIndex >= sizeOfArray(g_actorInfoInit)) {
|
|
return false;
|
|
}
|
|
|
|
const LegoActorInfo::Part& part = g_actorInfoInit[p_actorInfoIndex].m_parts[c_infohatPart];
|
|
|
|
p_state.hatVariantIndex++;
|
|
if (part.m_partNameIndices[p_state.hatVariantIndex] == 0xff) {
|
|
p_state.hatVariantIndex = 0;
|
|
}
|
|
|
|
if (!p_rootROI) {
|
|
return true;
|
|
}
|
|
|
|
ApplyHatVariant(p_rootROI, p_actorInfoIndex, p_state);
|
|
return true;
|
|
}
|
|
|
|
bool CharacterCustomizer::SwitchSound(CustomizeState& p_state)
|
|
{
|
|
p_state.sound++;
|
|
if (p_state.sound >= g_maxSound) {
|
|
p_state.sound = 0;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CharacterCustomizer::SwitchMove(CustomizeState& p_state)
|
|
{
|
|
p_state.move++;
|
|
if (p_state.move >= g_maxMove) {
|
|
p_state.move = 0;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CharacterCustomizer::SwitchMood(CustomizeState& p_state)
|
|
{
|
|
p_state.mood++;
|
|
if (p_state.mood > 3) {
|
|
p_state.mood = 0;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CharacterCustomizer::ApplyChange(
|
|
LegoROI* p_rootROI,
|
|
uint8_t p_actorInfoIndex,
|
|
CustomizeState& p_state,
|
|
uint8_t p_changeType,
|
|
uint8_t p_partIndex
|
|
)
|
|
{
|
|
switch (p_changeType) {
|
|
case CHANGE_VARIANT:
|
|
SwitchVariant(p_rootROI, p_actorInfoIndex, p_state);
|
|
break;
|
|
case CHANGE_SOUND:
|
|
SwitchSound(p_state);
|
|
break;
|
|
case CHANGE_MOVE:
|
|
SwitchMove(p_state);
|
|
break;
|
|
case CHANGE_COLOR:
|
|
SwitchColor(p_rootROI, p_actorInfoIndex, p_state, p_partIndex);
|
|
break;
|
|
case CHANGE_MOOD:
|
|
SwitchMood(p_state);
|
|
break;
|
|
}
|
|
}
|
|
|
|
int CharacterCustomizer::MapClickedPartIndex(const char* p_partName)
|
|
{
|
|
for (int i = 0; i < COLORABLE_PARTS_COUNT; i++) {
|
|
if (!SDL_strcasecmp(p_partName, g_actorLODs[i + 1].m_name)) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void CharacterCustomizer::ApplyFullState(LegoROI* p_rootROI, uint8_t p_actorInfoIndex, const CustomizeState& p_state)
|
|
{
|
|
if (p_actorInfoIndex >= sizeOfArray(g_actorInfoInit)) {
|
|
return;
|
|
}
|
|
|
|
// Apply colors for the 6 independent colorable parts
|
|
static const int colorableParts[] =
|
|
{c_infohatPart, c_infogronPart, c_armlftPart, c_armrtPart, c_leglftPart, c_legrtPart};
|
|
|
|
for (int i = 0; i < (int) sizeOfArray(colorableParts); i++) {
|
|
int partIndex = colorableParts[i];
|
|
|
|
if (!(g_actorLODs[partIndex + 1].m_flags & LegoActorLOD::c_useColor)) {
|
|
continue;
|
|
}
|
|
|
|
LegoROI* childROI = FindChildROI(p_rootROI, g_actorLODs[partIndex + 1].m_name);
|
|
if (!childROI) {
|
|
continue;
|
|
}
|
|
|
|
const LegoActorInfo::Part& part = g_actorInfoInit[p_actorInfoIndex].m_parts[partIndex];
|
|
|
|
LegoFloat red, green, blue, alpha;
|
|
LegoROI::GetRGBAColor(
|
|
part.m_names[part.m_nameIndices[p_state.colorIndices[partIndex]]],
|
|
red,
|
|
green,
|
|
blue,
|
|
alpha
|
|
);
|
|
childROI->SetLodColor(red, green, blue, alpha);
|
|
}
|
|
|
|
// Apply hat variant if different from default
|
|
const LegoActorInfo::Part& hatPart = g_actorInfoInit[p_actorInfoIndex].m_parts[c_infohatPart];
|
|
if (p_state.hatVariantIndex != hatPart.m_partNameIndex) {
|
|
ApplyHatVariant(p_rootROI, p_actorInfoIndex, p_state);
|
|
}
|
|
}
|
|
|
|
void CharacterCustomizer::ApplyHatVariant(LegoROI* p_rootROI, uint8_t p_actorInfoIndex, const CustomizeState& p_state)
|
|
{
|
|
if (p_actorInfoIndex >= sizeOfArray(g_actorInfoInit)) {
|
|
return;
|
|
}
|
|
|
|
const LegoActorInfo::Part& part = g_actorInfoInit[p_actorInfoIndex].m_parts[c_infohatPart];
|
|
|
|
MxU8 partNameIndex = part.m_partNameIndices[p_state.hatVariantIndex];
|
|
if (partNameIndex == 0xff) {
|
|
return;
|
|
}
|
|
|
|
LegoROI* childROI = FindChildROI(p_rootROI, g_actorLODs[c_infohatLOD].m_name);
|
|
|
|
if (childROI != NULL) {
|
|
char lodName[256];
|
|
|
|
ViewLODList* lodList = GetViewLODListManager()->Lookup(part.m_partName[partNameIndex]);
|
|
MxS32 lodSize = lodList->Size();
|
|
SDL_snprintf(lodName, sizeof(lodName), "%s_cv%u", p_rootROI->GetName(), s_variantCounter++);
|
|
ViewLODList* dupLodList = GetViewLODListManager()->Create(lodName, lodSize);
|
|
|
|
Tgl::Renderer* renderer = VideoManager()->GetRenderer();
|
|
LegoFloat red, green, blue, alpha;
|
|
LegoROI::GetRGBAColor(
|
|
part.m_names[part.m_nameIndices[p_state.colorIndices[c_infohatPart]]],
|
|
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();
|
|
}
|
|
}
|
|
|
|
void CharacterCustomizer::PlayClickSound(LegoROI* p_roi, const CustomizeState& p_state, bool p_basedOnMood)
|
|
{
|
|
MxU32 objectId =
|
|
p_basedOnMood ? (p_state.mood + g_characterSoundIdMoodOffset) : (p_state.sound + g_characterSoundIdOffset);
|
|
|
|
if (objectId) {
|
|
MxDSAction action;
|
|
action.SetAtomId(MxAtomId(LegoCharacterManager::GetCustomizeAnimFile(), e_lowerCase2));
|
|
action.SetObjectId(objectId);
|
|
|
|
const char* name = p_roi->GetName();
|
|
action.AppendExtra(SDL_strlen(name) + 1, name);
|
|
Start(&action);
|
|
}
|
|
}
|
|
|
|
MxU32 CharacterCustomizer::PlayClickAnimation(LegoROI* p_roi, const CustomizeState& p_state)
|
|
{
|
|
MxU32 objectId = p_state.move + g_characterAnimationId;
|
|
|
|
MxDSAction action;
|
|
action.SetAtomId(MxAtomId(LegoCharacterManager::GetCustomizeAnimFile(), e_lowerCase2));
|
|
action.SetObjectId(objectId);
|
|
|
|
char extra[1024];
|
|
SDL_snprintf(extra, sizeof(extra), "SUBST:actor_01:%s", p_roi->GetName());
|
|
action.AppendExtra(SDL_strlen(extra) + 1, extra);
|
|
StartActionIfInitialized(action);
|
|
|
|
return objectId;
|
|
}
|
|
|
|
void CharacterCustomizer::StopClickAnimation(MxU32 p_objectId)
|
|
{
|
|
MxDSAction action;
|
|
action.SetAtomId(MxAtomId(LegoCharacterManager::GetCustomizeAnimFile(), e_lowerCase2));
|
|
action.SetObjectId(p_objectId);
|
|
DeleteObject(action);
|
|
}
|
|
|
|
bool CharacterCustomizer::ResolveClickChangeType(uint8_t& p_changeType, int& p_partIndex, LegoROI* p_clickedROI)
|
|
{
|
|
p_partIndex = -1;
|
|
|
|
switch (GameState()->GetActorId()) {
|
|
case LegoActor::c_pepper:
|
|
if (GameState()->GetCurrentAct() == LegoGameState::e_act2 ||
|
|
GameState()->GetCurrentAct() == LegoGameState::e_act3) {
|
|
return false;
|
|
}
|
|
p_changeType = CHANGE_VARIANT;
|
|
break;
|
|
case LegoActor::c_mama:
|
|
p_changeType = CHANGE_SOUND;
|
|
break;
|
|
case LegoActor::c_papa:
|
|
p_changeType = CHANGE_MOVE;
|
|
break;
|
|
case LegoActor::c_nick:
|
|
p_changeType = CHANGE_COLOR;
|
|
if (p_clickedROI) {
|
|
p_partIndex = MapClickedPartIndex(p_clickedROI->GetName());
|
|
}
|
|
if (p_partIndex < 0) {
|
|
return false;
|
|
}
|
|
break;
|
|
case LegoActor::c_laura:
|
|
p_changeType = CHANGE_MOOD;
|
|
break;
|
|
case LegoActor::c_brickster:
|
|
return false;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|