diff --git a/.gitattributes b/.gitattributes index 430dfc15..a2ec39dd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,3 +11,4 @@ **/*.desktop text eol=lf assets/widescreen/** filter=lfs diff=lfs merge=lfs -text assets/hdmusic/** filter=lfs diff=lfs merge=lfs -text +assets/rabbits/** filter=lfs diff=lfs merge=lfs -text diff --git a/LEGO1/lego/legoomni/src/entity/legoworld.cpp b/LEGO1/lego/legoomni/src/entity/legoworld.cpp index ab141f38..7d920223 100644 --- a/LEGO1/lego/legoomni/src/entity/legoworld.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoworld.cpp @@ -751,6 +751,8 @@ void LegoWorld::Enable(MxBool p_enable) AnimationManager()->Resume(); } + Extension::Call(Extensions::SI::HandleEnable, this, TRUE); + GameState()->ResetROI(); #ifndef BETA10 SetIsWorldActive(TRUE); @@ -760,6 +762,8 @@ void LegoWorld::Enable(MxBool p_enable) Extension::Call(MP::HandleWorldEnable, this, TRUE); } else if (!p_enable && m_disabledObjects.size() == 0) { + Extension::Call(Extensions::SI::HandleEnable, this, FALSE); + MxPresenter* presenter; LegoPathController* controller; LegoPathActor* actor = UserActor(); diff --git a/assets/main.cpp b/assets/main.cpp index 0900f715..13969f64 100644 --- a/assets/main.cpp +++ b/assets/main.cpp @@ -6,6 +6,22 @@ #include #include +void ZeroInitObject(si::Object* o) +{ + o->unknown1_ = 0; + o->flags_ = 0; + o->unknown4_ = 0; + o->duration_ = 0; + o->loops_ = 0; + o->unknown26_ = 0; + o->unknown27_ = 0; + o->unknown28_ = 0; + o->unknown29_ = 0; + o->unknown30_ = 0; + o->volume_ = 0; + o->time_offset_ = 0; +} + si::Interleaf::Version version = si::Interleaf::Version2_2; uint32_t bufferSize = 65536; uint32_t bufferCount = 8; @@ -293,6 +309,106 @@ void CreateBadEnd() si.Write(result.c_str()); } +void CreateRabbits() +{ + std::string result = out + "/rabbits.si"; + + si::Interleaf si; + mxHd.seek(0, si::MemoryBuffer::SeekStart); + si.Read(&mxHd); + + // Object 0 (id=0): Blaubart model (fire-and-forget, loaded by chained Prepend) + si::Object* blaubart = new si::Object; + ZeroInitObject(blaubart); + std::string file = std::string("rabbits/blaubart.mod"); + std::string extra = "Prepend:/lego/extra/rabbits;1"; + blaubart->id_ = 0; + blaubart->type_ = si::MxOb::Object; + blaubart->presenter_ = "LegoModelPresenter"; + blaubart->name_ = "blaubart"; + blaubart->flags_ = MxDSAction::c_enabled; + blaubart->filetype_ = si::MxOb::OBJ; + blaubart->loops_ = 1; + blaubart->unknown28_ = 1; + blaubart->unknown29_ = 1; + blaubart->location_ = si::Vector3(-65.5f, 14.0f, 23.5f); + blaubart->direction_ = si::Vector3(0, 0, 1); + blaubart->up_ = si::Vector3(0, 1, 0); + blaubart->extra_ = si::bytearray(extra.c_str(), extra.length() + 1); + if (!blaubart->ReplaceWithFile(file.c_str())) { + abort(); + } + + si.AppendChild(blaubart); + depfile << result << ": " << (std::filesystem::current_path() / file).string() << std::endl; + + // Object 1 (id=1): Fluse model (fire-and-forget, loaded by chained Prepend from Blaubart) + si::Object* fluse = new si::Object; + ZeroInitObject(fluse); + file = std::string("rabbits/fluse.mod"); + fluse->id_ = 1; + fluse->type_ = si::MxOb::Object; + fluse->presenter_ = "LegoModelPresenter"; + fluse->name_ = "fluse"; + fluse->flags_ = MxDSAction::c_enabled; + fluse->filetype_ = si::MxOb::OBJ; + fluse->loops_ = 1; + fluse->unknown28_ = 1; + fluse->unknown29_ = 1; + fluse->location_ = si::Vector3(-62.0f, 14.0f, 28.0f); + fluse->direction_ = si::Vector3(0, 0, 1); + fluse->up_ = si::Vector3(0, 1, 0); + if (!fluse->ReplaceWithFile(file.c_str())) { + abort(); + } + + si.AppendChild(fluse); + depfile << result << ": " << (std::filesystem::current_path() / file).string() << std::endl; + + // Object 2 (id=2): LegoAnimMMPresenter (composite) + // EnableWith triggers when Isle world becomes active + // Prepend chains: loads Blaubart model (id=0), which chains to Fluse model (id=1) + si::Object* composite = new si::Object; + ZeroInitObject(composite); + extra = "EnableWith:\\Lego\\Scripts\\Isle\\Isle;0, Prepend:/lego/extra/rabbits;0"; + composite->id_ = 2; + composite->type_ = si::MxOb::Presenter; + composite->presenter_ = "LegoAnimMMPresenter"; + composite->flags_ = MxDSAction::c_enabled | MxDSAction::c_bit3; + composite->loops_ = 1; + composite->extra_ = si::bytearray(extra.c_str(), extra.length() + 1); + composite->name_ = "rabbits_composite"; + composite->direction_ = si::Vector3(0, 0, 1); + composite->up_ = si::Vector3(0, 1, 0); + si.AppendChild(composite); + + // Child (id=3): Combined animation (both rabbits in one anim tree) + si::Object* anim = new si::Object; + ZeroInitObject(anim); + file = std::string("rabbits/rabbits.ani"); + anim->id_ = 3; + anim->type_ = si::MxOb::Object; + anim->presenter_ = "LegoLoopingAnimPresenter"; + anim->name_ = "rabbits_anim"; + anim->flags_ = MxDSAction::c_enabled | si::MxOb::NoLoop; + anim->filetype_ = si::MxOb::OBJ; + anim->duration_ = -1; + anim->loops_ = 1; + anim->unknown28_ = 1; + anim->unknown29_ = 1; + anim->location_ = si::Vector3(0, 0, 0); + anim->direction_ = si::Vector3(0, 0, 1); + anim->up_ = si::Vector3(0, 1, 0); + if (!anim->ReplaceWithFile(file.c_str())) { + abort(); + } + + composite->AppendChild(anim); + depfile << result << ": " << (std::filesystem::current_path() / file).string() << std::endl; + + si.Write(result.c_str()); +} + int main(int argc, char* argv[]) { out = argv[1]; @@ -307,5 +423,6 @@ int main(int argc, char* argv[]) CreateWidescreen(); CreateHDMusic(); CreateBadEnd(); + CreateRabbits(); return 0; } diff --git a/assets/rabbits/blaubart.mod b/assets/rabbits/blaubart.mod new file mode 100644 index 00000000..eb31fb8f --- /dev/null +++ b/assets/rabbits/blaubart.mod @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac169fa2b91c3cda2748a1164a05d4d9a4e1daabe01377512a509e2f406925b0 +size 1686652 diff --git a/assets/rabbits/fluse.mod b/assets/rabbits/fluse.mod new file mode 100644 index 00000000..fe41c7ee --- /dev/null +++ b/assets/rabbits/fluse.mod @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb4093239917c305075a8ed952b264cbfa504210c5a67da616756342f57f7f36 +size 1687240 diff --git a/assets/rabbits/rabbits.ani b/assets/rabbits/rabbits.ani new file mode 100644 index 00000000..181708bd --- /dev/null +++ b/assets/rabbits/rabbits.ani @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5687e8f5b0cd15d19740b0b04724b5bf994e1a0e9659f556429c268fea280ebd +size 16334 diff --git a/extensions/include/extensions/siloader.h b/extensions/include/extensions/siloader.h index 5a06074d..9f03bb01 100644 --- a/extensions/include/extensions/siloader.h +++ b/extensions/include/extensions/siloader.h @@ -27,6 +27,7 @@ class SiLoaderExt { static std::optional HandleRemove(StreamObject p_object, LegoWorld* p_world); static std::optional HandleDelete(MxDSAction& p_action); static MxBool HandleEndAction(MxEndActionNotificationParam& p_param); + static MxBool HandleEnable(LegoWorld* p_world, MxBool p_enable); template static std::optional ReplacedIn(MxDSAction& p_action, Args... p_args); @@ -41,10 +42,12 @@ class SiLoaderExt { static std::vector directives; static std::vector> startWith; static std::vector> removeWith; + static std::vector> enableWith; static std::vector> replace; static std::vector> prepend; static std::vector fullScreenMovie; static std::vector disable3d; + static std::vector firedPrepend; static bool LoadFile(const char* p_file); static bool LoadDirective(const char* p_directive); @@ -84,6 +87,7 @@ constexpr auto HandleWorld = &SiLoaderExt::HandleWorld; constexpr auto HandleRemove = &SiLoaderExt::HandleRemove; constexpr auto HandleDelete = &SiLoaderExt::HandleDelete; constexpr auto HandleEndAction = &SiLoaderExt::HandleEndAction; +constexpr auto HandleEnable = &SiLoaderExt::HandleEnable; constexpr auto ReplacedIn = [](auto&&... args) { return SiLoaderExt::ReplacedIn(std::forward(args)...); }; @@ -95,6 +99,7 @@ constexpr decltype(&SiLoaderExt::HandleWorld) HandleWorld = nullptr; constexpr decltype(&SiLoaderExt::HandleRemove) HandleRemove = nullptr; constexpr decltype(&SiLoaderExt::HandleDelete) HandleDelete = nullptr; constexpr decltype(&SiLoaderExt::HandleEndAction) HandleEndAction = nullptr; +constexpr decltype(&SiLoaderExt::HandleEnable) HandleEnable = nullptr; constexpr auto ReplacedIn = [](auto&&... args) -> std::optional { ((void) args, ...); return std::nullopt; diff --git a/extensions/src/siloader.cpp b/extensions/src/siloader.cpp index e09981a9..4255a5c7 100644 --- a/extensions/src/siloader.cpp +++ b/extensions/src/siloader.cpp @@ -19,10 +19,12 @@ std::vector SiLoaderExt::files; std::vector SiLoaderExt::directives; std::vector> SiLoaderExt::startWith; std::vector> SiLoaderExt::removeWith; +std::vector> SiLoaderExt::enableWith; std::vector> SiLoaderExt::replace; std::vector> SiLoaderExt::prepend; std::vector SiLoaderExt::fullScreenMovie; std::vector SiLoaderExt::disable3d; +std::vector SiLoaderExt::firedPrepend; bool SiLoaderExt::enabled = false; void SiLoaderExt::Initialize() @@ -108,6 +110,10 @@ std::optional SiLoaderExt::HandleStart(MxDSAction& p_action) if (p_action.GetExtraLength() == 0 || !SDL_strstr(p_action.GetExtraData(), prependedMarker)) { for (const auto& key : prepend) { if (key.first == object) { + auto it = std::find(firedPrepend.begin(), firedPrepend.end(), key.second); + if (it != firedPrepend.end()) { + firedPrepend.erase(it); + } MxDSAction action; MxResult result = start(key.second, p_action, action); @@ -155,6 +161,46 @@ MxBool SiLoaderExt::HandleWorld(LegoWorld* p_world) return TRUE; } +MxBool SiLoaderExt::HandleEnable(LegoWorld* p_world, MxBool p_enable) +{ + StreamObject object{p_world->GetAtomId(), p_world->GetEntityId()}; + + if (p_enable) { + firedPrepend.clear(); + + auto start = [](const StreamObject& p_object, MxDSAction& p_out) { + if (!OpenStream(p_object.first.GetInternal())) { + return; + } + + p_out.SetAtomId(p_object.first); + p_out.SetObjectId(p_object.second); + p_out.SetUnknown24(-1); + Start(&p_out); + }; + + for (const auto& key : enableWith) { + if (key.first == object) { + MxDSAction action; + start(key.second, action); + } + } + } + else { + for (const auto& key : enableWith) { + if (key.first == object) { + MxDSAction action; + action.SetAtomId(key.second.first); + action.SetObjectId(key.second.second); + action.SetUnknown24(-1); + DeleteObject(action); + } + } + } + + return TRUE; +} + std::optional SiLoaderExt::HandleRemove(StreamObject p_object, LegoWorld* p_world) { for (const auto& key : removeWith) { @@ -226,7 +272,9 @@ MxBool SiLoaderExt::HandleEndAction(MxEndActionNotificationParam& p_param) if (!p_param.GetSender() || !p_param.GetSender()->IsA("MxCompositePresenter")) { for (const auto& key : prepend) { - if (key.second == object) { + if (key.second == object && + std::find(firedPrepend.begin(), firedPrepend.end(), object) == firedPrepend.end()) { + firedPrepend.push_back(object); MxDSAction action; start(key.first, *p_param.GetAction(), action); } @@ -280,6 +328,12 @@ bool SiLoaderExt::LoadDirective(const char* p_directive) StreamObject{MxAtomId{targetAtom, e_lowerCase2}, targetId} ); } + else if (SDL_sscanf(p_directive, "EnableWith:%255[^:;]%*[:;]%u%*[:;]%255[^:;]%*[:;]%u", originAtom, &originId, targetAtom, &targetId) == 4) { + enableWith.emplace_back( + StreamObject{MxAtomId{originAtom, e_lowerCase2}, originId}, + StreamObject{MxAtomId{targetAtom, e_lowerCase2}, targetId} + ); + } else if (SDL_sscanf(p_directive, "Replace:%255[^:;]%*[:;]%u%*[:;]%255[^:;]%*[:;]%u", originAtom, &originId, targetAtom, &targetId) == 4) { replace.emplace_back( StreamObject{MxAtomId{originAtom, e_lowerCase2}, originId}, @@ -342,6 +396,15 @@ void SiLoaderExt::ParseExtra(const MxAtomId& p_atom, si::Core* p_core) } } + if ((directive = SDL_strstr(extra.c_str(), "EnableWith:"))) { + if (SDL_sscanf(directive, "EnableWith:%255[^:;]%*[:;]%u", atom, &id) == 2) { + enableWith.emplace_back( + StreamObject{MxAtomId{atom, e_lowerCase2}, id}, + StreamObject{p_atom, object->id_} + ); + } + } + if ((directive = SDL_strstr(extra.c_str(), "Replace:"))) { if (SDL_sscanf(directive, "Replace:%255[^:;]%*[:;]%u", atom, &id) == 2) { replace.emplace_back(