diff --git a/Client/game_sa/C2DEffectSAInterface.h b/Client/game_sa/C2DEffectSAInterface.h new file mode 100644 index 0000000000..a4d1e4ad08 --- /dev/null +++ b/Client/game_sa/C2DEffectSAInterface.h @@ -0,0 +1,259 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/C2DEffectSAInterface.h + * PURPOSE: Header file for 2dfx game interface layer + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ +#pragma once + +#include "game/RenderWare.h" +#include "game/CModelInfo.h" +#include "CObjectSA.h" + +struct t2dEffectLight +{ + RwColor color; + float coronaFarClip; + float pointLightRange; + float coronaSize; + float shadowSize; + + // Flags + union + { + struct + { + // Flags 1 + std::uint16_t checkObstacles : 1; + std::uint16_t fogType : 1; + std::uint16_t fogType2 : 1; + std::uint16_t withoutCorona : 1; + std::uint16_t onlyLongDistance : 1; + std::uint16_t atDay : 1; + std::uint16_t atNight : 1; + std::uint16_t blinking1 : 1; + + // Flags 2 + std::uint16_t onlyFromBelow : 1; + std::uint16_t blinking2 : 1; + std::uint16_t updateHeightAboveGround : 1; + std::uint16_t checkDirection : 1; + std::uint16_t blinking3 : 1; + }; + + std::uint16_t flags; + }; + + e2dCoronaFlashType coronaFlashType; // show mode + bool coronaEnableReflection; + std::uint8_t coronaFlareType; // lens flare effect 0 - off, 1 - on + std::uint8_t shadowColorMultiplier; + std::int8_t shadowZDistance; + std::int8_t offsetX; + std::int8_t offsetY; + std::int8_t offsetZ; + std::uint8_t field_1E[2]; + RwTexture* coronaTex; + RwTexture* shadowTex; + std::int32_t field_28; + std::int32_t field_2C; +}; + +// The particle effect name is an entry in effects.fxp +struct t2dEffectParticle +{ + char szName[24]; +}; + +// It`s used for spawning peds (Like on ticketbooth, Windows of shops, Blackjack-tables) +// It includes information about the External Script ped is going to use when spawned, it`s facing angle and it`s behaviour +struct t2dEffectAttractor +{ + RwV3d queueDirection; + RwV3d useDirection; + RwV3d forwardDirection; + e2dAttractorType attractorType; + std::uint8_t pedExistingProbability; + std::uint8_t field_26; + std::uint8_t flags; + char szScriptName[8]; +}; + +// entry-exit markers similar to ipl version +struct t2dEffectEnex +{ + float enterAngle; // Rotation angle enter-marker (relative to the object) + RwV3d size; // The radius of the approximation to the marker + RwV3d exitPosn; // The position of exit-marker (offset relative to enter position) + float exitAngle; // angle of rotation exit-marker (relative to the object) + std::int16_t interiorId; + std::uint8_t flags1; // Unknown flags + std::uint8_t skyColor; + char szInteriorName[8]; + std::uint8_t timeOn; + std::uint8_t timeOff; + + // Flags 2 + union + { + struct + { + std::uint8_t unknown1 : 1; + std::uint8_t unknown2 : 1; + std::uint8_t timedEffect : 1; + }; + std::uint8_t flags2; + }; + + std::uint8_t field_2F; +}; + +struct t2dEffectRoadsign +{ + RwV2d size; + RwV3d rotation; + + // Flags + union + { + struct + { + std::uint8_t numOfLines : 2; + std::uint8_t symbolsPerLine : 2; + std::uint8_t textColor : 2; + }; + + std::uint8_t flags; + }; + + std::uint8_t field_16[2]; + char* text; // size 64 + RpAtomic* atomic; +}; + +// Section defines a place where peds can cover during firefights +struct t2dEffectCoverPoint +{ + RwV2d direction; + std::uint8_t type; + std::uint8_t field_9[3]; +}; + +// Example in vgseesc01.dff +struct t2dEffectEscalator +{ + RwV3d bottom; + RwV3d top; + RwV3d end; // Z pos, matches top Z if escalator goes up, bottom Z if it goes down + std::uint8_t direction; // 0 - down, 1 - up + std::uint8_t field_25[3]; +}; + +// Example in kb_bandit_u.dff +// Used to determine additional coordinates that can be used in scripts +struct t2dEffectTriggerPoint +{ + std::int32_t id; +}; + +// Some interiors stuff, probably unused? +struct t2dEffectFurniture +{ + std::uint8_t type; + std::int8_t groupId; + // size + std::uint8_t width; + std::uint8_t depth; + std::uint8_t height; + // doors + std::int8_t door; + std::int8_t l_door[2]; // start, end + std::int8_t r_door[2]; // start, end + std::int8_t t_door[2]; // start, end + // windows + std::int8_t l_window[2]; // start, end + std::int8_t r_window[2]; // start, end + std::int8_t t_window[2]; // start, end + // something like offsets? + std::int8_t goLeft[3]; // x,y,z? + std::int8_t goBottom[3]; // x,y,z? + std::int8_t goWidth[3]; // x,y,z? + std::int8_t goDepth[3]; // x,y,z? + + std::uint8_t seed; + std::uint8_t status; + float rotation; +}; + +union t2dEffectUnion +{ + t2dEffectLight light; + t2dEffectParticle particle; + t2dEffectAttractor attractor; + t2dEffectEnex enex; + t2dEffectRoadsign roadsign; + t2dEffectCoverPoint coverPoint; + t2dEffectEscalator escalator; + t2dEffectTriggerPoint triggerPoint; // slot machine, k.a.a.c gates, basketball hoop + t2dEffectFurniture furniture; +}; + +class C2DEffectSAInterface +{ +public: + CVector position; // RwV3d + e2dEffectType type; + std::uint8_t field_D[3]; + + t2dEffectUnion effect; + +public: + void Shutdown() { ((void(__thiscall*)(C2DEffectSAInterface*))0x4C57D0)(this); } +}; + +class C2DEffectInfoStoreSAInterface +{ +public: + std::uint32_t objCount; + C2DEffectSAInterface objects[100]; +}; + +class C2DEffectPluginDataSAInterface +{ +public: + std::uint32_t count; + C2DEffectSAInterface objects[]; +}; + +class CEscalatorSAInterface +{ +public: + RwV3d startPos; + RwV3d bottomPos; + RwV3d topPos; + RwV3d endPos; + std::uint8_t rotation[72]; // CMatrixSAInterface + bool exist; + bool objectCreated; + bool moveDown; + std::uint8_t field_7B; // pad + std::int32_t numIntermediatePlanes; + std::uint32_t numBottomPlanes; + std::uint32_t numTopPlanes; + std::uint8_t field_88[8]; // unused field + RwSphere bounding; + float currentPosition; + CEntitySAInterface* entity; + CObjectSAInterface* objects[42]; + +public: + void AddThisOne(CVector* start, CVector* bottom, CVector* top, CVector* end, bool moveDown, CEntitySAInterface* entity) + { + ((void(__thiscall*)(CEscalatorSAInterface*, CVector*, CVector*, CVector*, CVector*, bool, CEntitySAInterface*))0x717970)(this, start, bottom, top, end, moveDown, entity); + } + void SwitchOff() { ((void(__thiscall*)(CEscalatorSAInterface*))0x717860)(this); } +}; diff --git a/Client/game_sa/C2DEffectsSA.cpp b/Client/game_sa/C2DEffectsSA.cpp new file mode 100644 index 0000000000..8ee6a713b3 --- /dev/null +++ b/Client/game_sa/C2DEffectsSA.cpp @@ -0,0 +1,1137 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/C2DEffectsSA.cpp + * PURPOSE: 2DFX effects handler class + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" +#include "C2DEffectsSA.h" +#include "CGameSA.h" +#include "CFxSA.h" +#include "gamesa_renderware.h" + +extern CGameSA* pGame; + +C2DEffectSAInterface* C2DEffectsSA::GetEffect(std::uint32_t model, std::uint32_t index) const +{ + CModelInfo* modelInfo = pGame->GetModelInfo(model); + if (!modelInfo) + return nullptr; + + return modelInfo->Get2DFXEffectByIndex(index); +} + +void C2DEffectsSA::InitEffect(C2DEffectSAInterface* effect, std::uint32_t model, CEntitySAInterface* entity, std::vector* affectedEntities) +{ + if (!effect) + return; + + switch (effect->type) + { + case e2dEffectType::PARTICLE: + { + if (affectedEntities) + { + for (auto& particleEntity : *affectedEntities) + g_Fx->CreateEntityFx(particleEntity, effect->effect.particle.szName, &effect->position); + } + else if (entity) + g_Fx->CreateEntityFx(entity, effect->effect.particle.szName, &effect->position); + + break; + } + case e2dEffectType::ROADSIGN: + { + t2dEffectRoadsign& roadsign = effect->effect.roadsign; + + std::uint32_t numLines = Roadsign_GetNumLinesFromFlags(roadsign.flags); + std::uint32_t numLetters = Roadsign_GetNumLettersFromFlags(roadsign.flags); + std::uint8_t palleteID = Roadsign_GetPalleteIDFromFlags(roadsign.flags); + roadsign.atomic = Roadsign_CreateAtomic(effect->position, roadsign.rotation, roadsign.size.x, roadsign.size.y, numLines, &roadsign.text[0], + &roadsign.text[16], &roadsign.text[32], &roadsign.text[48], numLetters, palleteID); + + CModelInfo* modelInfo = pGame->GetModelInfo(model); + if (modelInfo) + SetRoadsignTextColor(effect, modelInfo->GetStored2DFXRoadsignColor(modelInfo->Get2DFXEffectIndex(effect))); + + break; + } + case e2dEffectType::ESCALATOR: + { + if (!entity && affectedEntities) + entity = affectedEntities->at(0); + + if (entity) + { + CVector vecStart{}; + CVector vecBottom{}; + CVector vecTop{}; + CVector vecEnd{}; + + entity->TransformFromObjectSpace(&vecStart, &effect->position); + entity->TransformFromObjectSpace(&vecBottom, reinterpret_cast(&effect->effect.escalator.bottom)); + entity->TransformFromObjectSpace(&vecTop, reinterpret_cast(&effect->effect.escalator.top)); + entity->TransformFromObjectSpace(&vecEnd, reinterpret_cast(&effect->effect.escalator.end)); + + for (std::size_t i = 0; i < NUM_MAX_ESCALATORS; i++) + { + if (aEscalators[i].exist) + continue; + + aEscalators[i].AddThisOne(&vecStart, &vecBottom, &vecTop, &vecEnd, effect->effect.escalator.direction == 0, entity); + break; + } + } + + break; + } + default: + break; + } +} + +void C2DEffectsSA::DeInitEffect(C2DEffectSAInterface* effect, std::uint32_t model, bool isCustomEffect, CEntitySAInterface* entity) +{ + if (!effect) + return; + + switch (effect->type) + { + case e2dEffectType::LIGHT: + { + t2dEffectLight& light = effect->effect.light; + + if (light.coronaTex) + { + RwTextureDestroy(light.coronaTex); + light.coronaTex = nullptr; + } + + if (light.shadowTex) + { + RwTextureDestroy(light.shadowTex); + light.shadowTex = nullptr; + } + break; + } + case e2dEffectType::PARTICLE: + { + if (!entity) + { + const auto& entities = CFxSA::GetEntitiesFromFx(model); + for (auto* fxEntity : entities) + g_Fx->DestroyEntityFx(fxEntity); + } + else + g_Fx->DestroyEntityFx(entity); + + break; + } + case e2dEffectType::ROADSIGN: + { + RpAtomic* atomic = effect->effect.roadsign.atomic; + if (atomic) + { + RwFrame* frame = RpAtomicGetFrame(atomic); + if (frame) + { + RpAtomicSetFrame(atomic, nullptr); + RwFrameDestroy(frame); + } + + RpAtomicDestroy(atomic); + effect->effect.roadsign.atomic = nullptr; + } + + // We only free memory for the custom effect. + // The original effects are managed by CMemoryMgr in the RW plugin (EffectStreamRead) + if (isCustomEffect && effect->effect.roadsign.text) + { + std::free(effect->effect.roadsign.text); + effect->effect.roadsign.text = nullptr; + } + + break; + } + case e2dEffectType::ESCALATOR: + { + for (std::size_t i = 0; i < NUM_MAX_ESCALATORS; i++) + { + if (!aEscalators[i].exist || !aEscalators[i].entity) + continue; + + if (entity && aEscalators[i].entity != entity) + continue; + + if (!entity && aEscalators[i].entity->m_nModelIndex != model) + continue; + + aEscalators[i].SwitchOff(); + aEscalators[i].exist = false; + break; + } + + break; + } + default: + break; + } +} + +void C2DEffectsSA::ReInitEffect(C2DEffectSAInterface* effect, std::uint32_t model, bool isCustomEffect, CEntitySAInterface* entity) +{ + // We preserve the entities that have particle/escalator 2dfx effect, + // so that during initialization we can add it back to the same entities + std::vector affectedEntities{}; + if (effect->type == e2dEffectType::PARTICLE && !entity) + affectedEntities = CFxSA::GetEntitiesFromFx(model); + else if (effect->type == e2dEffectType::ESCALATOR && !entity) + { + for (std::size_t i = 0; i < NUM_MAX_ESCALATORS; i++) + { + if (aEscalators[i].exist && aEscalators[i].entity && aEscalators[i].entity->m_nModelIndex == model) + { + affectedEntities.push_back(aEscalators[i].entity); + break; + } + } + } + + DeInitEffect(effect, model, isCustomEffect, entity); + InitEffect(effect, model, entity, affectedEntities.empty() ? nullptr : &affectedEntities); +} + +void C2DEffectsSA::ReInitEffect(std::uint32_t model, std::uint32_t index) +{ + C2DEffectSAInterface* effect = GetEffect(model, index); + if (!effect) + return; + + ReInitEffect(effect, model, pGame->GetModelInfo(model)->IsCustom2DFXEffect(effect)); +} + +void C2DEffectsSA::Set2DFXEffectPosition(std::uint32_t model, std::uint32_t index, const CVector& position) +{ + C2DEffectSAInterface* effect = GetEffect(model, index); + if (!effect) + return; + + effect->position = position; + if (effect->type != e2dEffectType::LIGHT) + ReInitEffect(model, index); +} + +CVector C2DEffectsSA::Get2DFXEffectPosition(std::uint32_t model, std::uint32_t index) const +{ + C2DEffectSAInterface* effect = GetEffect(model, index); + return effect ? effect->position : CVector{}; +} + +void C2DEffectsSA::Set2DFXProperties(C2DEffectSAInterface* effectInterface, const S2DEffectData& effectProperties) +{ + if (!effectInterface) + return; + + effectInterface->position = effectProperties.position; + + switch (effectInterface->type) + { + case e2dEffectType::LIGHT: + { + t2dEffectLight& light = effectInterface->effect.light; + light.coronaFarClip = effectProperties.drawDistance; + light.pointLightRange = effectProperties.lightRange; + light.coronaSize = effectProperties.coronaSize; + light.shadowSize = effectProperties.shadowSize; + light.shadowColorMultiplier = effectProperties.shadowMult; + light.shadowZDistance = effectProperties.shadowDist; + light.coronaFlashType = effectProperties.coronaFlashType; + light.coronaEnableReflection = effectProperties.coronaReflection; + light.coronaFlareType = effectProperties.coronaFlareType; + light.flags = effectProperties.flags; + light.offsetX = effectProperties.offset.x; + light.offsetY = effectProperties.offset.y; + light.offsetZ = effectProperties.offset.z; + light.color = RwColor{effectProperties.color.R, effectProperties.color.G, effectProperties.color.B, effectProperties.color.A}; + + if (light.coronaTex) + RwTextureDestroy(light.coronaTex); + + if (light.shadowTex) + RwTextureDestroy(light.shadowTex); + + CTxdStore_PushCurrentTxd(); + SetTextureDict(CTxdStore_FindTxdSlot("particle")); + light.coronaTex = RwReadTexture(effectProperties.coronaName.c_str(), nullptr); + light.shadowTex = RwReadTexture(effectProperties.shadowName.c_str(), nullptr); + CTxdStore_PopCurrentTxd(); + break; + } + case e2dEffectType::PARTICLE: + { + t2dEffectParticle& particle = effectInterface->effect.particle; + std::strncpy(particle.szName, effectProperties.prt_name.c_str(), 24); + break; + } + case e2dEffectType::ROADSIGN: + { + t2dEffectRoadsign& roadsign = effectInterface->effect.roadsign; + roadsign.size = RwV2d{effectProperties.size.fX, effectProperties.size.fY}; + roadsign.rotation = RwV3d{effectProperties.rot.fX, effectProperties.rot.fY, effectProperties.rot.fZ}; + roadsign.flags = static_cast(effectProperties.flags); + + if (!roadsign.text) + roadsign.text = static_cast(std::malloc(64)); + + if (roadsign.text) + { + ZeroMemory(roadsign.text, 64); + + SetRoadsignText(effectInterface, effectProperties.text_1, 1); + SetRoadsignText(effectInterface, effectProperties.text_2, 2); + SetRoadsignText(effectInterface, effectProperties.text_3, 3); + SetRoadsignText(effectInterface, effectProperties.text_4, 4); + } + + SetRoadsignTextColor(effectInterface, + RwColor{effectProperties.color.R, effectProperties.color.G, effectProperties.color.B, effectProperties.color.A}); + break; + } + case e2dEffectType::ESCALATOR: + { + t2dEffectEscalator& escalator = effectInterface->effect.escalator; + escalator.bottom = RwV3d{effectProperties.bottom.fX, effectProperties.bottom.fY, effectProperties.bottom.fZ}; + escalator.top = RwV3d{effectProperties.top.fX, effectProperties.top.fY, effectProperties.top.fZ}; + escalator.end = RwV3d{effectProperties.end.fX, effectProperties.end.fY, effectProperties.end.fZ}; + escalator.direction = effectProperties.direction; + break; + } + default: + break; + } +} + +void C2DEffectsSA::Set2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, float value) +{ + C2DEffectSAInterface* effect = GetEffect(model, index); + if (!effect) + return; + + switch (effect->type) + { + case e2dEffectType::LIGHT: + { + t2dEffectLight& light = effect->effect.light; + + switch (property) + { + case e2dEffectProperty::FAR_CLIP_DISTANCE: + light.coronaFarClip = value; + break; + case e2dEffectProperty::LIGHT_RANGE: + light.pointLightRange = value; + break; + case e2dEffectProperty::CORONA_SIZE: + light.coronaSize = value; + break; + case e2dEffectProperty::SHADOW_SIZE: + light.shadowSize = value; + break; + case e2dEffectProperty::SHADOW_MULT: + light.shadowColorMultiplier = static_cast(value); + break; + case e2dEffectProperty::FLARE_TYPE: + light.coronaFlareType = static_cast(value); + break; + case e2dEffectProperty::SHADOW_DISTANCE: + light.shadowZDistance = static_cast(value); + break; + case e2dEffectProperty::FLAGS: + light.flags = static_cast(value); + break; + case e2dEffectProperty::COLOR: + { + SColor color = TOCOLOR2SCOLOR(value); + light.color = RwColor{color.R, color.G, color.B, color.A}; + + break; + } + default: + break; + } + + break; + } + case e2dEffectType::ROADSIGN: + { + t2dEffectRoadsign& roadsign = effect->effect.roadsign; + + switch (property) + { + case e2dEffectProperty::FLAGS: + roadsign.flags = static_cast(value); + break; + case e2dEffectProperty::COLOR: + { + SColor color = TOCOLOR2SCOLOR(value); + RwColor newColor = RwColor{color.R, color.G, color.B, color.A}; + + pGame->GetModelInfo(model)->Store2DFXRoadsignColor(index, newColor); + SetRoadsignTextColor(effect, newColor); + break; + } + default: + break; + } + + break; + } + case e2dEffectType::ESCALATOR: + { + t2dEffectEscalator& escalator = effect->effect.escalator; + if (property == e2dEffectProperty::DIRECTION) + escalator.direction = static_cast(value); + + break; + } + default: + break; + } +} + +void C2DEffectsSA::Set2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, bool value) +{ + C2DEffectSAInterface* effect = GetEffect(model, index); + if (!effect || effect->type != e2dEffectType::LIGHT || property != e2dEffectProperty::CORONA_REFLECTION) + return; + + effect->effect.light.coronaEnableReflection = value; +} + +void C2DEffectsSA::Set2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, std::string& value) +{ + C2DEffectSAInterface* effect = GetEffect(model, index); + if (!effect) + return; + + switch (effect->type) + { + case e2dEffectType::LIGHT: + { + t2dEffectLight light = effect->effect.light; + + switch (property) + { + case e2dEffectProperty::CORONA_NAME: + { + if (light.coronaTex) + RwTextureDestroy(light.coronaTex); + + CTxdStore_PushCurrentTxd(); + SetTextureDict(CTxdStore_FindTxdSlot("particle")); + light.coronaTex = RwReadTexture(value.c_str(), nullptr); + CTxdStore_PopCurrentTxd(); + break; + } + case e2dEffectProperty::SHADOW_NAME: + { + if (light.shadowTex) + RwTextureDestroy(light.shadowTex); + + CTxdStore_PushCurrentTxd(); + SetTextureDict(CTxdStore_FindTxdSlot("particle")); + light.shadowTex = RwReadTexture(value.c_str(), nullptr); + CTxdStore_PopCurrentTxd(); + break; + } + default: + break; + } + + break; + } + case e2dEffectType::PARTICLE: + { + if (property == e2dEffectProperty::PRT_NAME) + std::strncpy(effect->effect.particle.szName, value.c_str(), 24); + + break; + } + case e2dEffectType::ROADSIGN: + { + t2dEffectRoadsign& roadsign = effect->effect.roadsign; + + switch (property) + { + case e2dEffectProperty::TEXT_1: + std::strncpy(roadsign.text, value.c_str(), 16); + break; + case e2dEffectProperty::TEXT_2: + std::strncpy(roadsign.text + 16, value.c_str(), 16); + break; + case e2dEffectProperty::TEXT_3: + std::strncpy(roadsign.text + 32, value.c_str(), 16); + break; + case e2dEffectProperty::TEXT_4: + std::strncpy(roadsign.text + 48, value.c_str(), 16); + break; + default: + break; + } + } + default: + break; + } +} + +void C2DEffectsSA::Set2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, e2dCoronaFlashType& value) +{ + C2DEffectSAInterface* effect = GetEffect(model, index); + if (!effect || effect->type != e2dEffectType::LIGHT || property != e2dEffectProperty::FLASH_TYPE) + return; + + effect->effect.light.coronaFlashType = value; +} + +void C2DEffectsSA::Set2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, const std::vector& value) +{ + C2DEffectSAInterface* effect = GetEffect(model, index); + if (!effect) + return; + + switch (effect->type) + { + case e2dEffectType::LIGHT: + { + if (property == e2dEffectProperty::OFFSET) + { + effect->effect.light.offsetX = value[0]; + effect->effect.light.offsetY = value[1]; + effect->effect.light.offsetZ = value[2]; + } + + break; + } + case e2dEffectType::ROADSIGN: + { + switch (property) + { + case e2dEffectProperty::SIZE: + effect->effect.roadsign.size = RwV2d{value[0], value[1]}; + break; + case e2dEffectProperty::ROT: + effect->effect.roadsign.rotation = RwV3d{value[0], value[1], value[2]}; + break; + default: + break; + } + + break; + } + case e2dEffectType::ESCALATOR: + { + switch (property) + { + case e2dEffectProperty::BOTTOM: + effect->effect.escalator.bottom = RwV3d{value[0], value[1], value[2]}; + break; + case e2dEffectProperty::TOP: + effect->effect.escalator.top = RwV3d{value[0], value[1], value[2]}; + break; + case e2dEffectProperty::END: + effect->effect.escalator.end = RwV3d{value[0], value[1], value[2]}; + break; + default: + break; + } + + break; + } + default: + break; + } +} + +void C2DEffectsSA::Reset2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, const S2DEffectData& originalProperties) +{ + C2DEffectSAInterface* effect = GetEffect(model, index); + if (!effect) + return; + + S2DEffectData& currProperties = Get2DFXPropertiesData(model, index); + UpdatePropertiesData(currProperties, originalProperties, property); + Set2DFXProperties(effect, currProperties); +} + +S2DEffectData C2DEffectsSA::Get2DFXPropertiesData(std::uint32_t model, std::uint32_t index) const +{ + S2DEffectData data{}; + C2DEffectSAInterface* effect = GetEffect(model, index); + + if (effect) + { + data.position = effect->position; + + switch (effect->type) + { + case e2dEffectType::LIGHT: + { + t2dEffectLight& light = effect->effect.light; + data.drawDistance = light.coronaFarClip; + data.lightRange = light.pointLightRange; + data.coronaSize = light.coronaSize; + data.shadowSize = light.shadowSize; + data.shadowDist = light.shadowZDistance; + data.shadowMult = light.shadowColorMultiplier; + data.coronaFlashType = light.coronaFlashType; + data.coronaFlareType = light.coronaFlareType; + data.coronaReflection = light.coronaEnableReflection; + data.offset = {light.offsetX, light.offsetY, light.offsetZ}; + data.coronaName = std::string(light.coronaTex->name, 32); + data.shadowName = std::string(light.shadowTex->name, 32); + data.flags = light.flags; + + SColor color; + color.R = light.color.r; + color.G = light.color.g; + color.B = light.color.b; + color.A = light.color.a; + + data.color = color; + break; + } + case e2dEffectType::PARTICLE: + { + data.prt_name = std::string(effect->effect.particle.szName, strnlen(effect->effect.particle.szName, 24)); + break; + } + case e2dEffectType::ROADSIGN: + { + t2dEffectRoadsign& roadsign = effect->effect.roadsign; + + data.flags = roadsign.flags; + data.size = CVector{roadsign.size.x, roadsign.size.y}; + data.rot = CVector{roadsign.rotation.x, roadsign.rotation.y, roadsign.rotation.z}; + + data.text_1 = std::string(roadsign.text, strnlen(roadsign.text, 16)); + data.text_2 = std::string(roadsign.text + 16, strnlen(roadsign.text + 16, 16)); + data.text_3 = std::string(roadsign.text + 32, strnlen(roadsign.text + 32, 16)); + data.text_4 = std::string(roadsign.text + 48, strnlen(roadsign.text + 48, 16)); + + pGame->Get2DFXEffects()->Get2DFXProperty(model, index, e2dEffectProperty::COLOR, data.color); + break; + } + case e2dEffectType::ESCALATOR: + { + t2dEffectEscalator& escalator = effect->effect.escalator; + + data.bottom = CVector{escalator.bottom.x, escalator.bottom.y, escalator.bottom.z}; + data.top = CVector{escalator.top.x, escalator.top.y, escalator.top.z}; + data.end = CVector{escalator.end.x, escalator.end.y, escalator.end.z}; + data.direction = escalator.direction; + break; + } + } + } + + return data; +} + +void C2DEffectsSA::Get2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, float& outValue) const +{ + C2DEffectSAInterface* effect = GetEffect(model, index); + if (!effect) + return; + + switch (effect->type) + { + case e2dEffectType::LIGHT: + { + t2dEffectLight& light = effect->effect.light; + + switch (property) + { + case e2dEffectProperty::FAR_CLIP_DISTANCE: + outValue = light.coronaFarClip; + break; + case e2dEffectProperty::LIGHT_RANGE: + outValue = light.pointLightRange; + break; + case e2dEffectProperty::CORONA_SIZE: + outValue = light.coronaSize; + break; + case e2dEffectProperty::SHADOW_SIZE: + outValue = light.shadowSize; + break; + case e2dEffectProperty::SHADOW_MULT: + outValue = light.shadowColorMultiplier; + break; + case e2dEffectProperty::FLARE_TYPE: + outValue = light.coronaFlareType; + break; + case e2dEffectProperty::SHADOW_DISTANCE: + outValue = light.shadowZDistance; + break; + case e2dEffectProperty::FLAGS: + outValue = light.flags; + break; + default: + break; + } + + break; + } + case e2dEffectType::ROADSIGN: + { + t2dEffectRoadsign& roadsign = effect->effect.roadsign; + + switch (property) + { + case e2dEffectProperty::FLAGS: + outValue = roadsign.flags; + break; + default: + break; + } + + break; + } + case e2dEffectType::ESCALATOR: + { + t2dEffectEscalator& escalator = effect->effect.escalator; + outValue = static_cast(escalator.direction); + + break; + } + default: + break; + } +} + +void C2DEffectsSA::Get2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, bool& outValue) const +{ + C2DEffectSAInterface* effect = GetEffect(model, index); + if (!effect) + return; + + if (effect->type == e2dEffectType::LIGHT && property == e2dEffectProperty::CORONA_REFLECTION) + outValue = effect->effect.light.coronaEnableReflection; +} + +void C2DEffectsSA::Get2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, std::string& outValue) const +{ + C2DEffectSAInterface* effect = GetEffect(model, index); + if (!effect) + return; + + switch (effect->type) + { + case e2dEffectType::LIGHT: + { + t2dEffectLight& light = effect->effect.light; + + switch (property) + { + case e2dEffectProperty::CORONA_NAME: + outValue = light.coronaTex ? std::string(light.coronaTex->name, strnlen(light.coronaTex->name, 32)) : ""; + break; + case e2dEffectProperty::SHADOW_NAME: + outValue = light.shadowTex ? std::string(light.shadowTex->name, strnlen(light.shadowTex->name, 32)) : ""; + break; + default: + break; + } + } + case e2dEffectType::PARTICLE: + { + if (property == e2dEffectProperty::PRT_NAME) + outValue = std::string(effect->effect.particle.szName, strnlen(effect->effect.particle.szName, 24)); + + break; + } + case e2dEffectType::ROADSIGN: + { + t2dEffectRoadsign& roadsign = effect->effect.roadsign; + + switch (property) + { + case e2dEffectProperty::TEXT_1: + outValue = std::string(roadsign.text, strnlen(roadsign.text, 16)); + break; + case e2dEffectProperty::TEXT_2: + outValue = std::string(roadsign.text + 16, strnlen(roadsign.text + 16, 16)); + break; + case e2dEffectProperty::TEXT_3: + outValue = std::string(roadsign.text + 32, strnlen(roadsign.text + 32, 16)); + break; + case e2dEffectProperty::TEXT_4: + outValue = std::string(roadsign.text + 48, strnlen(roadsign.text + 48, 16)); + break; + default: + break; + } + + break; + } + default: + break; + } +} + +void C2DEffectsSA::Get2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, e2dCoronaFlashType& outValue) const +{ + C2DEffectSAInterface* effect = GetEffect(model, index); + if (!effect) + return; + + if (effect->type == e2dEffectType::LIGHT && property == e2dEffectProperty::FLASH_TYPE) + outValue = effect->effect.light.coronaFlashType; +} + +void C2DEffectsSA::Get2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, SColor& outValue) const +{ + C2DEffectSAInterface* effect = GetEffect(model, index); + if (!effect) + return; + + switch (effect->type) + { + case e2dEffectType::LIGHT: + { + t2dEffectLight& light = effect->effect.light; + outValue.R = light.color.r; + outValue.G = light.color.g; + outValue.B = light.color.b; + outValue.A = light.color.a; + break; + } + case e2dEffectType::ROADSIGN: + { + RwColor& textColor = GetRoadsignTextColor(effect); + outValue.R = textColor.r; + outValue.G = textColor.g; + outValue.B = textColor.b; + outValue.A = textColor.a; + break; + } + default: + break; + } +} + +void C2DEffectsSA::Get2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, std::vector& outValue) const +{ + C2DEffectSAInterface* effect = GetEffect(model, index); + if (!effect) + return; + + switch (effect->type) + { + case e2dEffectType::LIGHT: + { + t2dEffectLight& light = effect->effect.light; + outValue = {static_cast(light.offsetX), static_cast(light.offsetY), static_cast(light.offsetZ)}; + break; + } + case e2dEffectType::ROADSIGN: + { + t2dEffectRoadsign& roadsign = effect->effect.roadsign; + + switch (property) + { + case e2dEffectProperty::SIZE: + outValue = {roadsign.size.x, roadsign.size.y}; + break; + case e2dEffectProperty::ROT: + outValue = {roadsign.rotation.x, roadsign.rotation.y, roadsign.rotation.z}; + break; + default: + break; + } + } + case e2dEffectType::ESCALATOR: + { + t2dEffectEscalator& escalator = effect->effect.escalator; + + switch (property) + { + case e2dEffectProperty::BOTTOM: + outValue = {escalator.bottom.x, escalator.bottom.y, escalator.bottom.z}; + break; + case e2dEffectProperty::TOP: + outValue = {escalator.top.x, escalator.top.y, escalator.top.z}; + break; + case e2dEffectProperty::END: + outValue = {escalator.end.x, escalator.end.y, escalator.end.z}; + break; + default: + break; + } + } + default: + break; + } +} + +void C2DEffectsSA::SetRoadsignTextColor(C2DEffectSAInterface* effect, const RwColor& color) +{ + if (!effect || effect->type != e2dEffectType::ROADSIGN) + return; + + t2dEffectRoadsign& roadsign = effect->effect.roadsign; + if (!roadsign.atomic) + return; + + RpGeometry* geometry = roadsign.atomic->geometry; + if (!geometry) + return; + + RpMaterials materials = geometry->materials; + for (std::size_t i = 0; i < materials.entries; i++) + { + RpMaterial* material = materials.materials[i]; + if (material) + material->color = color; + } +} + +RwColor C2DEffectsSA::GetRoadsignTextColor(C2DEffectSAInterface* effect) const +{ + RwColor dummy = RwColor{0, 0, 0, 0}; + + if (!effect || effect->type != e2dEffectType::ROADSIGN) + return dummy; + + t2dEffectRoadsign& roadsign = effect->effect.roadsign; + if (!roadsign.atomic) + return dummy; + + RpGeometry* geometry = roadsign.atomic->geometry; + if (!geometry) + return dummy; + + RpMaterials materials = geometry->materials; + if (materials.entries > 0) + return materials.materials[0]->color; + else + return dummy; +} + +void C2DEffectsSA::SetRoadsignText(C2DEffectSAInterface* effect, const std::string& text, std::uint8_t line) +{ + if (!effect || effect->type != e2dEffectType::ROADSIGN) + return; + + if (!effect->effect.roadsign.text) + { + effect->effect.roadsign.text = static_cast(std::malloc(64)); + + if (effect->effect.roadsign.text) + ZeroMemory(effect->effect.roadsign.text, 64); + } + + if (effect->effect.roadsign.text) + { + ZeroMemory(effect->effect.roadsign.text + 16 * (line - 1), 16); + std::strncpy(effect->effect.roadsign.text + 16 * (line - 1), text.c_str(), 16); + } +} + +void C2DEffectsSA::UpdatePropertiesData(S2DEffectData& dst, const S2DEffectData& src, const e2dEffectProperty& property) const +{ + switch (property) + { + case e2dEffectProperty::FAR_CLIP_DISTANCE: + dst.drawDistance = src.drawDistance; + break; + case e2dEffectProperty::LIGHT_RANGE: + dst.lightRange = src.lightRange; + break; + case e2dEffectProperty::CORONA_SIZE: + dst.coronaSize = src.coronaSize; + break; + case e2dEffectProperty::SHADOW_SIZE: + dst.shadowSize = src.shadowSize; + break; + case e2dEffectProperty::SHADOW_MULT: + dst.shadowMult = src.shadowMult; + break; + case e2dEffectProperty::FLASH_TYPE: + dst.coronaFlashType = src.coronaFlashType; + break; + case e2dEffectProperty::CORONA_REFLECTION: + dst.coronaReflection = src.coronaReflection; + break; + case e2dEffectProperty::FLARE_TYPE: + dst.coronaFlareType = src.coronaFlareType; + break; + case e2dEffectProperty::SHADOW_DISTANCE: + dst.shadowDist = src.shadowDist; + break; + case e2dEffectProperty::OFFSET: + dst.offset = src.offset; + break; + case e2dEffectProperty::COLOR: + dst.color = src.color; + break; + case e2dEffectProperty::CORONA_NAME: + dst.coronaName = src.coronaName; + break; + case e2dEffectProperty::SHADOW_NAME: + dst.shadowName = src.shadowName; + break; + case e2dEffectProperty::FLAGS: + dst.flags = src.flags; + break; + case e2dEffectProperty::PRT_NAME: + dst.prt_name = src.prt_name; + break; + case e2dEffectProperty::SIZE: + dst.size = src.size; + break; + case e2dEffectProperty::ROT: + dst.rot = src.rot; + break; + case e2dEffectProperty::TEXT_1: + dst.text_1 = src.text_1; + break; + case e2dEffectProperty::TEXT_2: + dst.text_2 = src.text_2; + break; + case e2dEffectProperty::TEXT_3: + dst.text_3 = src.text_3; + break; + case e2dEffectProperty::TEXT_4: + dst.text_4 = src.text_4; + break; + case e2dEffectProperty::BOTTOM: + dst.bottom = src.bottom; + break; + case e2dEffectProperty::TOP: + dst.top = src.top; + break; + case e2dEffectProperty::END: + dst.end = src.end; + break; + case e2dEffectProperty::DIRECTION: + dst.direction = src.direction; + break; + case e2dEffectProperty::POSITION: + dst.position = src.position; + break; + default: + break; + } +} + +RpAtomic* C2DEffectsSA::Roadsign_CreateAtomic(const CVector& position, const RwV3d& rotation, float sizeX, float sizeY, std::uint32_t numLines, char* line1, + char* line2, char* line3, char* line4, std::uint32_t numLetters, std::uint8_t palleteID) +{ + // Call CCustomRoadsignMgr::CreateRoadsignAtomic + RpAtomic* atomic = ((RpAtomic * (__cdecl*)(float, float, std::int32_t, char*, char*, char*, char*, std::int32_t, std::uint8_t))0x6FF2D0)( + sizeX, sizeY, numLines, line1, line2, line3, line4, numLetters, palleteID); + if (!atomic) + return nullptr; + + RwFrame* frame = RpAtomicGetFrame(atomic); + if (frame) + { + RwFrameSetIdentity(frame); + + const RwV3d axis0{1.0f, 0.0f, 0.0f}, axis1{0.0f, 1.0f, 0.0f}, axis2{0.0f, 0.0f, 1.0f}; + RwFrameRotate(frame, &axis2, rotation.z, rwCOMBINEREPLACE); + RwFrameRotate(frame, &axis0, rotation.x, rwCOMBINEPOSTCONCAT); + RwFrameRotate(frame, &axis1, rotation.y, rwCOMBINEPOSTCONCAT); + + RwV3d pos{position.fX, position.fY, position.fZ}; + RwFrameTranslate(frame, &pos, TRANSFORM_AFTER); + RwFrameUpdateObjects(frame); + } + + return atomic; +} + +std::uint32_t C2DEffectsSA::Roadsign_GetPalleteIDFromFlags(std::uint8_t flags) const noexcept +{ + std::uint32_t id = (flags >> 4) & 3; + return id <= 3 ? id : 0; +} + +std::uint32_t C2DEffectsSA::Roadsign_GetNumLettersFromFlags(std::uint8_t flags) const noexcept +{ + std::uint32_t letters = (flags >> 2) & 3; + switch (letters) + { + case 1u: + return 2; + case 2u: + return 4; + case 3u: + return 8; + default: + return 16; + } +} + +std::uint32_t C2DEffectsSA::Roadsign_GetNumLinesFromFlags(std::uint8_t flags) const noexcept +{ + std::uint32_t lines = flags & 3; + return (lines <= 3 && lines > 0) ? lines : 4; +} + +bool C2DEffectsSA::IsPropertyValueNumber(const e2dEffectProperty& property) const noexcept +{ + switch (property) + { + case e2dEffectProperty::FAR_CLIP_DISTANCE: + case e2dEffectProperty::LIGHT_RANGE: + case e2dEffectProperty::CORONA_SIZE: + case e2dEffectProperty::SHADOW_SIZE: + case e2dEffectProperty::SHADOW_MULT: + case e2dEffectProperty::FLARE_TYPE: + case e2dEffectProperty::SHADOW_DISTANCE: + case e2dEffectProperty::FLAGS: + case e2dEffectProperty::DIRECTION: + return true; + default: + return false; + } +} + +bool C2DEffectsSA::IsPropertyValueString(const e2dEffectProperty& property) const noexcept +{ + switch (property) + { + case e2dEffectProperty::CORONA_NAME: + case e2dEffectProperty::SHADOW_NAME: + case e2dEffectProperty::PRT_NAME: + case e2dEffectProperty::TEXT_1: + case e2dEffectProperty::TEXT_2: + case e2dEffectProperty::TEXT_3: + case e2dEffectProperty::TEXT_4: + return true; + default: + return false; + } +} + +bool C2DEffectsSA::IsPropertyValueTable(const e2dEffectProperty& property) const noexcept +{ + switch (property) + { + case e2dEffectProperty::OFFSET: + case e2dEffectProperty::SIZE: + case e2dEffectProperty::ROT: + case e2dEffectProperty::BOTTOM: + case e2dEffectProperty::TOP: + case e2dEffectProperty::END: + return true; + default: + return false; + } +} diff --git a/Client/game_sa/C2DEffectsSA.h b/Client/game_sa/C2DEffectsSA.h new file mode 100644 index 0000000000..f04ca54fbc --- /dev/null +++ b/Client/game_sa/C2DEffectsSA.h @@ -0,0 +1,73 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: game_sa/C2DEffectsSA.h + * PURPOSE: Header file for 2dfx effects handler class + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ +#pragma once +#include +#include "C2DEffectSAInterface.h" + +#define ARRAY_CEscalators 0xC6E9A8 +#define NUM_MAX_ESCALATORS 32 + +static auto* aEscalators = reinterpret_cast(ARRAY_CEscalators); + +class C2DEffectsSA : public C2DEffects +{ +public: + void InitEffect(C2DEffectSAInterface* effect, std::uint32_t model, CEntitySAInterface* entity = nullptr, + std::vector* affectedEntities = nullptr) override; + void DeInitEffect(C2DEffectSAInterface* effect, std::uint32_t model, bool isCustomEffect = false, CEntitySAInterface* entity = nullptr) override; + void ReInitEffect(C2DEffectSAInterface* effect, std::uint32_t model, bool isCustomEffect = false, CEntitySAInterface* entity = nullptr) override; + void ReInitEffect(std::uint32_t model, std::uint32_t index) override; + + void Set2DFXEffectPosition(std::uint32_t model, std::uint32_t index, const CVector& position) override; + CVector Get2DFXEffectPosition(std::uint32_t model, std::uint32_t index) const override; + + e2dEffectType Get2DFXEffectType(C2DEffectSAInterface* effect) const override { return effect ? effect->type : e2dEffectType::NONE; } + e2dEffectType Get2DFXEffectType(std::uint32_t model, std::uint32_t index) const override { return Get2DFXEffectType(GetEffect(model, index)); }; + S2DEffectData Get2DFXPropertiesData(std::uint32_t model, std::uint32_t index) const override; + + void Set2DFXProperties(C2DEffectSAInterface* effectInterface, const S2DEffectData& effectProperties) override; + + void Set2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, float value) override; + void Set2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, bool value) override; + void Set2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, std::string& value) override; + void Set2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, e2dCoronaFlashType& value) override; + void Set2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, const std::vector& value) override; + void Reset2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, const S2DEffectData& originalProperties) override; + + void Get2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, float& outValue) const override; + void Get2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, bool& outValue) const override; + void Get2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, std::string& outValue) const override; + void Get2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, e2dCoronaFlashType& outValue) const override; + void Get2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, SColor& outValue) const override; + void Get2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, std::vector& outValue) const override; + + // Roadsign functions + void SetRoadsignTextColor(C2DEffectSAInterface* effect, const RwColor& color); + RwColor GetRoadsignTextColor(C2DEffectSAInterface* effect) const; + + void SetRoadsignText(C2DEffectSAInterface* effect, const std::string& text, std::uint8_t line); + + void UpdatePropertiesData(S2DEffectData& dst, const S2DEffectData& src, const e2dEffectProperty& property) const override; + +private: + C2DEffectSAInterface* GetEffect(std::uint32_t model, std::uint32_t index) const; + + // Roadsign functions + RpAtomic* Roadsign_CreateAtomic(const CVector& position, const RwV3d& rotation, float sizeX, float sizeY, std::uint32_t numLines, char* line1, char* line2, + char* line3, char* line4, std::uint32_t numLetters, std::uint8_t palleteID); + std::uint32_t Roadsign_GetPalleteIDFromFlags(std::uint8_t flags) const noexcept; + std::uint32_t Roadsign_GetNumLettersFromFlags(std::uint8_t flags) const noexcept; + std::uint32_t Roadsign_GetNumLinesFromFlags(std::uint8_t flags) const noexcept; + + bool IsPropertyValueNumber(const e2dEffectProperty& property) const noexcept; + bool IsPropertyValueString(const e2dEffectProperty& property) const noexcept; + bool IsPropertyValueTable(const e2dEffectProperty& property) const noexcept; +}; diff --git a/Client/game_sa/CEntitySA.h b/Client/game_sa/CEntitySA.h index 8cfff9d692..78d9215169 100644 --- a/Client/game_sa/CEntitySA.h +++ b/Client/game_sa/CEntitySA.h @@ -224,6 +224,10 @@ class CEntitySAInterface : public CPlaceableSAInterface ResolveReferences(); RemoveShadows(); } + + void TransformFromObjectSpace(CVector* outVector, CVector* offset) { + ((void(__thiscall*)(CEntitySAInterface*, CVector*, CVector*))0x5334F0)(this, outVector, offset); + } }; static_assert(sizeof(CEntitySAInterface) == 0x38, "Invalid size for CEntitySAInterface"); diff --git a/Client/game_sa/CFxSA.cpp b/Client/game_sa/CFxSA.cpp index 30107e4fa3..57f90e369d 100644 --- a/Client/game_sa/CFxSA.cpp +++ b/Client/game_sa/CFxSA.cpp @@ -246,7 +246,7 @@ void CFxSA::AddParticle(FxParticleSystems eFxParticle, const CVector& vecPositio FxPrtMult_c fxPrt{{fR,fG,fB,fA}, fSize, 0, fLife}; CVector newDirection; - FxSystem_c* fxParticleSystem; + FxSystem_cSAInterface* fxParticleSystem; switch (eFxParticle) { @@ -326,6 +326,25 @@ void CFxSA::AddParticle(FxParticleSystems eFxParticle, const CVector& vecPositio newDirection.fZ = (rand() % 10000) * 0.0001f * 4 - 2 + newDirection.fZ; // Call FxSystem_c::AddParticle - ((int(__thiscall*)(FxSystem_c*, const CVector*, const CVector*, float, FxPrtMult_c*, float, float, float, int))FUNC_FXSystem_c_AddParticle)(fxParticleSystem, &vecPosition, &newDirection, 0, &fxPrt, -1.0f, fBrightness, 0, 0); + ((int(__thiscall*)(FxSystem_cSAInterface*, const CVector*, const CVector*, float, FxPrtMult_c*, float, float, float, int))FUNC_FxSystem_c_AddParticle)(fxParticleSystem, &vecPosition, &newDirection, 0, &fxPrt, -1.0f, fBrightness, 0, 0); } } + +std::vector CFxSA::GetEntitiesFromFx(std::uint32_t modelID) +{ + auto vec = std::vector(); + void* lastParticle = g_Fx->m_lastParticleEntity; + + while (lastParticle) + { + auto** entity = reinterpret_cast(reinterpret_cast(lastParticle) + 0xC); + auto* prevParticle = *reinterpret_cast(reinterpret_cast(lastParticle) + 0x4); + + if (entity && *entity && (*entity)->m_nModelIndex == modelID) + vec.push_back(*entity); + + lastParticle = prevParticle; + } + + return vec; +} diff --git a/Client/game_sa/CFxSA.h b/Client/game_sa/CFxSA.h index efb7d0403f..9d9c265365 100644 --- a/Client/game_sa/CFxSA.h +++ b/Client/game_sa/CFxSA.h @@ -12,50 +12,149 @@ #pragma once #include +#include "CFxSystemBPSA.h" +#include "CVector.h" +#include "CRenderWareSA.h" -struct RwColor; -class FxSystem_c; - -#define FUNC_CFx_AddBlood 0x49eb00 -#define FUNC_CFx_AddWood 0x49ee10 -#define FUNC_CFx_AddSparks 0x49f040 -#define FUNC_CFx_AddTyreBurst 0x49f300 -#define FUNC_CFx_AddBulletImpact 0x49f3d0 -#define FUNC_CFx_AddPunchImpact 0x49f670 -#define FUNC_CFx_AddDebris 0x49f750 -#define FUNC_CFx_AddGlass 0x49f970 -#define FUNC_CFx_TriggerWaterHydrant 0x4a0d70 -#define FUNC_CFx_TriggerGunshot 0x4a0de0 -#define FUNC_CFx_TriggerTankFire 0x4a0fa0 -#define FUNC_CFx_TriggerWaterSplash 0x4a1070 -#define FUNC_CFx_TriggerBulletSplash 0x4a10e0 -#define FUNC_CFx_TriggerFootSplash 0x4a1150 -#define FUNC_FXSystem_c_AddParticle 0x4AA440 +class CEntitySAInterface; + +#define FUNC_CFx_AddBlood 0x49eb00 +#define FUNC_CFx_AddWood 0x49ee10 +#define FUNC_CFx_AddSparks 0x49f040 +#define FUNC_CFx_AddTyreBurst 0x49f300 +#define FUNC_CFx_AddBulletImpact 0x49f3d0 +#define FUNC_CFx_AddPunchImpact 0x49f670 +#define FUNC_CFx_AddDebris 0x49f750 +#define FUNC_CFx_AddGlass 0x49f970 +#define FUNC_CFx_TriggerWaterHydrant 0x4a0d70 +#define FUNC_CFx_TriggerGunshot 0x4a0de0 +#define FUNC_CFx_TriggerTankFire 0x4a0fa0 +#define FUNC_CFx_TriggerWaterSplash 0x4a1070 +#define FUNC_CFx_TriggerBulletSplash 0x4a10e0 +#define FUNC_CFx_TriggerFootSplash 0x4a1150 +#define FUNC_FxSystem_c_AddParticle 0x4AA440 + +enum class eFXQuality : std::uint32_t +{ + QUALITY_LOW = 0, + QUALITY_MEDIUM, + QUALITY_HIGH, + QUALITY_VERY_HIGH, +}; + +enum class eFxSystemPlayState : std::uint8_t +{ + PLAYING = 0, + STOPPED, + UNKNOWN, +}; + +enum class eFxSystemKillState : std::uint8_t +{ + NOT_KILLED = 0, + PLAY_AND_KILL, + KILLED, + UNKNOWN, +}; + +class FxSystem_cSAInterface +{ +public: + std::uint32_t m_link[2]; // ListItem_c + CFxSystemBPSAInterface* m_bluePrint; + void* m_transformMatrix; // RwMatrixTag* + std::uint8_t m_baseMatrix[64]; // RwMatrixTag + eFxSystemPlayState m_playState; + eFxSystemKillState m_killState; + bool m_useConstTime; + std::uint8_t field_53[2]; + float m_cameraDistance; + std::uint16_t m_constTime; + std::uint16_t m_rateMult; + std::uint16_t m_timeMult; + + union + { + struct + { + std::uint8_t m_hasOwnMatrix : 1; + std::uint8_t m_local : 1; + std::uint8_t m_useZTest : 1; + std::uint8_t m_stopParticleCreation : 1; + std::uint8_t m_prevCulled : 1; + std::uint8_t m_mustCreateParticles : 1; + }; + + std::uint8_t flags; + }; + + std::uint8_t field_63; + float m_loopInterval; + CVector m_velAdd; + void* m_boundingSphere; // CParticleBounding* or FxSphere_c* + void** m_primsList; // FxPrim_c** + std::uint8_t m_fireAE[136]; // CAEFireAudioEntity +}; class CFxSAInterface { public: - FxSystem_c* m_fxSysBlood; - FxSystem_c* m_fxSysBoatSplash; - FxSystem_c* m_fxSysBubble; - FxSystem_c* m_fxSysDebris; - FxSystem_c* m_fxSysSmoke; - FxSystem_c* m_fxSysGunshell; - FxSystem_c* m_fxSysSand; - FxSystem_c* m_fxSysSand2; - FxSystem_c* m_fxSysSmokeHuge; - FxSystem_c* m_fxSysSmoke2; - FxSystem_c* m_fxSysSpark; - FxSystem_c* m_fxSysSpark2; - FxSystem_c* m_fxSysSplash; - FxSystem_c* m_fxSysWake; - FxSystem_c* m_fxSysWaterSplash; - FxSystem_c* m_fxSysWheelDirt; - FxSystem_c* m_fxSysGlass; + FxSystem_cSAInterface* m_fxSysBlood; + FxSystem_cSAInterface* m_fxSysBoatSplash; + FxSystem_cSAInterface* m_fxSysBubble; + FxSystem_cSAInterface* m_fxSysDebris; + FxSystem_cSAInterface* m_fxSysSmoke; + FxSystem_cSAInterface* m_fxSysGunshell; + FxSystem_cSAInterface* m_fxSysSand; + FxSystem_cSAInterface* m_fxSysSand2; + FxSystem_cSAInterface* m_fxSysSmokeHuge; + FxSystem_cSAInterface* m_fxSysSmoke2; + FxSystem_cSAInterface* m_fxSysSpark; + FxSystem_cSAInterface* m_fxSysSpark2; + FxSystem_cSAInterface* m_fxSysSplash; + FxSystem_cSAInterface* m_fxSysWake; + FxSystem_cSAInterface* m_fxSysWaterSplash; + FxSystem_cSAInterface* m_fxSysWheelDirt; + FxSystem_cSAInterface* m_fxSysGlass; -private: + // List_c + void* m_lastParticleEntity; + void* m_firstParticleEntity; + std::uint32_t m_particleEntitiesCount; + + std::uint32_t m_numCreatedBloodPools; + eFXQuality m_fxQuality; + std::uint32_t m_verticesCount2; + std::uint32_t m_verticesCount; + std::uint32_t m_transformRenderFlags; + RwRaster* m_rasterToRender; + RwMatrix* m_transformLTM; + void* m_verts; // RxObjSpace3DVertex* + +public: + void CreateEntityFx(CEntitySAInterface* entity, const char* effectName, CVector* position) + { + if (!entity) + return; + + RwMatrix* matrixTransform = nullptr; + if (auto* object = reinterpret_cast(entity->m_pRwObject)) + { + if (auto* frame = static_cast(object->parent)) + matrixTransform = &frame->modelling; + } + + ((void(__thiscall*)(CFxSAInterface*, CEntitySAInterface*, const char*, CVector*, RwMatrix*))0x4A11E0)(this, entity, effectName, position, matrixTransform); + } + + void DestroyEntityFx(CEntitySAInterface* entity) + { + ((void(__thiscall*)(CFxSAInterface*, CEntitySAInterface*))0x4A1280)(this, entity); + } }; +static auto* g_Fx = reinterpret_cast(0xA9AE00); // g_Fx + class CFxSA : public CFx { public: @@ -78,6 +177,8 @@ class CFxSA : public CFx void TriggerFootSplash(CVector& vecPosition); void AddParticle(FxParticleSystems eFxParticle, const CVector& vecPosition, const CVector& vecDirection, float fR, float fG, float fB, float fA, bool bRandomizeColors, std::uint32_t iCount, float fBrightness, float fSize, bool bRandomizeSizes, float fLife); + static std::vector GetEntitiesFromFx(std::uint32_t modelID); + private: CFxSAInterface* m_pInterface; diff --git a/Client/game_sa/CGameSA.cpp b/Client/game_sa/CGameSA.cpp index 7cac177f42..bf6321cd8d 100644 --- a/Client/game_sa/CGameSA.cpp +++ b/Client/game_sa/CGameSA.cpp @@ -148,6 +148,7 @@ CGameSA::CGameSA() m_pPlantManager = new CPlantManagerSA(); m_pBuildingRemoval = new CBuildingRemovalSA(); m_pVehicleAudioSettingsManager = std::make_unique(); + m_p2DFXEffects = std::make_unique(); m_pRenderer = std::make_unique(); @@ -368,6 +369,28 @@ CModelInfo* CGameSA::GetModelInfo(DWORD dwModelID, bool bCanBeInvalid) return nullptr; } +CModelInfo* CGameSA::GetModelInfo(CBaseModelInfoSAInterface* baseModelInfo) +{ + static std::unordered_map s_cache; + + auto it = s_cache.find(baseModelInfo); + if (it != s_cache.end()) + return it->second; + + const std::size_t count = GetCountOfAllFileIDs(); + for (std::size_t i = 0; i < count; i++) + { + CModelInfoSA* modelInfo = &ModelInfo[i]; + if (modelInfo->IsValid() && modelInfo->GetInterface() == baseModelInfo) + { + s_cache[baseModelInfo] = modelInfo; + return modelInfo; + } + } + + return nullptr; +} + /** * Starts the game * \todo make addresses into constants @@ -1145,6 +1168,11 @@ void CGameSA::ResetAlphaTransparencies() CModelInfoSA::StaticResetAlphaTransparencies(); } +void CGameSA::Reset2DFXEffects() const +{ + CModelInfoSA::StaticReset2DFXEffects(); +} + // Disable VSync by forcing what normally happends at the end of the loading screens // Note #1: This causes the D3D device to be reset after the next frame // Note #2: Some players do not need this to disable VSync. (Possibly because their video card driver settings override it somewhere) diff --git a/Client/game_sa/CGameSA.h b/Client/game_sa/CGameSA.h index 9ed552b92d..498e0abcf5 100644 --- a/Client/game_sa/CGameSA.h +++ b/Client/game_sa/CGameSA.h @@ -19,6 +19,7 @@ #include "CPlantManagerSA.h" #include "CRendererSA.h" #include "CVehicleAudioSettingsManagerSA.h" +#include "C2DEffectsSA.h" class CAnimBlendClumpDataSAInterface; class CObjectGroupPhysicalPropertiesSA; @@ -175,6 +176,7 @@ class CGameSA : public CGame CPlantManagerSA* GetPlantManager() const noexcept { return m_pPlantManager; }; CBuildingRemoval* GetBuildingRemoval() { return m_pBuildingRemoval; } CRenderer* GetRenderer() const noexcept override { return m_pRenderer.get(); } + C2DEffects* Get2DFXEffects() const noexcept override { return m_p2DFXEffects.get(); } CVehicleAudioSettingsManager* GetVehicleAudioSettingsManager() const noexcept override { @@ -183,6 +185,7 @@ class CGameSA : public CGame CWeaponInfo* GetWeaponInfo(eWeaponType weapon, eWeaponSkill skill = WEAPONSKILL_STD); CModelInfo* GetModelInfo(DWORD dwModelID, bool bCanBeInvalid = false); + CModelInfo* GetModelInfo(CBaseModelInfoSAInterface* baseModelInfo); CObjectGroupPhysicalProperties* GetObjectGroupPhysicalProperties(unsigned char ucObjectGroup); int32_t GetBaseIDforDFF() { return 0; } @@ -302,6 +305,7 @@ class CGameSA : public CGame void ResetAlphaTransparencies(); void DisableVSync(); void ResetModelTimes(); + void Reset2DFXEffects() const override; void OnPedContextChange(CPed* pPedContext); CPed* GetPedContext(); @@ -361,6 +365,7 @@ class CGameSA : public CGame CBuildingRemoval* m_pBuildingRemoval; std::unique_ptr m_pVehicleAudioSettingsManager; + std::unique_ptr m_p2DFXEffects; std::unique_ptr m_pRenderer; diff --git a/Client/game_sa/CModelInfoSA.cpp b/Client/game_sa/CModelInfoSA.cpp index 8b7dd50071..8f773e60a4 100644 --- a/Client/game_sa/CModelInfoSA.cpp +++ b/Client/game_sa/CModelInfoSA.cpp @@ -19,6 +19,7 @@ #include "CPedSA.h" #include "CWorldSA.h" #include "gamesa_renderware.h" +#include "CFxSA.h" extern CCoreInterface* g_pCore; extern CGameSA* pGame; @@ -35,6 +36,7 @@ std::map CModelInfo std::unordered_map CModelInfoSA::ms_OriginalObjectPropertiesGroups; std::unordered_map> CModelInfoSA::ms_VehicleModelDefaultWheelSizes; std::map CModelInfoSA::ms_DefaultTxdIDMap; +std::unordered_set CModelInfoSA::ms_modified2DFXModels; union tIdeFlags { @@ -1751,7 +1753,7 @@ void CModelInfoSA::MakeObjectModel(ushort usBaseID) MemCpyFast(m_pInterface, pBaseObjectInfo, sizeof(CBaseModelInfoSAInterface)); m_pInterface->usNumberOfRefs = 0; m_pInterface->pRwObject = nullptr; - m_pInterface->usUnknown = 65535; + m_pInterface->s2DEffectIndex = -1; m_pInterface->usDynamicIndex = 65535; ppModelInfo[m_dwModelID] = m_pInterface; @@ -1768,7 +1770,7 @@ void CModelInfoSA::MakeObjectDamageableModel(std::uint16_t baseModel) MemCpyFast(m_pInterface, pBaseObjectInfo, sizeof(CDamageableModelInfoSAInterface)); m_pInterface->usNumberOfRefs = 0; m_pInterface->pRwObject = nullptr; - m_pInterface->usUnknown = 65535; + m_pInterface->s2DEffectIndex = -1; m_pInterface->usDynamicIndex = 65535; m_pInterface->m_damagedAtomic = nullptr; @@ -1786,7 +1788,7 @@ void CModelInfoSA::MakeTimedObjectModel(ushort usBaseID) MemCpyFast(m_pInterface, pBaseObjectInfo, sizeof(CTimeModelInfoSAInterface)); m_pInterface->usNumberOfRefs = 0; m_pInterface->pRwObject = nullptr; - m_pInterface->usUnknown = 65535; + m_pInterface->s2DEffectIndex = -1; m_pInterface->usDynamicIndex = 65535; m_pInterface->timeInfo.m_wOtherTimeModel = 0; @@ -1803,7 +1805,7 @@ void CModelInfoSA::MakeClumpModel(ushort usBaseID) MemCpyFast(pNewInterface, pBaseObjectInfo, sizeof(CClumpModelInfoSAInterface)); pNewInterface->usNumberOfRefs = 0; pNewInterface->pRwObject = nullptr; - pNewInterface->usUnknown = 65535; + pNewInterface->s2DEffectIndex = -1; pNewInterface->usDynamicIndex = 65535; ppModelInfo[m_dwModelID] = pNewInterface; @@ -1821,7 +1823,7 @@ void CModelInfoSA::MakeVehicleAutomobile(ushort usBaseID) m_pInterface->usNumberOfRefs = 0; m_pInterface->pRwObject = nullptr; m_pInterface->pVisualInfo = nullptr; - m_pInterface->usUnknown = 65535; + m_pInterface->s2DEffectIndex = -1; m_pInterface->usDynamicIndex = 65535; ppModelInfo[m_dwModelID] = m_pInterface; @@ -1865,58 +1867,6 @@ void CModelInfoSA::DeallocateModel(void) ppModelInfo[m_dwModelID] = nullptr; *pGame->GetStreaming()->GetStreamingInfo(m_dwModelID) = CStreamingInfo{}; } -////////////////////////////////////////////////////////////////////////////////////////// -// -// Hook for NodeNameStreamRead -// -// Ignore extra characters in dff frame name -// -////////////////////////////////////////////////////////////////////////////////////////// -__declspec(noinline) void OnMY_NodeNameStreamRead(RwStream* stream, char* pDest, uint uiSize) -{ - // Calc sizes - const uint uiMaxBufferSize = 24; - uint uiAmountToRead = std::min(uiMaxBufferSize - 1, uiSize); - uint uiAmountToSkip = uiSize - uiAmountToRead; - - // Read good bit - RwStreamRead(stream, pDest, uiAmountToRead); - pDest[uiAmountToRead] = 0; - - // Skip bad bit (this might not be required) - if (uiAmountToSkip > 0) - RwStreamSkip(stream, uiAmountToSkip); -} - -// Hook info -#define HOOKPOS_NodeNameStreamRead 0x072FA68 -#define HOOKSIZE_NodeNameStreamRead 15 -DWORD RETURN_NodeNameStreamRead = 0x072FA77; -void _declspec(naked) HOOK_NodeNameStreamRead() -{ - _asm - { - pushad - push edi - push esi - push ebx - call OnMY_NodeNameStreamRead - add esp, 4*3 - popad - - jmp RETURN_NodeNameStreamRead - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -// -// Setup hooks -// -////////////////////////////////////////////////////////////////////////////////////////// -void CModelInfoSA::StaticSetHooks() -{ - EZHookInstall(NodeNameStreamRead); -} // Recursive RwFrame children searching function void CModelInfoSA::RwSetSupportedUpgrades(RwFrame* parent, DWORD dwModel) @@ -2161,3 +2111,451 @@ bool CVehicleModelInfoSAInterface::IsComponentDamageable(int componentIndex) con { return pVisualInfo->m_maskComponentDamagable & (1 << componentIndex); } + +void CModelInfoSA::On2DFXEffectsLoaded(CEntitySAInterface* entity) +{ + // Destroy the removed effects + for (std::uint32_t index : m_removedGame2DFXIndices) + Remove2DFXEffect(index, false, entity); + + // Apply the saved properties and recreate the effect if necessary + for (std::size_t i = 0; i < Get2DFXEffectsCount(); i++) + { + C2DEffectSAInterface* effect = Get2DFXEffectByIndex(i); + if (!effect) + continue; + + auto it = m_modifiedPropertiesGame2DFXIndices.find(i); + bool modifiedGameEffect = it != m_modifiedPropertiesGame2DFXIndices.end(); + if (modifiedGameEffect) + pGame->Get2DFXEffects()->Set2DFXProperties(effect, it->second); + + // Re-create effect + bool custom = IsCustom2DFXEffect(effect); + if (modifiedGameEffect || custom) + { + // For custom effects, we need to recreate the roadsign effect + // to preserve the text color + if (effect->type != e2dEffectType::LIGHT && ((custom && effect->type == e2dEffectType::ROADSIGN) || !custom)) + pGame->Get2DFXEffects()->ReInitEffect(effect, m_dwModelID, false, entity); + } + } +} + +void CModelInfoSA::Reset2DFXEffects() +{ + m_removedGame2DFXIndices.clear(); + + // Destroy all custom effects from memory & update counter + std::size_t customCount = m_custom2DFXEffects.size(); + std::size_t startIndex = ppModelInfo[m_dwModelID]->ucNumOf2DEffects - customCount; + for (std::size_t i = 0; i < customCount; ++i) + Remove2DFXEffect(startIndex + customCount - 1 - i, false); + + m_custom2DFXEffects.clear(); + m_modifiedPropertiesGame2DFXIndices.clear(); + m_roadsignEffectsColors.clear(); + m_originalModel2DFXProperties.clear(); +} + +void CModelInfoSA::Store2DFXProperties(std::uint32_t index, const e2dEffectProperty& property) +{ + C2DEffectSAInterface* effect = Get2DFXEffectByIndex(index); + if (!effect) + return; + + // Save model as modified + ms_modified2DFXModels.insert(m_dwModelID); + + // Only store properties of game effects, + // because custom effects retain their properties after a restream + if (IsCustom2DFXEffect(effect)) + return; + + // Init new properties data + auto it = m_modifiedPropertiesGame2DFXIndices.find(index); + if (it == m_modifiedPropertiesGame2DFXIndices.end()) + { + S2DEffectData& data = pGame->Get2DFXEffects()->Get2DFXPropertiesData(m_dwModelID, index); + m_modifiedPropertiesGame2DFXIndices[index] = std::move(data); + } + else // Update existing data + { + S2DEffectData& data = it->second; + S2DEffectData& currentData = pGame->Get2DFXEffects()->Get2DFXPropertiesData(m_dwModelID, index); + pGame->Get2DFXEffects()->UpdatePropertiesData(data, currentData, property); + } +} + +void CModelInfoSA::Store2DFXDefaultProperties(std::uint32_t index) +{ + C2DEffectSAInterface* effect = Get2DFXEffectByIndex(index); + if (!effect) + return; + + // Only store properties of game effects + if (IsCustom2DFXEffect(effect)) + return; + + if (m_originalModel2DFXProperties.find(index) == m_originalModel2DFXProperties.end()) + { + S2DEffectData& data = pGame->Get2DFXEffects()->Get2DFXPropertiesData(m_dwModelID, index); + m_originalModel2DFXProperties[index] = std::move(data); + } +} + +bool CModelInfoSA::IsCustom2DFXEffect(C2DEffectSAInterface* effect) +{ + auto it = std::find_if( + m_custom2DFXEffects.begin(), + m_custom2DFXEffects.end(), + [effect](const std::unique_ptr& ptr) { + return ptr.get() == effect; + } + ); + + return it != m_custom2DFXEffects.end(); +} + +RwColor CModelInfoSA::GetStored2DFXRoadsignColor(std::uint32_t index) const +{ + auto it = m_roadsignEffectsColors.find(index); + return it != m_roadsignEffectsColors.end() ? it->second : RwColor{255, 255, 255, 255}; +} + +std::uint32_t CModelInfoSA::Get2DFXEffectIndex(C2DEffectSAInterface* effect) const +{ + if (!effect) + return 0; + + for (std::size_t i = 0; i < Get2DFXEffectsCount(effect); i++) + { + C2DEffectSAInterface* e = Get2DFXEffectByIndex(i); + if (!e) + continue; + + if (e == effect) + return i; + } + + return 0; +} + +bool CModelInfoSA::Add2DFXEffect(const e2dEffectType& effectType, const CVector& position, const S2DEffectData& effectProperties) +{ + CBaseModelInfoSAInterface* modelInterface = GetInterface(); + if (!modelInterface) + return false; + + // Allocate new effect + auto effectInterface = std::make_unique(); + effectInterface->position = position; + effectInterface->type = effectType; + + // Increase counter + if (modelInterface->ucNumOf2DEffects < 0) + modelInterface->ucNumOf2DEffects = 1; + else + modelInterface->ucNumOf2DEffects++; + + // Set properties & store roadsign text color + pGame->Get2DFXEffects()->Set2DFXProperties(effectInterface.get(), effectProperties); + if (effectType == e2dEffectType::ROADSIGN) + Store2DFXRoadsignColor(modelInterface->ucNumOf2DEffects - 1, RwColor{effectProperties.color.R, effectProperties.color.G, effectProperties.color.B, effectProperties.color.A}); + + // We need to place the effect in the array first, because + // InitEffect for roadsign calls Get2DFXEffectIndex, + // so the effect must already exist as a custom one at that point + // Otherwise, the roadsign will lose its custom text color + auto* effectPtr = effectInterface.get(); + m_custom2DFXEffects.push_back(std::move(effectInterface)); + pGame->Get2DFXEffects()->InitEffect(effectPtr, m_dwModelID); + + // Save model as modified + ms_modified2DFXModels.insert(m_dwModelID); + return true; +} + +bool CModelInfoSA::Remove2DFXEffect(std::uint32_t index, bool keepIndex, CEntitySAInterface* entity) +{ + C2DEffectSAInterface* effect = Get2DFXEffectByIndex(index); + if (!effect) + return false; + + // We need iterator here + auto it = std::find_if( + m_custom2DFXEffects.begin(), + m_custom2DFXEffects.end(), + [effect](const std::unique_ptr& ptr) { + return ptr.get() == effect; + } + ); + + bool isCustomEffect = it != m_custom2DFXEffects.end(); + if (keepIndex && !isCustomEffect) + m_removedGame2DFXIndices.insert(index); + + // Destroy visual 2dfx effect + pGame->Get2DFXEffects()->DeInitEffect(effect, m_dwModelID, isCustomEffect, entity); + + // Clear all properties + auto properties_it = m_modifiedPropertiesGame2DFXIndices.find(index); + if (properties_it != m_modifiedPropertiesGame2DFXIndices.end()) + m_modifiedPropertiesGame2DFXIndices.erase(properties_it); + + auto originalprop_it = m_originalModel2DFXProperties.find(index); + if (originalprop_it != m_originalModel2DFXProperties.end()) + m_originalModel2DFXProperties.erase(originalprop_it); + + // Clear text color for roadsign + if (effect->type == e2dEffectType::ROADSIGN) + { + auto it = m_roadsignEffectsColors.find(index); + if (it != m_roadsignEffectsColors.end()) + m_roadsignEffectsColors.erase(it); + } + + if (isCustomEffect) + { + // Delete effect from memory & decrease counter + m_custom2DFXEffects.erase(it); + ppModelInfo[m_dwModelID]->ucNumOf2DEffects--; + } + else + ms_modified2DFXModels.insert(m_dwModelID); // Save as modified + + return true; +} + +void CModelInfoSA::Restore2DFXEffect(std::uint32_t index) +{ + auto removed_it = m_removedGame2DFXIndices.find(index); + if (removed_it == m_removedGame2DFXIndices.end()) + return; + + m_removedGame2DFXIndices.erase(removed_it); + pGame->Get2DFXEffects()->ReInitEffect(m_dwModelID, index); +} + +bool CModelInfoSA::Reset2DFXEffectProperties(std::uint32_t index) +{ + C2DEffectSAInterface* effect = Get2DFXEffectByIndex(index); + if (!effect) + return false; + + if (IsCustom2DFXEffect(effect)) + return false; + + auto properties_it = m_modifiedPropertiesGame2DFXIndices.find(index); + if (properties_it != m_modifiedPropertiesGame2DFXIndices.end()) + { + auto originalprop_it = m_originalModel2DFXProperties.find(index); + if (originalprop_it != m_originalModel2DFXProperties.end()) + { + if (effect->type == e2dEffectType::ROADSIGN) + { + auto textColor_it = m_roadsignEffectsColors.find(index); + if (textColor_it != m_roadsignEffectsColors.end()) + m_roadsignEffectsColors.erase(textColor_it); + } + + pGame->Get2DFXEffects()->Set2DFXProperties(effect, originalprop_it->second); + if (effect->type != e2dEffectType::LIGHT) + pGame->Get2DFXEffects()->ReInitEffect(m_dwModelID, index); + + m_originalModel2DFXProperties.erase(originalprop_it); + return true; + } + + m_modifiedPropertiesGame2DFXIndices.erase(properties_it); + } + + return false; +} + +void CModelInfoSA::Reset2DFXProperty(std::uint32_t index, const e2dEffectProperty& property) +{ + C2DEffectSAInterface* effect = Get2DFXEffectByIndex(index); + if (!effect) + return; + + if (IsCustom2DFXEffect(effect)) + return; + + auto it = m_originalModel2DFXProperties.find(index); + if (it == m_originalModel2DFXProperties.end()) + return; + + if (effect->type == e2dEffectType::ROADSIGN && property == e2dEffectProperty::COLOR) + { + auto textColor_it = m_roadsignEffectsColors.find(index); + if (textColor_it != m_roadsignEffectsColors.end()) + m_roadsignEffectsColors.erase(textColor_it); + } + + // Reset modified property to original + auto modified_it = m_modifiedPropertiesGame2DFXIndices.find(index); + if (modified_it != m_modifiedPropertiesGame2DFXIndices.end()) + { + pGame->Get2DFXEffects()->UpdatePropertiesData(modified_it->second, it->second, property); + + // If all properties are the same + if (modified_it->second == it->second) + m_modifiedPropertiesGame2DFXIndices.erase(modified_it); + } + + pGame->Get2DFXEffects()->Reset2DFXProperty(m_dwModelID, index, property, it->second); + + // We don't need the original properties for this index anymore + if (modified_it == m_modifiedPropertiesGame2DFXIndices.end()) + m_originalModel2DFXProperties.erase(it); + + if (effect->type != e2dEffectType::LIGHT) + pGame->Get2DFXEffects()->ReInitEffect(effect, m_dwModelID, false); +} + +C2DEffectSAInterface* CModelInfoSA::Get2DFXEffectByIndex(std::uint32_t index) const +{ + if (!ppModelInfo[m_dwModelID]) + return nullptr; + + // Index out of bounds + if (index > ppModelInfo[m_dwModelID]->ucNumOf2DEffects - 1) + return nullptr; + + return ppModelInfo[m_dwModelID]->Get2dEffect(index); +} + +C2DEffectSAInterface* CModelInfoSA::Get2DFXEffect(CBaseModelInfoSAInterface* modelInfo, RpGeometry* geometry, std::uint32_t numPluginEffects, std::uint32_t index) +{ + if (!geometry) + numPluginEffects = 0; + + // C2dfxInfoStore d2fxModels + static auto* storedEffects = reinterpret_cast(0xB4C2D8); + + std::uint32_t numCustomEffects = m_custom2DFXEffects.size(); + std::uint32_t numStoredEffects = modelInfo->ucNumOf2DEffects - numPluginEffects - numCustomEffects; + + C2DEffectSAInterface* result = nullptr; + + if (index < numStoredEffects) + result = &storedEffects->objects[index + modelInfo->s2DEffectIndex]; + else if (index < numStoredEffects + numPluginEffects) + { + auto* pluginEffectData = *RWPLUGINOFFSET(C2DEffectPluginDataSAInterface*, geometry, *reinterpret_cast(0xC3A1E0)); // g2dEffectPluginOffset + result = &pluginEffectData->objects[index - numStoredEffects]; + } + else + result = m_custom2DFXEffects[index - numPluginEffects - numStoredEffects].get(); + + // Return null for "removed" effects + if (result && m_removedGame2DFXIndices.find(index) != m_removedGame2DFXIndices.end()) + return nullptr; + + return result; +} + +void CModelInfoSA::StaticReset2DFXEffects() +{ + // Reset all modified models + for (std::uint32_t model : ms_modified2DFXModels) + { + CModelInfo* modelInfo = pGame->GetModelInfo(model); + if (modelInfo) + modelInfo->Reset2DFXEffects(); + } + + ms_modified2DFXModels.clear(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +// Hook for CBaseModelInfo:Get2dEffect +// +// Handle custom 2dfx's +// +////////////////////////////////////////////////////////////////////////////////////////// +static C2DEffectSAInterface* StaticGet2dEffect(CBaseModelInfoSAInterface* modelInfo, RpGeometry* geometry, std::uint32_t numPluginEffects, std::uint32_t index) +{ + CModelInfo* ourModelInfo = pGame->GetModelInfo(modelInfo); + if (!ourModelInfo) + return nullptr; + + return ourModelInfo->Get2DFXEffect(modelInfo, geometry, numPluginEffects, index); +} + +#define HOOKPOS_Get2dEffect 0x4C4CDC +#define HOOKSIZE_Get2dEffect 10 +static void __declspec(naked) HOOK_Get2dEffect() +{ + _asm + { + push esi + push eax + push edi + push ebx + call StaticGet2dEffect + add esp, 16 + + pop edi + pop esi + pop ebp + pop ebx + retn 4 + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +// Hook for NodeNameStreamRead +// +// Ignore extra characters in dff frame name +// +////////////////////////////////////////////////////////////////////////////////////////// +__declspec(noinline) void OnMY_NodeNameStreamRead(RwStream* stream, char* pDest, uint uiSize) +{ + // Calc sizes + const uint uiMaxBufferSize = 24; + uint uiAmountToRead = std::min(uiMaxBufferSize - 1, uiSize); + uint uiAmountToSkip = uiSize - uiAmountToRead; + + // Read good bit + RwStreamRead(stream, pDest, uiAmountToRead); + pDest[uiAmountToRead] = 0; + + // Skip bad bit (this might not be required) + if (uiAmountToSkip > 0) + RwStreamSkip(stream, uiAmountToSkip); +} + +// Hook info +#define HOOKPOS_NodeNameStreamRead 0x072FA68 +#define HOOKSIZE_NodeNameStreamRead 15 +DWORD RETURN_NodeNameStreamRead = 0x072FA77; +void _declspec(naked) HOOK_NodeNameStreamRead() +{ + _asm + { + pushad + push edi + push esi + push ebx + call OnMY_NodeNameStreamRead + add esp, 4*3 + popad + + jmp RETURN_NodeNameStreamRead + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +// Setup hooks +// +////////////////////////////////////////////////////////////////////////////////////////// +void CModelInfoSA::StaticSetHooks() +{ + EZHookInstall(NodeNameStreamRead); + EZHookInstall(Get2dEffect); +} diff --git a/Client/game_sa/CModelInfoSA.h b/Client/game_sa/CModelInfoSA.h index d083209a2a..b12fb7676d 100644 --- a/Client/game_sa/CModelInfoSA.h +++ b/Client/game_sa/CModelInfoSA.h @@ -15,6 +15,9 @@ #include #include "CRenderWareSA.h" #include "game/RenderWare.h" +#include "C2DEffectSAInterface.h" +#include "game/C2DEffects.h" +#include class CPedModelInfoSA; class CPedModelInfoSAInterface; @@ -159,7 +162,7 @@ class CBaseModelInfoSAInterface unsigned char ucAlpha : 8; // +12 unsigned char ucNumOf2DEffects : 8; // +13 - unsigned short usUnknown : 16; // +14 Something with 2d effects + std::int16_t s2DEffectIndex; // +14 Something with 2d effects unsigned short usDynamicIndex : 16; // +16 @@ -232,6 +235,11 @@ class CBaseModelInfoSAInterface // +726 = Word array as referenced in CVehicleModelInfo::GetVehicleUpgrade(int) // +762 = Array of WORD containing something relative to paintjobs // +772 = Anim file index + + C2DEffectSAInterface* Get2dEffect(std::uint32_t index) + { + return ((C2DEffectSAInterface*(__thiscall*)(CBaseModelInfoSAInterface*, std::uint32_t))0x4C4C70)(this, index); + } }; static_assert(sizeof(CBaseModelInfoSAInterface) == 0x20, "Invalid size for CBaseModelInfoSAInterface"); @@ -356,6 +364,13 @@ class CModelInfoSA : public CModelInfo static std::map ms_DefaultTxdIDMap; SVehicleSupportedUpgrades m_ModelSupportedUpgrades; + static std::unordered_set ms_modified2DFXModels; + std::vector> m_custom2DFXEffects; + std::unordered_set m_removedGame2DFXIndices; + std::unordered_map m_modifiedPropertiesGame2DFXIndices; + std::unordered_map m_originalModel2DFXProperties; + std::unordered_map m_roadsignEffectsColors; + public: CModelInfoSA(); @@ -497,8 +512,31 @@ class CModelInfoSA : public CModelInfo static bool IsVehicleModel(std::uint32_t model) noexcept; + // 2dfx functions + bool Add2DFXEffect(const e2dEffectType& effectType, const CVector& position, const S2DEffectData& effectProperties) override; + bool Remove2DFXEffect(std::uint32_t index, bool keepIndex = true, CEntitySAInterface* entity = nullptr) override; + void Restore2DFXEffect(std::uint32_t index) override; + bool Reset2DFXEffectProperties(std::uint32_t index) override; + void Reset2DFXProperty(std::uint32_t index, const e2dEffectProperty& property) override; + + C2DEffectSAInterface* Get2DFXEffectByIndex(std::uint32_t index) const override; + C2DEffectSAInterface* Get2DFXEffect(CBaseModelInfoSAInterface* modelInfo, RpGeometry* geometry, std::uint32_t numPluginEffects, std::uint32_t index) override; + std::uint32_t Get2DFXEffectsCount(bool includeCustomEffects = true) const override { return m_pInterface ? (includeCustomEffects ? m_pInterface->ucNumOf2DEffects : m_pInterface->ucNumOf2DEffects - m_custom2DFXEffects.size()) : 0; } + + static void StaticReset2DFXEffects(); + private: void CopyStreamingInfoFromModel(ushort usCopyFromModelID); void RwSetSupportedUpgrades(RwFrame* parent, DWORD dwModel); void SetModelSpecialType(eModelSpecialType eType, bool bState); + + // 2dfx functions + void On2DFXEffectsLoaded(CEntitySAInterface* entity) override; + void Reset2DFXEffects() override; + void Store2DFXProperties(std::uint32_t index, const e2dEffectProperty& property) override; + void Store2DFXDefaultProperties(std::uint32_t index) override; + void Store2DFXRoadsignColor(std::uint32_t index, const RwColor& color) override {m_roadsignEffectsColors[index] = RwColor{color.r, color.g, color.b, color.a}; } + bool IsCustom2DFXEffect(C2DEffectSAInterface* effect) override; + RwColor GetStored2DFXRoadsignColor(std::uint32_t index) const override; + std::uint32_t Get2DFXEffectIndex(C2DEffectSAInterface* effect) const override; }; diff --git a/Client/game_sa/gamesa_renderware.h b/Client/game_sa/gamesa_renderware.h index bd1cf14323..7180357232 100644 --- a/Client/game_sa/gamesa_renderware.h +++ b/Client/game_sa/gamesa_renderware.h @@ -105,7 +105,8 @@ typedef RpHAnimHierarchy*(__cdecl* GetAnimHierarchyFromSkinClump_t)(RpClump*); typedef int(__cdecl* RpHAnimIDGetIndex_t)(RpHAnimHierarchy*, int); typedef RwMatrix*(__cdecl* RpHAnimHierarchyGetMatrixArray_t)(RpHAnimHierarchy*); typedef RtQuat*(__cdecl* RtQuatRotate_t)(RtQuat* quat, const RwV3d* axis, float angle, RwOpCombineType combineOp); - +typedef RwTexture*(__cdecl* RwReadTexture_t)(const char* name, const char* mask); +typedef RwFrame*(__cdecl* RwFrameRotate_t)(RwFrame* frame, const RwV3d* axis, float angle, RwOpCombineType combine); /*****************************************************************************/ /** Renderware function mappings **/ /*****************************************************************************/ @@ -195,6 +196,8 @@ RWFUNC(GetAnimHierarchyFromSkinClump_t GetAnimHierarchyFromSkinClump, (GetAnimHi RWFUNC(RpHAnimIDGetIndex_t RpHAnimIDGetIndex, (RpHAnimIDGetIndex_t)0xDEAD) RWFUNC(RpHAnimHierarchyGetMatrixArray_t RpHAnimHierarchyGetMatrixArray, (RpHAnimHierarchyGetMatrixArray_t)0xDEAD) RWFUNC(RtQuatRotate_t RtQuatRotate, (RtQuatRotate_t)0xDEAD) +RWFUNC(RwReadTexture_t RwReadTexture, reinterpret_cast(0xDEAD)) +RWFUNC(RwFrameRotate_t RwFrameRotate, reinterpret_cast(0xDEAD)) /*****************************************************************************/ /** GTA function definitions and mappings **/ @@ -213,6 +216,9 @@ typedef void(__cdecl* CTxdStore_RemoveRef_t)(unsigned int id); typedef void(__cdecl* CTxdStore_AddRef_t)(unsigned int id); typedef int(__cdecl* CTxdStore_GetNumRefs_t)(unsigned int id); typedef RwTexDictionary*(__cdecl* CTxdStore_GetTxd_t)(unsigned int id); +typedef RwTexDictionary*(__cdecl* CTxdStore_PushCurrentTxd_t)(); +typedef void(__cdecl* CTxdStore_PopCurrentTxd_t)(); +typedef std::uint32_t(__cdecl* CTxdStore_FindTxdSlot_t)(const char* name); typedef RwTexture*(__cdecl* CClothesBuilder_CopyTexture_t)(RwTexture* texture); RWFUNC(SetTextureDict_t SetTextureDict, (SetTextureDict_t)0xDEAD) @@ -227,6 +233,9 @@ RWFUNC(CTxdStore_RemoveTxd_t CTxdStore_RemoveTxd, (CTxdStore_RemoveTxd_t)0xDEAD) RWFUNC(CTxdStore_RemoveRef_t CTxdStore_RemoveRef, (CTxdStore_RemoveRef_t)0xDEAD) RWFUNC(CTxdStore_AddRef_t CTxdStore_AddRef, (CTxdStore_AddRef_t)0xDEAD) RWFUNC(CTxdStore_GetNumRefs_t CTxdStore_GetNumRefs, (CTxdStore_GetNumRefs_t)0xDEAD) +RWFUNC(CTxdStore_PushCurrentTxd_t CTxdStore_PushCurrentTxd, reinterpret_cast(0xDEAD)) +RWFUNC(CTxdStore_PopCurrentTxd_t CTxdStore_PopCurrentTxd, reinterpret_cast(0xDEAD)) +RWFUNC(CTxdStore_FindTxdSlot_t CTxdStore_FindTxdSlot, reinterpret_cast(0xDEAD)) RWFUNC(CClothesBuilder_CopyTexture_t CClothesBuilder_CopyTexture, (CClothesBuilder_CopyTexture_t)0xDEAD) /*****************************************************************************/ diff --git a/Client/game_sa/gamesa_renderware.hpp b/Client/game_sa/gamesa_renderware.hpp index 86da3e1958..7bbf039918 100644 --- a/Client/game_sa/gamesa_renderware.hpp +++ b/Client/game_sa/gamesa_renderware.hpp @@ -89,7 +89,9 @@ void InitRwFunctions() RpHAnimIDGetIndex = (RpHAnimIDGetIndex_t)0x7C51A0; RpHAnimHierarchyGetMatrixArray = (RpHAnimHierarchyGetMatrixArray_t)0x7C5120; RtQuatRotate = (RtQuatRotate_t)0x7EB7C0; - + RwReadTexture = reinterpret_cast(0x7F3AC0); + RwFrameRotate = reinterpret_cast(0x7F1010); + SetTextureDict = (SetTextureDict_t)0x007319C0; LoadClumpFile = (LoadClumpFile_t)0x005371F0; LoadModel = (LoadModel_t)0x0040C6B0; @@ -102,5 +104,9 @@ void InitRwFunctions() CTxdStore_RemoveRef = (CTxdStore_RemoveRef_t)0x00731A30; CTxdStore_AddRef = (CTxdStore_AddRef_t)0x00731A00; CTxdStore_GetNumRefs = (CTxdStore_GetNumRefs_t)0x00731AA0; + CTxdStore_PushCurrentTxd = reinterpret_cast(0x7316A0); + CTxdStore_PopCurrentTxd = reinterpret_cast(0x7316B0); + CTxdStore_FindTxdSlot = reinterpret_cast(0x731850); + CClothesBuilder_CopyTexture = (CClothesBuilder_CopyTexture_t)0x005A5730; } diff --git a/Client/mods/deathmatch/StdInc.h b/Client/mods/deathmatch/StdInc.h index 9cbbb90f8b..0f5ea46166 100644 --- a/Client/mods/deathmatch/StdInc.h +++ b/Client/mods/deathmatch/StdInc.h @@ -146,6 +146,7 @@ #include #include #include +#include #include // Shared includes diff --git a/Client/mods/deathmatch/logic/CClient2DFXManager.cpp b/Client/mods/deathmatch/logic/CClient2DFXManager.cpp new file mode 100644 index 0000000000..954187a933 --- /dev/null +++ b/Client/mods/deathmatch/logic/CClient2DFXManager.cpp @@ -0,0 +1,680 @@ +#include "StdInc.h" +#include "CClient2DFXManager.h" +#include "game/C2DEffects.h" + +bool CClient2DFXManager::InitFlagsFromTable(const e2dEffectType& effectType, const effectFlagsTable& tbl, std::uint16_t& flags) +{ + switch (effectType) + { + case e2dEffectType::LIGHT: + { + auto GetBoolValue = [&tbl](const e2dEffectFlags& key) -> bool + { + auto it = tbl.find(key); + if (it == tbl.end()) + return false; + + if (auto val = std::get_if(&(it->second))) + return *val; + + return false; + }; + + if (GetBoolValue(e2dEffectFlags::CHECK_OBSTACLES)) + flags |= (1 << 0); + if (GetBoolValue(e2dEffectFlags::FOG_TYPE)) + flags |= (1 << 1); + if (GetBoolValue(e2dEffectFlags::FOG_TYPE2)) + flags |= (1 << 2); + if (GetBoolValue(e2dEffectFlags::WITHOUT_CORONA)) + flags |= (1 << 3); + if (GetBoolValue(e2dEffectFlags::ONLY_LONG_DISTANCE)) + flags |= (1 << 4); + if (GetBoolValue(e2dEffectFlags::AT_DAY)) + flags |= (1 << 5); + if (GetBoolValue(e2dEffectFlags::AT_NIGHT)) + flags |= (1 << 6); + if (GetBoolValue(e2dEffectFlags::BLINKING1)) + flags |= (1 << 7); + + if (GetBoolValue(e2dEffectFlags::ONLY_FROM_BELOW)) + flags |= (1 << 8); + if (GetBoolValue(e2dEffectFlags::BLINKING2)) + flags |= (1 << 9); + if (GetBoolValue(e2dEffectFlags::UPDATE_HEIGHT_ABOVE_GROUND)) + flags |= (1 << 10); + if (GetBoolValue(e2dEffectFlags::CHECK_DIRECTION)) + flags |= (1 << 11); + if (GetBoolValue(e2dEffectFlags::BLINKING3)) + flags |= (1 << 12); + + break; + } + case e2dEffectType::ROADSIGN: + { + auto GetIntValue = [&tbl](const e2dEffectFlags& key, int defaultValue = 0) -> int + { + auto it = tbl.find(key); + if (it == tbl.end()) + return defaultValue; + + if (auto val = std::get_if(&it->second)) + return *val; + + return defaultValue; + }; + + int lines = GetIntValue(e2dEffectFlags::LINES_NUM); + int linesBits = lines == 4 ? 0 : lines; + flags |= (linesBits & 0b11); + + int characters = GetIntValue(e2dEffectFlags::CHARACTERS_PER_LINE); + int charactersBits = 0; + + if (lines < 1 || lines > 4) + return false; + + if (characters != 2 && characters != 4 && characters != 8 && characters != 16) + return false; + + if (characters == 16) + charactersBits = 0; + else if (characters == 8) + charactersBits = 3; + else if (characters == 4) + charactersBits = 2; + else if (characters == 2) + charactersBits = 1; + + flags |= (charactersBits & 0b11) << 2; + break; + } + + default: + break; + } + + return true; +} + +effectFlagsTable CClient2DFXManager::InitFlagsTable(const e2dEffectType& effectType, std::uint16_t flags) +{ + effectFlagsTable tbl; + + switch (effectType) + { + case e2dEffectType::LIGHT: + { + auto SetFlag = [&tbl, flags](const e2dEffectFlags& key, int bit) + { + if (flags & (1 << bit)) + tbl[key] = true; + }; + + SetFlag(e2dEffectFlags::CHECK_OBSTACLES, 0); + SetFlag(e2dEffectFlags::FOG_TYPE, 1); + SetFlag(e2dEffectFlags::FOG_TYPE2, 2); + SetFlag(e2dEffectFlags::WITHOUT_CORONA, 3); + SetFlag(e2dEffectFlags::ONLY_LONG_DISTANCE, 4); + SetFlag(e2dEffectFlags::AT_DAY, 5); + SetFlag(e2dEffectFlags::AT_NIGHT, 6); + SetFlag(e2dEffectFlags::BLINKING1, 7); + + SetFlag(e2dEffectFlags::ONLY_FROM_BELOW, 8); + SetFlag(e2dEffectFlags::BLINKING2, 9); + SetFlag(e2dEffectFlags::UPDATE_HEIGHT_ABOVE_GROUND, 10); + SetFlag(e2dEffectFlags::CHECK_DIRECTION, 11); + SetFlag(e2dEffectFlags::BLINKING3, 12); + + break; + } + case e2dEffectType::ROADSIGN: + { + int linesBits = flags & 0b11; + int lines = linesBits == 0 ? 4 : linesBits; + tbl[e2dEffectFlags::LINES_NUM] = lines; + + int charactersBits = (flags >> 2) & 0b11; + int characters = 16; + + if (charactersBits == 0) + characters = 16; + else if (charactersBits == 3) + characters = 8; + else if (charactersBits == 2) + characters = 4; + else if (charactersBits == 1) + characters = 2; + + tbl[e2dEffectFlags::CHARACTERS_PER_LINE] = characters; + + break; + } + + default: + break; + } + + return tbl; +} + +S2DEffectData CClient2DFXManager::InitProperties(const e2dEffectType& effectType, const effectPropertiesMap& properties) +{ + S2DEffectData data; + + switch (effectType) + { + case e2dEffectType::LIGHT: + { + e2dCoronaFlashType showMode = e2dCoronaFlashType::DEFAULT; + StringToEnum(std::get(*MapFind(properties, "showMode")), showMode); + + data.drawDistance = std::get(*MapFind(properties, "drawDistance")); + data.lightRange = std::get(*MapFind(properties, "lightRange")); + data.coronaSize = std::get(*MapFind(properties, "coronaSize")); + data.shadowSize = std::get(*MapFind(properties, "shadowSize")); + data.shadowMult = std::get(*MapFind(properties, "shadowMultiplier")); + data.coronaFlashType = showMode; + data.coronaReflection = std::get(*MapFind(properties, "coronaReflection")); + data.coronaFlareType = std::get(*MapFind(properties, "flareType")); + data.color = TOCOLOR2SCOLOR(static_cast(std::get(*MapFind(properties, "color")))); + data.shadowDist = std::get(*MapFind(properties, "shadowDistance")); + + auto* flags = MapFind(properties, "flags"); + if (std::holds_alternative(*flags)) + data.flags = static_cast(std::get(*flags)); + else if (std::holds_alternative(*flags)) + { + if (!InitFlagsFromTable(effectType, std::get(*flags), data.flags)) + data.flags = 0; + } + + auto& offset = std::get>(*MapFind(properties, "offset")); + data.offset.x = static_cast(offset[0]); + data.offset.y = static_cast(offset[1]); + data.offset.z = static_cast(offset[2]); + + data.coronaName = std::get(*MapFind(properties, "coronaName")); + data.shadowName = std::get(*MapFind(properties, "shadowName")); + break; + } + case e2dEffectType::PARTICLE: + { + data.prt_name = std::get(*MapFind(properties, "name")); + break; + } + case e2dEffectType::ROADSIGN: + { + auto& size = std::get>(*MapFind(properties, "size")); + data.size = CVector2D(size[0], size[1]); + + auto& rot = std::get>(*MapFind(properties, "rotation")); + data.rot = CVector(rot[0], rot[1], rot[2]); + + data.flags = std::get(*MapFind(properties, "flags")); + data.color = TOCOLOR2SCOLOR(static_cast(std::get(*MapFind(properties, "color")))); + data.text_1 = std::get(*MapFind(properties, "text1")); + data.text_2 = std::get(*MapFind(properties, "text2")); + data.text_3 = std::get(*MapFind(properties, "text3")); + data.text_4 = std::get(*MapFind(properties, "text4")); + break; + } + case e2dEffectType::ESCALATOR: + { + auto& bottom = std::get>(*MapFind(properties, "bottom")); + data.bottom = CVector(bottom[0], bottom[1], bottom[2]); + + auto& top = std::get>(*MapFind(properties, "top")); + data.top = CVector(top[0], top[1], top[2]); + + auto& end = std::get>(*MapFind(properties, "end")); + data.end = CVector(end[0], end[1], end[2]); + + data.direction = std::get(*MapFind(properties, "direction")); + break; + } + default: + break; + } + + return data; +} + +effectPropertiesMap CClient2DFXManager::GetProperties(std::uint32_t model, std::uint32_t index) +{ + auto tbl = effectPropertiesMap(); + C2DEffects* effects = g_pGame->Get2DFXEffects(); + + switch (effects->Get2DFXEffectType(model, index)) + { + case e2dEffectType::LIGHT: + { + float drawDist = 0.0f; + float lightRange = 0.0f; + float coronaSize = 0.0f; + float shadowSize = 0.0f; + float shadowMult = 0.0f; + float shadowDist = 0.0f; + float flareType = 0.0f; + float flags = 0; + + std::vector offset; + offset.reserve(3); + + e2dCoronaFlashType flashType = e2dCoronaFlashType::DEFAULT; + + bool coronaReflection = false; + std::string coronaName{}; + std::string shadowName{}; + SColor color{}; + + effects->Get2DFXProperty(model, index, e2dEffectProperty::FAR_CLIP_DISTANCE, drawDist); + effects->Get2DFXProperty(model, index, e2dEffectProperty::LIGHT_RANGE, lightRange); + effects->Get2DFXProperty(model, index, e2dEffectProperty::CORONA_SIZE, coronaSize); + effects->Get2DFXProperty(model, index, e2dEffectProperty::SHADOW_SIZE, shadowSize); + effects->Get2DFXProperty(model, index, e2dEffectProperty::SHADOW_MULT, shadowMult); + effects->Get2DFXProperty(model, index, e2dEffectProperty::SHADOW_DISTANCE, shadowDist); + effects->Get2DFXProperty(model, index, e2dEffectProperty::FLARE_TYPE, flareType); + effects->Get2DFXProperty(model, index, e2dEffectProperty::FLAGS, flags); + effects->Get2DFXProperty(model, index, e2dEffectProperty::FLASH_TYPE, flashType); + effects->Get2DFXProperty(model, index, e2dEffectProperty::CORONA_REFLECTION, coronaReflection); + effects->Get2DFXProperty(model, index, e2dEffectProperty::CORONA_NAME, coronaName); + effects->Get2DFXProperty(model, index, e2dEffectProperty::SHADOW_NAME, shadowName); + effects->Get2DFXProperty(model, index, e2dEffectProperty::COLOR, color); + effects->Get2DFXProperty(model, index, e2dEffectProperty::OFFSET, offset); + + tbl["drawDistance"] = drawDist; + tbl["lightRange"] = lightRange; + tbl["coronaSize"] = coronaSize; + tbl["shadowSize"] = shadowSize; + tbl["shadowMultiplier"] = shadowMult; + tbl["shadowDistance"] = shadowDist; + tbl["flareType"] = flareType; + tbl["showMode"] = EnumToString(flashType); + tbl["coronaReflection"] = coronaReflection; + tbl["flags"] = flags; + tbl["offset"] = offset; + tbl["coronaName"] = coronaName; + tbl["shadowName"] = shadowName; + tbl["color"] = + std::vector{static_cast(color.R), static_cast(color.G), static_cast(color.B), static_cast(color.A)}; + + break; + } + case e2dEffectType::PARTICLE: + { + std::string name{}; + effects->Get2DFXProperty(model, index, e2dEffectProperty::PRT_NAME, name); + + tbl["name"] = name; + break; + } + case e2dEffectType::ROADSIGN: + { + std::vector size; + std::vector rot; + size.reserve(2); + rot.reserve(3); + + float flags = 0; + std::string text_1{}; + std::string text_2{}; + std::string text_3{}; + std::string text_4{}; + SColor color{}; + + effects->Get2DFXProperty(model, index, e2dEffectProperty::SIZE, size); + effects->Get2DFXProperty(model, index, e2dEffectProperty::ROT, rot); + effects->Get2DFXProperty(model, index, e2dEffectProperty::FLAGS, flags); + effects->Get2DFXProperty(model, index, e2dEffectProperty::TEXT_1, text_1); + effects->Get2DFXProperty(model, index, e2dEffectProperty::TEXT_2, text_2); + effects->Get2DFXProperty(model, index, e2dEffectProperty::TEXT_3, text_3); + effects->Get2DFXProperty(model, index, e2dEffectProperty::TEXT_4, text_4); + effects->Get2DFXProperty(model, index, e2dEffectProperty::COLOR, color); + + tbl["size"] = size; + tbl["rotation"] = rot; + tbl["flags"] = flags; + tbl["text1"] = text_1; + tbl["text2"] = text_2; + tbl["text3"] = text_3; + tbl["text4"] = text_4; + tbl["color"] = + std::vector{static_cast(color.R), static_cast(color.G), static_cast(color.B), static_cast(color.A)}; + + break; + } + case e2dEffectType::ESCALATOR: + { + std::vector bottom; + std::vector top; + std::vector end; + bottom.reserve(3); + top.reserve(3); + end.reserve(3); + + float direction{}; + + effects->Get2DFXProperty(model, index, e2dEffectProperty::BOTTOM, bottom); + effects->Get2DFXProperty(model, index, e2dEffectProperty::TOP, top); + effects->Get2DFXProperty(model, index, e2dEffectProperty::END, end); + effects->Get2DFXProperty(model, index, e2dEffectProperty::DIRECTION, direction); + + tbl["bottom"] = bottom; + tbl["top"] = top; + tbl["end"] = end; + tbl["direction"] = direction; + + break; + } + default: + break; + } + + return tbl; +} + +const char* CClient2DFXManager::IsValidEffectData(const e2dEffectType& effectType, const effectPropertiesMap& effectData) +{ + // Check if keys & values are ok! + switch (effectType) + { + case e2dEffectType::LIGHT: + { + // corona far clip + auto* drawDistance = MapFind(effectData, "drawDistance"); + if (!drawDistance || !std::holds_alternative(*drawDistance)) + return "Invalid \"drawDistance\" value"; + + auto* lightRange = MapFind(effectData, "lightRange"); + if (!lightRange || !std::holds_alternative(*lightRange)) + return "Invalid \"lightRange\" value"; + + auto* coronaSize = MapFind(effectData, "coronaSize"); + if (!coronaSize || !std::holds_alternative(*coronaSize)) + return "Invalid \"coronaSize\" value"; + + auto* shadowSize = MapFind(effectData, "shadowSize"); + if (!shadowSize || !std::holds_alternative(*shadowSize)) + return "Invalid \"shadowSize\" value"; + + auto* shadowMultiplier = MapFind(effectData, "shadowMultiplier"); + if (!shadowMultiplier || !std::holds_alternative(*shadowMultiplier) || std::get(*shadowMultiplier) < 0.0f) + return "Invalid \"shadowMultiplier\" value"; + + auto* showMode = MapFind(effectData, "showMode"); + e2dCoronaFlashType tempType; + if (!showMode || !std::holds_alternative(*showMode) || !StringToEnum(std::get(*showMode), tempType)) + return "Invalid \"showMode\" value"; + + auto* coronaReflection = MapFind(effectData, "coronaReflection"); + if (!coronaReflection || !std::holds_alternative(*coronaReflection)) + return "Invalid \"coronaReflection\" value"; + + auto* coronaFlareType = MapFind(effectData, "flareType"); + if (!coronaFlareType || !std::holds_alternative(*coronaFlareType) || + (std::get(*coronaFlareType) < 0.0f || std::get(*coronaFlareType) > 1.0f)) + return "Invalid \"flareType\" value"; + + auto* flags = MapFind(effectData, "flags"); + bool flt = (std::holds_alternative(*flags) && std::get(*flags) >= 0); + bool tb = std::holds_alternative(*flags); + bool validFlags = (std::holds_alternative(*flags) && std::get(*flags) >= 0) || std::holds_alternative(*flags); + if (!flags || !validFlags) + return "Invalid \"flags\" value"; + + auto* shadowZDistance = MapFind(effectData, "shadowDistance"); + if (!shadowZDistance || (!std::holds_alternative(*shadowZDistance))) + return "Invalid \"shadowDistance\" value"; + + auto* offset = MapFind(effectData, "offset"); + if (!offset || !std::holds_alternative>(*offset) || std::get>(*offset).size() < 3) + return "Invalid \"offset\" value"; + + auto* color = MapFind(effectData, "color"); + if (!color || !std::holds_alternative(*color)) + return "Invalid \"color\" value"; + + auto* coronaTexture = MapFind(effectData, "coronaName"); + e2dEffectTextureName tempName; + if (!coronaTexture || !std::holds_alternative(*coronaTexture) || !StringToEnum(std::get(*coronaTexture), tempName)) + return "Invalid \"coronaName\" value"; + + auto* shadowTexture = MapFind(effectData, "shadowName"); + e2dEffectTextureName shadowName; + if (!shadowTexture || !std::holds_alternative(*shadowTexture) || !StringToEnum(std::get(*shadowTexture), shadowName)) + return "Invalid \"shadowName\" value"; + + break; + } + case e2dEffectType::PARTICLE: + { + auto* particleName = MapFind(effectData, "name"); + if (!particleName || !std::holds_alternative(*particleName) || std::get(*particleName).length() > 24) + return "Invalid \"particle name\" value"; + + break; + } + case e2dEffectType::ROADSIGN: + { + auto* size = MapFind(effectData, "size"); + if (!size || !std::holds_alternative>(*size) || std::get>(*size).size() < 2) + return "Invalid \"size\" value"; + + auto* rot = MapFind(effectData, "rotation"); + if (!rot || !std::holds_alternative>(*rot) || std::get>(*rot).size() < 3) + return "Invalid \"rotation\" value"; + + auto* flags = MapFind(effectData, "flags"); + bool validFlags = (std::holds_alternative(*flags) && std::get(*flags) >= 0) || std::holds_alternative(*flags); + if (!flags || !validFlags) + return "Invalid \"flags\" value"; + + auto* text = MapFind(effectData, "text1"); + if (!text || !std::holds_alternative(*text)) + return "Invalid \"text1\" value"; + + auto* text2 = MapFind(effectData, "text2"); + if (!text2 || !std::holds_alternative(*text2)) + return "Invalid \"text2\" value"; + + auto* text3 = MapFind(effectData, "text3"); + if (!text3 || !std::holds_alternative(*text3)) + return "Invalid \"text3\" value"; + + auto* text4 = MapFind(effectData, "text4"); + if (!text4 || !std::holds_alternative(*text4)) + return "Invalid \"text4\" value"; + + auto* color = MapFind(effectData, "color"); + if (!color || !std::holds_alternative(*color)) + return "Invalid \"color\" value"; + + break; + } + case e2dEffectType::ESCALATOR: + { + auto* bottom = MapFind(effectData, "bottom"); + if (!bottom || !std::holds_alternative>(*bottom) || std::get>(*bottom).size() < 3) + return "Invalid \"bottom\" value"; + + auto* top = MapFind(effectData, "top"); + if (!top || !std::holds_alternative>(*top) || std::get>(*top).size() < 3) + return "Invalid \"top\" value"; + + auto* end = MapFind(effectData, "end"); + if (!end || !std::holds_alternative>(*end) || std::get>(*end).size() < 3) + return "Invalid \"end\" value"; + + auto* direction = MapFind(effectData, "direction"); + if (!direction || !std::holds_alternative(*direction) || (std::get(*direction) < 0.0f || std::get(*direction) > 1.0f)) + return "Invalid \"direction\" value"; + + break; + } + case e2dEffectType::SUN_GLARE: // It has no properties + break; + // Unnecessary in MTA + case e2dEffectType::ATTRACTOR: + case e2dEffectType::FURNITURE: + case e2dEffectType::ENEX: + case e2dEffectType::TRIGGER_POINT: + case e2dEffectType::COVER_POINT: + break; + } + + return nullptr; +} + +bool CClient2DFXManager::IsValidPropertyValue(const e2dEffectType& effectType, const e2dEffectProperty& property, + std::variant, effectFlagsTable> value) +{ + bool isValid = false; + switch (property) + { + case e2dEffectProperty::FAR_CLIP_DISTANCE: + case e2dEffectProperty::LIGHT_RANGE: + case e2dEffectProperty::CORONA_SIZE: + case e2dEffectProperty::SHADOW_SIZE: + case e2dEffectProperty::SHADOW_MULT: + case e2dEffectProperty::SHADOW_DISTANCE: + case e2dEffectProperty::FLARE_TYPE: + case e2dEffectProperty::COLOR: + case e2dEffectProperty::DIRECTION: + { + isValid = std::holds_alternative(value); + if (isValid) + { + float val = std::get(value); + if (val < 0.0f && property != e2dEffectProperty::COLOR) + isValid = false; + + if (val > 1.0f && (property == e2dEffectProperty::FLARE_TYPE || property == e2dEffectProperty::DIRECTION)) + isValid = false; + } + break; + } + case e2dEffectProperty::OFFSET: + case e2dEffectProperty::SIZE: + case e2dEffectProperty::ROT: + case e2dEffectProperty::BOTTOM: + case e2dEffectProperty::TOP: + case e2dEffectProperty::END: + { + isValid = std::holds_alternative>(value); + if (isValid) + { + auto& val = std::get>(value); + if ((property == e2dEffectProperty::SIZE && val.size() < 2) || (property != e2dEffectProperty::SIZE && val.size() < 3)) + isValid = false; + } + + break; + } + case e2dEffectProperty::CORONA_REFLECTION: + { + isValid = std::holds_alternative(value); + break; + } + case e2dEffectProperty::FLASH_TYPE: + { + if (std::holds_alternative(value)) + { + e2dCoronaFlashType tempVal; + isValid = StringToEnum(std::get(value), tempVal); + } + break; + } + case e2dEffectProperty::CORONA_NAME: + case e2dEffectProperty::SHADOW_NAME: + { + if (std::holds_alternative(value)) + { + e2dEffectTextureName tempVal; + isValid = StringToEnum(std::get(value), tempVal); + } + break; + } + case e2dEffectProperty::PRT_NAME: + case e2dEffectProperty::TEXT_1: + case e2dEffectProperty::TEXT_2: + case e2dEffectProperty::TEXT_3: + case e2dEffectProperty::TEXT_4: + { + isValid = std::holds_alternative(value); + break; + } + case e2dEffectProperty::FLAGS: + { + isValid = std::holds_alternative(value) || std::holds_alternative(value); + if (isValid) + { + if (std::holds_alternative(value) && std::get(value) < 0) + isValid = false; + + if (std::holds_alternative(value)) + { + effectFlagsTable tbl = std::get(value); + if (tbl.empty()) + isValid = false; + + std::uint16_t temp = 0; + if (!InitFlagsFromTable(effectType, tbl, temp)) + isValid = false; + } + } + + break; + } + } + + if (isValid) + isValid = IsValidProperty(effectType, property); + + return isValid; +} + +bool CClient2DFXManager::IsValidProperty(const e2dEffectType& effectType, const e2dEffectProperty& property) +{ + switch (property) + { + case e2dEffectProperty::FAR_CLIP_DISTANCE: + case e2dEffectProperty::LIGHT_RANGE: + case e2dEffectProperty::CORONA_SIZE: + case e2dEffectProperty::SHADOW_SIZE: + case e2dEffectProperty::SHADOW_MULT: + case e2dEffectProperty::FLASH_TYPE: + case e2dEffectProperty::CORONA_REFLECTION: + case e2dEffectProperty::FLARE_TYPE: + case e2dEffectProperty::SHADOW_DISTANCE: + case e2dEffectProperty::OFFSET: + case e2dEffectProperty::CORONA_NAME: + case e2dEffectProperty::SHADOW_NAME: + return (effectType == e2dEffectType::LIGHT); + case e2dEffectProperty::COLOR: + case e2dEffectProperty::FLAGS: + return (effectType == e2dEffectType::LIGHT || effectType == e2dEffectType::ROADSIGN); + case e2dEffectProperty::PRT_NAME: + return (effectType == e2dEffectType::PARTICLE); + case e2dEffectProperty::SIZE: + case e2dEffectProperty::ROT: + case e2dEffectProperty::TEXT_1: + case e2dEffectProperty::TEXT_2: + case e2dEffectProperty::TEXT_3: + case e2dEffectProperty::TEXT_4: + return (effectType == e2dEffectType::ROADSIGN); + case e2dEffectProperty::BOTTOM: + case e2dEffectProperty::TOP: + case e2dEffectProperty::END: + case e2dEffectProperty::DIRECTION: + return (effectType == e2dEffectType::ESCALATOR); + default: + return false; + } +} + +bool CClient2DFXManager::IsValidModel(std::uint32_t model) +{ + return CClientObjectManager::IsValidModel(model) || CClientBuildingManager::IsValidModel(model) || CClientVehicleManager::IsValidModel(model) || + CClientPlayerManager::IsValidModel(model); +} diff --git a/Client/mods/deathmatch/logic/CClient2DFXManager.h b/Client/mods/deathmatch/logic/CClient2DFXManager.h new file mode 100644 index 0000000000..52f66ecd13 --- /dev/null +++ b/Client/mods/deathmatch/logic/CClient2DFXManager.h @@ -0,0 +1,29 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: Client/mods/deathmatch/logic/CClient2DFXManager.h + * PURPOSE: Handling 2DFX effects manager + * + *****************************************************************************/ +#pragma once + +class CClient2DFXManager +{ +public: + CClient2DFXManager(class CClientManager* mainManager) : m_mainManager(mainManager) {}; + + static bool InitFlagsFromTable(const e2dEffectType& effectType, const effectFlagsTable& tbl, std::uint16_t& flags); + static effectFlagsTable InitFlagsTable(const e2dEffectType& effectType, std::uint16_t flags); + + static S2DEffectData InitProperties(const e2dEffectType& effectType, const effectPropertiesMap& properties); + static effectPropertiesMap GetProperties(std::uint32_t model, std::uint32_t index); + + static bool IsValidModel(std::uint32_t model); + static const char* IsValidEffectData(const e2dEffectType& effectType, const effectPropertiesMap& effectData); + static bool IsValidPropertyValue(const e2dEffectType& effectType, const e2dEffectProperty& property, std::variant, effectFlagsTable> value); + static bool IsValidProperty(const e2dEffectType& effectType, const e2dEffectProperty& property); + +private: + class CClientManager* m_mainManager; +}; diff --git a/Client/mods/deathmatch/logic/CClientGame.cpp b/Client/mods/deathmatch/logic/CClientGame.cpp index 25f440fab5..dd1ab79ae4 100644 --- a/Client/mods/deathmatch/logic/CClientGame.cpp +++ b/Client/mods/deathmatch/logic/CClientGame.cpp @@ -3446,6 +3446,7 @@ void CClientGame::Event_OnIngame() g_pGame->ResetModelFlags(); g_pGame->ResetAlphaTransparencies(); g_pGame->ResetModelTimes(); + g_pGame->Reset2DFXEffects(); // Reset weapon render g_pGame->SetWeaponRenderEnabled(true); diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp index 1a53834762..df4170014d 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.cpp @@ -977,6 +977,117 @@ ADD_ENUM(VehicleAudioSettingProperty::ENGINE_UPGRADE, "engine-upgrade") ADD_ENUM(VehicleAudioSettingProperty::VEHICLE_TYPE_FOR_AUDIO, "vehicle-type-for-audio") IMPLEMENT_ENUM_CLASS_END("vehicle-audio-setting") +IMPLEMENT_ENUM_CLASS_BEGIN(e2dEffectType) +ADD_ENUM(e2dEffectType::LIGHT, "light") +ADD_ENUM(e2dEffectType::PARTICLE, "particle") +ADD_ENUM(e2dEffectType::SUN_GLARE, "sun_glare") +ADD_ENUM(e2dEffectType::ROADSIGN, "roadsign") +ADD_ENUM(e2dEffectType::ESCALATOR, "escalator") +ADD_ENUM(e2dEffectType::UNKNOWN, "unknown") +ADD_ENUM(e2dEffectType::NONE, "unknown") +IMPLEMENT_ENUM_CLASS_END("2dfx-type") + +IMPLEMENT_ENUM_CLASS_BEGIN(e2dCoronaFlashType) +ADD_ENUM(e2dCoronaFlashType::DEFAULT, "default") +ADD_ENUM(e2dCoronaFlashType::RANDOM, "random_flashing") +ADD_ENUM(e2dCoronaFlashType::RANDOM_WHEN_WET, "random_at_wet_weather") +ADD_ENUM(e2dCoronaFlashType::ANIM_SPEED_4X, "anim_speed_4x") +ADD_ENUM(e2dCoronaFlashType::ANIM_SPEED_2X, "anim_speed_2x") +ADD_ENUM(e2dCoronaFlashType::ANIM_SPEED_1X, "anim_speed_1x") +ADD_ENUM(e2dCoronaFlashType::WARNLIGHT, "warnlight") +ADD_ENUM(e2dCoronaFlashType::TRAFFICLIGHT, "trafficlight") +ADD_ENUM(e2dCoronaFlashType::TRAINCROSSING, "traincrosslight") +ADD_ENUM(e2dCoronaFlashType::ONLY_RAIN, "at_rain_only") +ADD_ENUM(e2dCoronaFlashType::ON5_OFF5, "on_off_at_5") +ADD_ENUM(e2dCoronaFlashType::ON6_OFF4, "on_at_6_off_at_4") +ADD_ENUM(e2dCoronaFlashType::ON4_OFF6, "on_at_4_off_at_6") +IMPLEMENT_ENUM_CLASS_END("2dfx-light-mode") + +IMPLEMENT_ENUM_CLASS_BEGIN(e2dEffectTextureName) +ADD_ENUM(e2dEffectTextureName::CORONA_STAR, "coronastar") +ADD_ENUM(e2dEffectTextureName::SHAD_HELI, "shad_heli") +ADD_ENUM(e2dEffectTextureName::SHAD_EXP, "shad_exp") +ADD_ENUM(e2dEffectTextureName::SHAD_CAR, "shad_car") +ADD_ENUM(e2dEffectTextureName::SHAD_BIKE, "shad_bike") +ADD_ENUM(e2dEffectTextureName::SEABD32, "seabd32") +ADD_ENUM(e2dEffectTextureName::ROADSIGNFONT, "roadsignfont") +ADD_ENUM(e2dEffectTextureName::PARTICLESKID, "particleskid") +ADD_ENUM(e2dEffectTextureName::LUNAR, "lunar") +ADD_ENUM(e2dEffectTextureName::LOCKONFIRE, "lockonFire") +ADD_ENUM(e2dEffectTextureName::LOCKON, "lockon") +ADD_ENUM(e2dEffectTextureName::LAMP_SHAD_64, "lamp_shad_64") +ADD_ENUM(e2dEffectTextureName::HEADLIGHT1, "headlight1") +ADD_ENUM(e2dEffectTextureName::HEADLIGHT, "headlight") +ADD_ENUM(e2dEffectTextureName::HANDMAN, "handman") +ADD_ENUM(e2dEffectTextureName::FINISHFLAG, "finishflag") +ADD_ENUM(e2dEffectTextureName::CORONARINGB, "coronaringb") +ADD_ENUM(e2dEffectTextureName::CORONAREFLECT, "coronareflect") +ADD_ENUM(e2dEffectTextureName::CORONAMOON, "coronamoon") +ADD_ENUM(e2dEffectTextureName::CORONAHEADLIGHTLINE, "coronaheadlightline") +ADD_ENUM(e2dEffectTextureName::CLOUDMASKED, "cloudmasked") +ADD_ENUM(e2dEffectTextureName::CLOUDHIGH, "cloudhigh") +ADD_ENUM(e2dEffectTextureName::CLOUD1, "cloud1") +ADD_ENUM(e2dEffectTextureName::CARFX1, "carfx1") +ADD_ENUM(e2dEffectTextureName::BLOODPOOL_64, "bloodpool_64") +ADD_ENUM(e2dEffectTextureName::WINCRACK_32, "wincrack_32") +ADD_ENUM(e2dEffectTextureName::WHITE, "white") +ADD_ENUM(e2dEffectTextureName::WATERWAKE, "waterwake") +ADD_ENUM(e2dEffectTextureName::WATERCLEAR256, "waterclear256") +ADD_ENUM(e2dEffectTextureName::TXGRASSBIG1, "txgrassbig1") +ADD_ENUM(e2dEffectTextureName::TXGRASSBIG0, "txgrassbig0") +ADD_ENUM(e2dEffectTextureName::SHAD_RCBARON, "shad_rcbaron") +ADD_ENUM(e2dEffectTextureName::SHAD_PED, "shad_ped") +IMPLEMENT_ENUM_CLASS_END("2dfx-texture-name") + +IMPLEMENT_ENUM_CLASS_BEGIN(e2dEffectProperty) +ADD_ENUM(e2dEffectProperty::FAR_CLIP_DISTANCE, "drawDistance") +ADD_ENUM(e2dEffectProperty::LIGHT_RANGE, "lightRange") +ADD_ENUM(e2dEffectProperty::CORONA_SIZE, "coronaSize") +ADD_ENUM(e2dEffectProperty::SHADOW_SIZE, "shadowSize") +ADD_ENUM(e2dEffectProperty::SHADOW_MULT, "shadowMultiplier") +ADD_ENUM(e2dEffectProperty::FLASH_TYPE, "showMode") +ADD_ENUM(e2dEffectProperty::CORONA_REFLECTION, "coronaReflection") +ADD_ENUM(e2dEffectProperty::FLARE_TYPE, "flareType") +ADD_ENUM(e2dEffectProperty::SHADOW_DISTANCE, "shadowDistance") +ADD_ENUM(e2dEffectProperty::OFFSET, "offset") +ADD_ENUM(e2dEffectProperty::COLOR, "color") +ADD_ENUM(e2dEffectProperty::CORONA_NAME, "coronaName") +ADD_ENUM(e2dEffectProperty::SHADOW_NAME, "shadowName") +ADD_ENUM(e2dEffectProperty::FLAGS, "flags") + +ADD_ENUM(e2dEffectProperty::PRT_NAME, "name") + +ADD_ENUM(e2dEffectProperty::SIZE, "size") +ADD_ENUM(e2dEffectProperty::ROT, "rotation") +ADD_ENUM(e2dEffectProperty::TEXT_1, "text1") +ADD_ENUM(e2dEffectProperty::TEXT_2, "text2") +ADD_ENUM(e2dEffectProperty::TEXT_3, "text3") +ADD_ENUM(e2dEffectProperty::TEXT_4, "text4") + +ADD_ENUM(e2dEffectProperty::BOTTOM, "bottom") +ADD_ENUM(e2dEffectProperty::TOP, "top") +ADD_ENUM(e2dEffectProperty::END, "end") +ADD_ENUM(e2dEffectProperty::DIRECTION, "direction") +IMPLEMENT_ENUM_CLASS_END("2dfx-property-name") + +IMPLEMENT_ENUM_CLASS_BEGIN(e2dEffectFlags) +ADD_ENUM(e2dEffectFlags::AT_DAY, "atDay") +ADD_ENUM(e2dEffectFlags::AT_NIGHT, "atNight") +ADD_ENUM(e2dEffectFlags::BLINKING1, "blinking1") +ADD_ENUM(e2dEffectFlags::BLINKING2, "blinking2") +ADD_ENUM(e2dEffectFlags::BLINKING3, "blinking3") +ADD_ENUM(e2dEffectFlags::FOG_TYPE, "fogType") +ADD_ENUM(e2dEffectFlags::FOG_TYPE2, "fogType2") +ADD_ENUM(e2dEffectFlags::CHECK_OBSTACLES, "checkObstacles") +ADD_ENUM(e2dEffectFlags::CHECK_DIRECTION, "checkDirection") +ADD_ENUM(e2dEffectFlags::ONLY_FROM_BELOW, "onlyFromBelow") +ADD_ENUM(e2dEffectFlags::ONLY_LONG_DISTANCE, "onlyLongDistance") +ADD_ENUM(e2dEffectFlags::UPDATE_HEIGHT_ABOVE_GROUND, "updateHeightAboveGround") +ADD_ENUM(e2dEffectFlags::WITHOUT_CORONA, "withoutCorona") + +ADD_ENUM(e2dEffectFlags::LINES_NUM, "lines") +ADD_ENUM(e2dEffectFlags::CHARACTERS_PER_LINE, "charactersPerLine") +IMPLEMENT_ENUM_CLASS_END("2dfx-flag-name") // // CResource from userdata diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h index da35c0d1cc..ddc8247082 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h @@ -101,6 +101,50 @@ DECLARE_ENUM_CLASS(PreloadAreaOption); DECLARE_ENUM_CLASS(taskType); DECLARE_ENUM(eEntityType); DECLARE_ENUM_CLASS(VehicleAudioSettingProperty); +DECLARE_ENUM_CLASS(e2dEffectType); +DECLARE_ENUM_CLASS(e2dCoronaFlashType); +DECLARE_ENUM_CLASS(e2dEffectProperty); +DECLARE_ENUM_CLASS(e2dEffectFlags); + +// For corona name & shadow name +enum class e2dEffectTextureName +{ + CORONA_STAR, + SHAD_HELI, + SHAD_EXP, + SHAD_CAR, + SHAD_BIKE, + SEABD32, + ROADSIGNFONT, + PARTICLESKID, + LUNAR, + LOCKONFIRE, + LOCKON, + LAMP_SHAD_64, + HEADLIGHT1, + HEADLIGHT, + HANDMAN, + FINISHFLAG, + CORONARINGB, + CORONAREFLECT, + CORONAMOON, + CORONAHEADLIGHTLINE, + CLOUDMASKED, + CLOUDHIGH, + CLOUD1, + CARFX1, + BLOODPOOL_64, + WINCRACK_32, + WHITE, + WATERWAKE, + WATERCLEAR256, + TXGRASSBIG1, + TXGRASSBIG0, + TARGET256, + SHAD_RCBARON, + SHAD_PED, +}; +DECLARE_ENUM_CLASS(e2dEffectTextureName); class CRemoteCall; diff --git a/Client/mods/deathmatch/logic/lua/CLuaManager.cpp b/Client/mods/deathmatch/logic/lua/CLuaManager.cpp index 89dadab332..c69696e4f7 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaManager.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaManager.cpp @@ -283,4 +283,5 @@ void CLuaManager::LoadCFunctions() CLuaClientDefs::LoadFunctions(); CLuaDiscordDefs::LoadFunctions(); CLuaBuildingDefs::LoadFunctions(); + CLua2DFXDefs::LoadFunctions(); } diff --git a/Client/mods/deathmatch/logic/luadefs/CLua2DFXDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLua2DFXDefs.cpp new file mode 100644 index 0000000000..d05fce1772 --- /dev/null +++ b/Client/mods/deathmatch/logic/luadefs/CLua2DFXDefs.cpp @@ -0,0 +1,367 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/shared_logic/luadefs/CLua2DFXDefs.cpp + * PURPOSE: Lua definitions class + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" +#include "CLua2DFXDefs.h" +#include +#include +#include + +void CLua2DFXDefs::LoadFunctions() +{ + constexpr static const std::pair functions[]{ + // Create/destroy functions + {"addModel2DFX", ArgumentParser}, + {"removeModel2DFX", ArgumentParser}, + {"restoreModel2DFX", ArgumentParser}, + {"resetModel2DFXEffects", ArgumentParser}, + + // Set functions + {"setModel2DFXPosition", ArgumentParser}, + {"setModel2DFXProperty", ArgumentParser}, + {"resetModel2DFXProperty", ArgumentParser}, + {"resetModel2DFXPosition", ArgumentParser}, + + // Get functions + {"getModel2DFXPosition", ArgumentParser}, + {"getModel2DFXProperty", ArgumentParser}, + {"getModel2DFXEffects", ArgumentParser}, + {"getModel2DFXCount", ArgumentParser}, + {"getModel2DFXType", ArgumentParser}, + }; + + // Add functions + for (const auto& [name, func] : functions) + CLuaCFunctions::AddFunction(name, func); +} + +bool CLua2DFXDefs::AddModel2DFX(std::uint32_t model, CVector position, e2dEffectType effectType, effectPropertiesMap effectData) +{ + if (effectType == e2dEffectType::NONE || effectType == e2dEffectType::UNKNOWN) + return false; + + if (!CClient2DFXManager::IsValidModel(model)) + throw std::invalid_argument("Invalid model ID"); + + const char* error = CClient2DFXManager::IsValidEffectData(effectType, effectData); + if (error) + throw LuaFunctionError(error); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(model); + if (!modelInfo) + return false; + + bool effect = modelInfo->Add2DFXEffect(effectType, position, CClient2DFXManager::InitProperties(effectType, effectData)); + if (effect && effectType == e2dEffectType::PARTICLE) + { + if (CClientObjectManager::IsValidModel(model)) + m_pManager->GetObjectManager()->RestreamObjects(model); + else if (CClientVehicleManager::IsValidModel(model)) + m_pManager->GetVehicleManager()->RestreamVehicles(model); + else if (CClientPlayerManager::IsValidModel(model)) + m_pManager->GetPedManager()->RestreamPeds(model); + } + + return effect; +} + +bool CLua2DFXDefs::RemoveModel2DFX(std::uint32_t model, std::uint32_t index) +{ + if (!CClient2DFXManager::IsValidModel(model)) + throw std::invalid_argument("Invalid model ID"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(model); + if (!modelInfo) + return false; + + if (index > GetModel2DFXCount(model, true) - 1) + throw std::invalid_argument("Index out of bounds"); + + return modelInfo->Remove2DFXEffect(index); +} + +void CLua2DFXDefs::RestoreModel2DFX(std::uint32_t model, std::uint32_t index) +{ + if (!CClient2DFXManager::IsValidModel(model)) + throw std::invalid_argument("Invalid model ID"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(model); + if (!modelInfo) + return; + + if (index > GetModel2DFXCount(model, true) - 1) + throw std::invalid_argument("Index out of bounds"); + + modelInfo->Restore2DFXEffect(index); +} + +void CLua2DFXDefs::ResetModel2DFXEffects(std::uint32_t model) +{ + if (!CClient2DFXManager::IsValidModel(model)) + throw std::invalid_argument("Invalid model ID"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(model); + if (!modelInfo) + return; + + // Restore original properties + for (std::size_t i = 0; i < modelInfo->Get2DFXEffectsCount(); i++) + modelInfo->Reset2DFXEffectProperties(i); + + // Destroy custom effects & restore "removed" original effects + modelInfo->Reset2DFXEffects(); +} + +void CLua2DFXDefs::SetModel2DFXPosition(std::uint32_t model, std::uint32_t index, CVector position) +{ + if (!CClient2DFXManager::IsValidModel(model)) + throw std::invalid_argument("Invalid model ID"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(model); + if (!modelInfo) + return; + + if (index > GetModel2DFXCount(model, true) - 1) + throw std::invalid_argument("Index out of bounds"); + + // Save original position + modelInfo->Store2DFXDefaultProperties(index); + g_pGame->Get2DFXEffects()->Set2DFXEffectPosition(model, index, position); + modelInfo->Store2DFXProperties(index, e2dEffectProperty::POSITION); +} + +bool CLua2DFXDefs::SetModel2DFXProperty(std::uint32_t model, std::uint32_t index, e2dEffectProperty property, + std::variant, effectFlagsTable> propertyValue) +{ + if (!CClient2DFXManager::IsValidModel(model)) + throw std::invalid_argument("Invalid model ID"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(model); + if (!modelInfo) + return false; + + if (index > GetModel2DFXCount(model, true) - 1) + throw std::invalid_argument("Index out of bounds"); + + const e2dEffectType& type = g_pGame->Get2DFXEffects()->Get2DFXEffectType(model, index); + + if (!CClient2DFXManager::IsValidPropertyValue(type, property, propertyValue)) + throw std::invalid_argument("Invalid property value"); + + // Save original properties + modelInfo->Store2DFXDefaultProperties(index); + + // We could use std::visit here, but CGameSA and CMultiplayerSA are in C++14, so... + if (property == e2dEffectProperty::FLASH_TYPE) + { + e2dCoronaFlashType val; + StringToEnum(std::get(propertyValue), val); + g_pGame->Get2DFXEffects()->Set2DFXProperty(model, index, property, val); + } + else if (property == e2dEffectProperty::FLAGS && std::holds_alternative(propertyValue)) + { + std::uint16_t flags = 0; + if (!CClient2DFXManager::InitFlagsFromTable(type, std::get(propertyValue), flags)) + return false; + + g_pGame->Get2DFXEffects()->Set2DFXProperty(model, index, property, static_cast(flags)); + } + else if (std::holds_alternative(propertyValue)) + g_pGame->Get2DFXEffects()->Set2DFXProperty(model, index, property, std::get(propertyValue)); + else if (std::holds_alternative(propertyValue)) + g_pGame->Get2DFXEffects()->Set2DFXProperty(model, index, property, std::get(propertyValue)); + else if (std::holds_alternative(propertyValue)) + g_pGame->Get2DFXEffects()->Set2DFXProperty(model, index, property, std::get(propertyValue)); + else if (std::holds_alternative>(propertyValue)) + g_pGame->Get2DFXEffects()->Set2DFXProperty(model, index, property, std::get>(propertyValue)); + + modelInfo->Store2DFXProperties(index, property); + + if (type != e2dEffectType::LIGHT) + g_pGame->Get2DFXEffects()->ReInitEffect(model, index); + + return true; +} + +void CLua2DFXDefs::ResetModel2DFXProperty(std::uint32_t model, std::uint32_t index, e2dEffectProperty property) +{ + if (!CClient2DFXManager::IsValidModel(model)) + throw std::invalid_argument("Invalid model ID"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(model); + if (!modelInfo) + return; + + if (index > GetModel2DFXCount(model, true) - 1) + throw std::invalid_argument("Index out of bounds"); + + if (!CClient2DFXManager::IsValidProperty(g_pGame->Get2DFXEffects()->Get2DFXEffectType(model, index), property)) + throw std::invalid_argument("Invalid property for this type of effect"); + + modelInfo->Reset2DFXProperty(index, property); +} + +void CLua2DFXDefs::ResetModel2DFXPosition(std::uint32_t model, std::uint32_t index) +{ + if (!CClient2DFXManager::IsValidModel(model)) + throw std::invalid_argument("Invalid model ID"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(model); + if (!modelInfo) + return; + + if (index > GetModel2DFXCount(model, true) - 1) + throw std::invalid_argument("Index out of bounds"); + + modelInfo->Reset2DFXProperty(index, e2dEffectProperty::POSITION); +} + +std::variant> CLua2DFXDefs::GetModel2DFXPosition(std::uint32_t model, std::uint32_t index) +{ + if (!CClient2DFXManager::IsValidModel(model)) + throw std::invalid_argument("Invalid model ID"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(model); + if (!modelInfo) + return false; + + if (index > GetModel2DFXCount(model, true) - 1) + throw std::invalid_argument("Index out of bounds"); + + CVector& pos = g_pGame->Get2DFXEffects()->Get2DFXEffectPosition(model, index); + return CLuaMultiReturn{pos.fX, pos.fY, pos.fZ}; +} + +std::variant, CLuaMultiReturn, + effectFlagsTable> +CLua2DFXDefs::GetModel2DFXProperty(std::uint32_t model, std::uint32_t index, e2dEffectProperty property, std::optional getFlagsAsTable) +{ + if (!CClient2DFXManager::IsValidModel(model)) + throw std::invalid_argument("Invalid model ID"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(model); + if (!modelInfo) + return false; + + if (index > GetModel2DFXCount(model, true) - 1) + throw std::invalid_argument("Index out of bounds"); + + if (!CClient2DFXManager::IsValidProperty(g_pGame->Get2DFXEffects()->Get2DFXEffectType(model, index), property)) + throw std::invalid_argument("Invalid property for this type of effect"); + + auto* effects = g_pGame->Get2DFXEffects(); + + std::variant, CLuaMultiReturn, + effectFlagsTable> + valueToReturn; + if (effects->IsPropertyValueNumber(property)) + { + float val{}; + effects->Get2DFXProperty(model, index, property, val); + + valueToReturn = val; + + if (property == e2dEffectProperty::FLAGS && getFlagsAsTable.value_or(false)) + valueToReturn = CClient2DFXManager::InitFlagsTable(effects->Get2DFXEffectType(model, index), static_cast(val)); + } + else if (effects->IsPropertyValueString(property)) + { + std::string val{}; + effects->Get2DFXProperty(model, index, property, val); + + valueToReturn = std::move(val); + } + else if (effects->IsPropertyValueTable(property)) + { + std::vector val; + val.reserve(property == e2dEffectProperty::SIZE ? 2 : 3); + effects->Get2DFXProperty(model, index, property, val); + + valueToReturn = CLuaMultiReturn{val[0], val[1], val.size() > 2 ? val[2] : 0}; + } + else + { + switch (property) + { + case e2dEffectProperty::CORONA_REFLECTION: + { + bool val = false; + effects->Get2DFXProperty(model, index, property, val); + + valueToReturn = val; + break; + } + case e2dEffectProperty::FLASH_TYPE: + { + e2dCoronaFlashType val = e2dCoronaFlashType::DEFAULT; + effects->Get2DFXProperty(model, index, property, val); + + std::string str = EnumToString(val); + valueToReturn = std::move(str); + break; + } + case e2dEffectProperty::COLOR: + { + SColor val{}; + effects->Get2DFXProperty(model, index, property, val); + + valueToReturn = CLuaMultiReturn{val.R, val.G, val.B, val.A}; + break; + } + } + } + + return valueToReturn; +} + +std::variant CLua2DFXDefs::GetModel2DFXEffects(std::uint32_t model, std::optional includeCustomEffects) +{ + if (!CClient2DFXManager::IsValidModel(model)) + throw std::invalid_argument("Invalid model ID"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(model); + if (!modelInfo) + return false; + + auto tbl = effectsMap(); + C2DEffects* effects = g_pGame->Get2DFXEffects(); + + for (std::size_t i = 0; i < modelInfo->Get2DFXEffectsCount(includeCustomEffects.value_or(true)); i++) + { + CVector& position = effects->Get2DFXEffectPosition(model, i); + const e2dEffectType& type = effects->Get2DFXEffectType(model, i); + + tbl[i] = { + {"position", std::vector{position.fX, position.fY, position.fZ}}, + {"type", EnumToString(type)}, + {"properties", CClient2DFXManager::GetProperties(model, i)}, + }; + } + + return tbl; +} + +std::uint32_t CLua2DFXDefs::GetModel2DFXCount(std::uint32_t model, std::optional includeCustomEffects) +{ + if (!CClient2DFXManager::IsValidModel(model)) + throw std::invalid_argument("Invalid model ID"); + + CModelInfo* modelInfo = g_pGame->GetModelInfo(model); + if (!modelInfo) + return 0; + + return modelInfo->Get2DFXEffectsCount(includeCustomEffects.value_or(true)); +} + +e2dEffectType CLua2DFXDefs::GetModel2DFXType(std::uint32_t model, std::uint32_t index) +{ + return g_pGame->Get2DFXEffects()->Get2DFXEffectType(model, index); +} diff --git a/Client/mods/deathmatch/logic/luadefs/CLua2DFXDefs.h b/Client/mods/deathmatch/logic/luadefs/CLua2DFXDefs.h new file mode 100644 index 0000000000..cf7bd24ee2 --- /dev/null +++ b/Client/mods/deathmatch/logic/luadefs/CLua2DFXDefs.h @@ -0,0 +1,61 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: mods/shared_logic/luadefs/CLua2DFXDefs.cpp + * PURPOSE: Lua definitions class + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once +#include "CLuaDefs.h" +#include +#include + +using effectFlagsTable = std::unordered_map>; +using effectPropertiesMap = std::unordered_map, effectFlagsTable>>; + +/* +[index] = { + type = "name", + position = {x,y,z}, + properties = { + coronaSize = 1, + shadowName = "shad_exp", + coronaReflection = false, + ... + }, +} +*/ +using effectDataMap = std::unordered_map, effectPropertiesMap>>; +using effectsMap = std::unordered_map; + +class CLua2DFXDefs : public CLuaDefs +{ +public: + static void LoadFunctions(); + + // Create/destroy functions + static bool AddModel2DFX(std::uint32_t model, CVector position, e2dEffectType effectType, effectPropertiesMap effectData); + static bool RemoveModel2DFX(std::uint32_t model, std::uint32_t index); + static void RestoreModel2DFX(std::uint32_t model, std::uint32_t index); + static void ResetModel2DFXEffects(std::uint32_t model); + + // Set functions + static void SetModel2DFXPosition(std::uint32_t model, std::uint32_t index, CVector position); + static bool SetModel2DFXProperty(std::uint32_t model, std::uint32_t index, e2dEffectProperty property, + std::variant, effectFlagsTable> propertyValue); + static void ResetModel2DFXProperty(std::uint32_t model, std::uint32_t index, e2dEffectProperty property); + static void ResetModel2DFXPosition(std::uint32_t model, std::uint32_t index); + + // Get functions + static std::variant> GetModel2DFXPosition(std::uint32_t model, std::uint32_t index); + static std::variant, CLuaMultiReturn, + effectFlagsTable> + GetModel2DFXProperty(std::uint32_t model, std::uint32_t index, e2dEffectProperty property, std::optional getFlagsAsTable); + static std::variant GetModel2DFXEffects(std::uint32_t model, std::optional includeCustomEffects); + static std::uint32_t GetModel2DFXCount(std::uint32_t model, std::optional includeCustomEffects); + static e2dEffectType GetModel2DFXType(std::uint32_t model, std::uint32_t index); +}; diff --git a/Client/multiplayer_sa/CMultiplayerSA.cpp b/Client/multiplayer_sa/CMultiplayerSA.cpp index 2389110fbf..14c975c68a 100644 --- a/Client/multiplayer_sa/CMultiplayerSA.cpp +++ b/Client/multiplayer_sa/CMultiplayerSA.cpp @@ -1588,6 +1588,7 @@ void CMultiplayerSA::InitHooks() InitHooks_VehicleDamage(); InitHooks_VehicleLights(); InitHooks_VehicleWeapons(); + InitHooks_2DFX(); InitHooks_Streaming(); InitHooks_FrameRateFixes(); diff --git a/Client/multiplayer_sa/CMultiplayerSA.h b/Client/multiplayer_sa/CMultiplayerSA.h index f4c79ce9b6..2a0cc5a95b 100644 --- a/Client/multiplayer_sa/CMultiplayerSA.h +++ b/Client/multiplayer_sa/CMultiplayerSA.h @@ -83,6 +83,7 @@ class CMultiplayerSA : public CMultiplayer void InitHooks_DeviceSelection(); void InitHooks_Explosions(); void InitHooks_Tasks(); + void InitHooks_2DFX(); CRemoteDataStorage* CreateRemoteDataStorage(); void DestroyRemoteDataStorage(CRemoteDataStorage* pData); void AddRemoteDataStorage(CPlayerPed* pPed, CRemoteDataStorage* pData); diff --git a/Client/multiplayer_sa/CMultiplayerSA_2DFX.cpp b/Client/multiplayer_sa/CMultiplayerSA_2DFX.cpp new file mode 100644 index 0000000000..a5bc6bf714 --- /dev/null +++ b/Client/multiplayer_sa/CMultiplayerSA_2DFX.cpp @@ -0,0 +1,788 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: multiplayer_sa/CMultiplayerSA_2DFX.cpp + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" + +//////////////////////////////////////////////////////////////////////// +// CTrafficLights::DisplayActualLight +//////////////////////////////////////////////////////////////////////// +#define HOOKPOS_CTrafficLights__DisplayActualLight 0X49DC18 +#define HOOKSIZE_CTrafficLights__DisplayActualLight 5 +static constexpr std::uintptr_t RETURN_CTrafficLights__DisplayActualLight = 0x49DC1D; +static constexpr std::uintptr_t SKIP_CTrafficLights__DisplayActualLight = 0x49DE1D; +static void _declspec(naked) HOOK_CTrafficLights__DisplayActualLight() +{ + _asm + { + mov ebp, eax + test ebp, ebp + jz skip + + mov al, [ebp+0Ch] + jmp RETURN_CTrafficLights__DisplayActualLight + + skip: + jmp SKIP_CTrafficLights__DisplayActualLight + } +} + +//////////////////////////////////////////////////////////////////////// +// CEntity::HasPreRenderEffects +//////////////////////////////////////////////////////////////////////// +#define HOOKPOS_CEntity__HasPreRenderEffects 0x532D19 +#define HOOKSIZE_CEntity__HasPreRenderEffects 5 +static constexpr std::uintptr_t RETURN_CEntity__HasPreRenderEffects = 0x532D1E; +static constexpr std::uintptr_t SKIP_CEntity__HasPreRenderEffects = 0x532D20; +static void _declspec(naked) HOOK_CEntity__HasPreRenderEffects() +{ + _asm + { + test eax, eax + jz skip + + mov cl, [eax+0Ch] + test cl, cl + jmp RETURN_CEntity__HasPreRenderEffects + + skip: + jmp SKIP_CEntity__HasPreRenderEffects + } +} + +//////////////////////////////////////////////////////////////////////// +// CEntity::FindTriggerPointCoors +//////////////////////////////////////////////////////////////////////// +#define HOOKPOS_CEntity__FindTriggerPointCoors 0x5333A8 +#define HOOKSIZE_CEntity__FindTriggerPointCoors 6 +static constexpr std::uintptr_t RETURN_CEntity__FindTriggerPointCoors = 0x5333AE; +static constexpr std::uintptr_t SKIP_CEntity__FindTriggerPointCoors = 0x5333B9; +static void _declspec(naked) HOOK_CEntity__FindTriggerPointCoors() +{ + _asm + { + test eax, eax + jz skip + + mov esi, eax + cmp byte ptr [esi+0Ch], 8 + jmp RETURN_CEntity__FindTriggerPointCoors + + skip: + jmp SKIP_CEntity__FindTriggerPointCoors + } +} + +//////////////////////////////////////////////////////////////////////// +// CEntity::GetRandom2dEffect +//////////////////////////////////////////////////////////////////////// +#define HOOKPOS_CEntity__GetRandom2dEffect 0x533448 +#define HOOKSIZE_CEntity__GetRandom2dEffect 6 +static constexpr std::uintptr_t RETURN_CEntity__GetRandom2dEffect = 0x53344E; +static constexpr std::uintptr_t SKIP_CEntity__GetRandom2dEffect = 0x533482; +static void _declspec(naked) HOOK_CEntity__GetRandom2dEffect() +{ + _asm + { + test eax, eax + jz skip + + mov esi, eax + movzx ecx, byte ptr [esi+0Ch] + jmp RETURN_CEntity__GetRandom2dEffect + + skip: + jmp SKIP_CEntity__GetRandom2dEffect + } +} + +//////////////////////////////////////////////////////////////////////// +// CEntity::CreateEffects +//////////////////////////////////////////////////////////////////////// +#define HOOKPOS_CEntity__CreateEffects 0x5337D8 +#define HOOKSIZE_CEntity__CreateEffects 6 +static constexpr std::uintptr_t RETURN_CEntity__CreateEffects = 0x5337DE; +static constexpr std::uintptr_t SKIP_CEntity__CreateEffects = 0x533B9C; +static void _declspec(naked) HOOK_CEntity__CreateEffects() +{ + _asm + { + test eax, eax + jz skip + + mov esi, eax + movzx eax, byte ptr [esi+0Ch] + jmp RETURN_CEntity__CreateEffects + + skip: + jmp SKIP_CEntity__CreateEffects + } +} + +//////////////////////////////////////////////////////////////////////// +// CEntity::DestroyEffects +//////////////////////////////////////////////////////////////////////// +#define HOOKPOS_CEntity__DestroyEffects 0x533C2A +#define HOOKSIZE_CEntity__DestroyEffects 5 +static constexpr std::uintptr_t RETURN_CEntity__DestroyEffects = 0x533C2F; +static constexpr std::uintptr_t SKIP_CEntity__DestroyEffects = 0x533D14; +static void _declspec(naked) HOOK_CEntity__DestroyEffects() +{ + _asm + { + test eax, eax + jz skip + + mov esi, eax + mov al, [esi+0Ch] + jmp RETURN_CEntity__DestroyEffects + + skip: + jmp SKIP_CEntity__DestroyEffects + } +} + +//////////////////////////////////////////////////////////////////////// +// CEntity::RenderEffects +//////////////////////////////////////////////////////////////////////// +#define HOOKPOS_CEntity__RenderEffects 0x5342D5 +#define HOOKSIZE_CEntity__RenderEffects 5 +static constexpr std::uintptr_t RETURN_CEntity__RenderEffects = 0x5342DA; +static constexpr std::uintptr_t SKIP_CEntity__RenderEffects = 0x534301; +static void _declspec(naked) HOOK_CEntity__RenderEffects() +{ + _asm + { + mov eax, 0x4C4C70 + call eax + + test eax, eax + jz skip + + jmp RETURN_CEntity__RenderEffects + + skip: + jmp SKIP_CEntity__RenderEffects + } +} + +//////////////////////////////////////////////////////////////////////// +// CEntity::ProcessLightsForEntity +//////////////////////////////////////////////////////////////////////// +#define HOOKPOS_CEntity__ProcessLightsForEntity 0x6FC827 +#define HOOKSIZE_CEntity__ProcessLightsForEntity 6 +static constexpr std::uintptr_t RETURN_CEntity__ProcessLightsForEntity = 0x6FC82D; +static constexpr std::uintptr_t SKIP_CEntity__ProcessLightsForEntity = 0x6FD488; +static void _declspec(naked) HOOK_CEntity__ProcessLightsForEntity() +{ + _asm + { + test eax, eax + jz skip + + mov esi, eax + xor eax, eax + mov ecx, edi + jmp RETURN_CEntity__ProcessLightsForEntity + + skip: + jmp SKIP_CEntity__ProcessLightsForEntity + } +} + +//////////////////////////////////////////////////////////////////////// +// InteriorManager_c::AddSameGroupEffectInfos +//////////////////////////////////////////////////////////////////////// +#define HOOKPOS_InteriorManager_c__AddSameGroupEffectInfos 0x598458 +#define HOOKSIZE_InteriorManager_c__AddSameGroupEffectInfos 5 +static constexpr std::uintptr_t RETURN_InteriorManager_c__AddSameGroupEffectInfos = 0x59845D; +static constexpr std::uintptr_t SKIP_InteriorManager_c__AddSameGroupEffectInfos = 0x598484; +static void _declspec(naked) HOOK_InteriorManager_c__AddSameGroupEffectInfos() +{ + _asm + { + mov eax, 0x4C4C70 + call eax + + test eax, eax + jz skip + + jmp RETURN_InteriorManager_c__AddSameGroupEffectInfos + + skip: + jmp SKIP_InteriorManager_c__AddSameGroupEffectInfos + } +} + +//////////////////////////////////////////////////////////////////////// +// InteriorManager_c::GetVisibleEffects +//////////////////////////////////////////////////////////////////////// +#define HOOKPOS_InteriorManager_c__GetVisibleEffects 0x598E5A +#define HOOKSIZE_InteriorManager_c__GetVisibleEffects 6 +static constexpr std::uintptr_t RETURN_InteriorManager_c__GetVisibleEffects = 0x598E60; +static constexpr std::uintptr_t SKIP_InteriorManager_c__GetVisibleEffects = 0x598EDD; +static void _declspec(naked) HOOK_InteriorManager_c__GetVisibleEffects() +{ + _asm + { + test eax, eax + jz skip + + mov edi, eax + cmp byte ptr [edi+0Ch], 5 + jmp RETURN_InteriorManager_c__GetVisibleEffects + + skip: + jmp SKIP_InteriorManager_c__GetVisibleEffects + } +} + +//////////////////////////////////////////////////////////////////////// +// CAttractorScanner::GetClosestAttractorOfType +//////////////////////////////////////////////////////////////////////// +#define HOOKPOS_CAttractorScanner__GetClosestAttractorOfType 0x6003B8 +#define HOOKSIZE_CAttractorScanner__GetClosestAttractorOfType 9 +static constexpr std::uintptr_t RETURN_CAttractorScanner__GetClosestAttractorOfType = 0x6003C1; +static constexpr std::uintptr_t SKIP_CAttractorScanner__GetClosestAttractorOfType = 0x600491; +static void _declspec(naked) HOOK_CAttractorScanner__GetClosestAttractorOfType() +{ + _asm + { + test eax, eax + jz skip + + mov esi, eax + mov eax, [esp+102Ch+1Ch] + jmp RETURN_CAttractorScanner__GetClosestAttractorOfType + + skip: + jmp SKIP_CAttractorScanner__GetClosestAttractorOfType + } +} + +//////////////////////////////////////////////////////////////////////// +// CAttractorScanner::ScanForAttractorsInPtrList +//////////////////////////////////////////////////////////////////////// +#define HOOKPOS_CAttractorScanner__ScanForAttractorsInPtrList 0x603523 +#define HOOKSIZE_CAttractorScanner__ScanForAttractorsInPtrList 5 +static constexpr std::uintptr_t RETURN_CAttractorScanner__ScanForAttractorsInPtrList = 0x603528; +static constexpr std::uintptr_t SKIP_CAttractorScanner__ScanForAttractorsInPtrList = 0x603548; +static void _declspec(naked) HOOK_CAttractorScanner__ScanForAttractorsInPtrList() +{ + _asm + { + mov eax, 0x4C4C70 + call eax + + test eax, eax + jz skip + + jmp RETURN_CAttractorScanner__ScanForAttractorsInPtrList + + skip: + jmp SKIP_CAttractorScanner__ScanForAttractorsInPtrList + } +} + +//////////////////////////////////////////////////////////////////////// +// CCover::FindCoverPointsForThisBuilding +//////////////////////////////////////////////////////////////////////// +#define HOOKPOS_CCover__FindCoverPointsForThisBuilding 0x699150 +#define HOOKSIZE_CCover__FindCoverPointsForThisBuilding 6 +static constexpr std::uintptr_t RETURN_CCover__FindCoverPointsForThisBuilding = 0x699156; +static constexpr std::uintptr_t SKIP_CCover__FindCoverPointsForThisBuilding = 0x699215; +static void _declspec(naked) HOOK_CCover__FindCoverPointsForThisBuilding() +{ + _asm + { + test eax, eax + jz skip + + mov edi, eax + cmp byte ptr [edi+0Ch], 9 + jmp RETURN_CCover__FindCoverPointsForThisBuilding + + skip: + jmp SKIP_CCover__FindCoverPointsForThisBuilding + } +} + +//////////////////////////////////////////////////////////////////////// +// CEntity::CreateEffects +// +// Call the function after loading the effects to deactivate those removed by the script. +// We have to do it this way because every time an object streams in, +// the effects are read again from the .dff file. +//////////////////////////////////////////////////////////////////////// +static void OnEntityEffectsLoaded(CEntitySAInterface* entity) +{ + CModelInfo* modelInfo = pGameInterface->GetModelInfo(entity->m_nModelIndex); + if (modelInfo) + modelInfo->On2DFXEffectsLoaded(entity); +} + +#define HOOKPOS_CEntity_Post_CreateEffects 0x533BAE +#define HOOKSIZE_CEntity_Post_CreateEffects 10 +static void _declspec(naked) HOOK_CEntity_Post_CreateEffects() +{ + _asm + { + push ebp + call OnEntityEffectsLoaded + add esp, 4 + + pop edi + pop ebp + pop ebx + add esp, 0C0h + retn + } +} + +//////////////////////////////////////////////////////////////////////// +// CCustomRoadsignMgr::RoadsignGetLineAndRow +// +// Some characters and symbols in roadsignfont.png are unavailable, +// and we want them to be accessible +//////////////////////////////////////////////////////////////////////// +static void RoadsignGetLineAndRow(int* line, int* row) +{ + char letter = 0; + _asm + { + mov letter, al + } + + switch (letter) + { + case '!': + *line = 0; + *row = 0; + break; + case '#': + *line = 3; + *row = 21; + break; + case '$': + *line = 0; + *row = 22; + break; + case '%': + *line = 2; + *row = 21; + break; + case '&': + *line = 2; + *row = 0; + break; + case '(': + *line = 0; + *row = 1; + break; + case ')': + *line = 1; + *row = 1; + break; + case '*': + *line = 1; + *row = 22; + break; + case '+': + *line = 2; + *row = 1; + break; + case '-': + *line = 0; + *row = 2; + break; + case '.': + *line = 1; + *row = 2; + break; + case '0': + *line = 3; + *row = 2; + break; + case '1': + *line = 0; + *row = 3; + break; + case '2': + *line = 1; + *row = 3; + break; + case '3': + *line = 2; + *row = 3; + break; + case '4': + *line = 3; + *row = 3; + break; + case '5': + *line = 0; + *row = 4; + break; + case '6': + *line = 1; + *row = 4; + break; + case '7': + *line = 2; + *row = 4; + break; + case '8': + *line = 3; + *row = 4; + break; + case '9': + *line = 0; + *row = 5; + break; + case ':': + *line = 2; + *row = 5; + break; + case ';': + *line = 1; + *row = 5; + break; + case '<': + *line = 2; + *row = 20; + break; + case '>': + *line = 3; + *row = 20; + break; + case '?': + *line = 3; + *row = 5; + break; + case '@': + *line = 2; + *row = 22; + break; + case 'A': + *line = 0; + *row = 6; + break; + case 'B': + *line = 1; + *row = 6; + break; + case 'C': + *line = 2; + *row = 6; + break; + case 'D': + *line = 3; + *row = 6; + break; + case 'E': + *line = 0; + *row = 7; + break; + case 'F': + *line = 1; + *row = 7; + break; + case 'G': + *line = 2; + *row = 7; + break; + case 'H': + *line = 3; + *row = 7; + break; + case 'I': + *line = 0; + *row = 8; + break; + case 'J': + *line = 1; + *row = 8; + break; + case 'K': + *line = 2; + *row = 8; + break; + case 'L': + *line = 3; + *row = 8; + break; + case 'M': + *line = 0; + *row = 9; + break; + case 'N': + *line = 1; + *row = 9; + break; + case 'O': + *line = 2; + *row = 9; + break; + case 'P': + *line = 3; + *row = 9; + break; + case 'Q': + *line = 0; + *row = 10; + break; + case 'R': + *line = 1; + *row = 10; + break; + case 'S': + *line = 2; + *row = 10; + break; + case 'T': + *line = 3; + *row = 10; + break; + case 'U': + *line = 0; + *row = 11; + break; + case 'V': + *line = 1; + *row = 11; + break; + case 'W': + *line = 2; + *row = 11; + break; + case 'X': + *line = 3; + *row = 11; + break; + case 'Y': + *line = 0; + *row = 12; + break; + case 'Z': + *line = 1; + *row = 12; + break; + case '[': + *line = 2; + *row = 12; + break; + case ']': + *line = 0; + *row = 13; + break; + case '^': + *line = 0; + *row = 21; + break; + case 'a': + *line = 1; + *row = 13; + break; + case 'b': + *line = 2; + *row = 13; + break; + case 'c': + *line = 3; + *row = 13; + break; + case 'd': + *line = 0; + *row = 14; + break; + case 'e': + *line = 1; + *row = 14; + break; + case 'f': + *line = 2; + *row = 14; + break; + case 'g': + *line = 3; + *row = 14; + break; + case 'h': + *line = 0; + *row = 15; + break; + case 'i': + *line = 1; + *row = 15; + break; + case 'j': + *line = 2; + *row = 15; + break; + case 'k': + *line = 3; + *row = 15; + break; + case 'l': + *line = 0; + *row = 16; + break; + case 'm': + *line = 1; + *row = 16; + break; + case 'n': + *line = 2; + *row = 16; + break; + case 'o': + *line = 3; + *row = 16; + break; + case 'p': + *line = 0; + *row = 17; + break; + case 'q': + *line = 1; + *row = 17; + break; + case 'r': + *line = 2; + *row = 17; + break; + case 's': + *line = 3; + *row = 17; + break; + case 't': + *line = 0; + *row = 18; + break; + case 'u': + *line = 1; + *row = 18; + break; + case 'v': + *line = 2; + *row = 18; + break; + case 'w': + *line = 3; + *row = 18; + break; + case 'x': + *line = 0; + *row = 19; + break; + case 'y': + *line = 1; + *row = 19; + break; + case 'z': + *line = 2; + *row = 19; + break; + case '{': + *line = 0; + *row = 23; + break; + case '|': + *line = 3; + *row = 22; + break; + case '}': + *line = 2; + *row = 23; + break; + case '~': + *line = 1; + *row = 21; + break; + // missing characters & symbols + case '/': + *line = 2; + *row = 2; + break; + case ',': + *line = 3; + *row = 1; + break; + case '\\': // | character + *line = 0; + *row = 20; + break; + case '\x04': // scull ♦ + *line = 3; + *row = 23; + break; + case '\xB9': // invalid ╣ + *line = 0; + *row = 24; + break; + case '\xB6': // parking icon icon ¶ + *line = 1; + *row = 24; + break; + case '\xA7': // winding road or something § + *line = 2; + *row = 24; + break; + case '\x07': // info icon • + *line = 3; + *row = 24; + break; + case '\x01': // alien icon ☺ + *line = 0; + *row = 25; + break; + case '\x02': // human icon ☻ + *line = 1; + *row = 25; + break; + case '\xDB': // train icon █ + *line = 2; + *row = 25; + break; + case '\x1E': // hill icon or something ▲ + *line = 3; + *row = 25; + break; + ///////////////////////////// + default: + *line = 0; + *row = 26; + break; + } +} + +void CMultiplayerSA::InitHooks_2DFX() +{ + EZHookInstall(CTrafficLights__DisplayActualLight); + EZHookInstall(CEntity__HasPreRenderEffects); + EZHookInstall(CEntity__FindTriggerPointCoors); + EZHookInstall(CEntity__GetRandom2dEffect); + EZHookInstall(CEntity__CreateEffects); + EZHookInstall(CEntity__DestroyEffects); + EZHookInstall(CEntity__RenderEffects); + EZHookInstall(CEntity__ProcessLightsForEntity); + EZHookInstall(InteriorManager_c__AddSameGroupEffectInfos); + EZHookInstall(InteriorManager_c__GetVisibleEffects); + EZHookInstall(CAttractorScanner__GetClosestAttractorOfType); + EZHookInstall(CAttractorScanner__ScanForAttractorsInPtrList); + EZHookInstall(CCover__FindCoverPointsForThisBuilding); + + EZHookInstall(CEntity_Post_CreateEffects); + + // CCustomRoadsignMgr::RoadsignGetLineAndRow + HookInstallCall(0x6FEBF1, (DWORD)&RoadsignGetLineAndRow); + + // Allow set roadsign text alpha + MemSet((void*)0x6FF3FF, 0x90, 5); +} diff --git a/Client/sdk/game/C2DEffects.h b/Client/sdk/game/C2DEffects.h new file mode 100644 index 0000000000..a8af2b586d --- /dev/null +++ b/Client/sdk/game/C2DEffects.h @@ -0,0 +1,118 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * FILE: sdk/game/C2DEffects.h + * PURPOSE: 2DFX effect functions + * + * Multi Theft Auto is available from https://www.multitheftauto.com/ + * + *****************************************************************************/ \ +#pragma once +#include "Common.h" +#include +#include +#include +#include + +class C2DEffectSAInterface; +class CEntitySAInterface; + +struct S2DEffectData +{ + CVector position{}; + + // light properties + float drawDistance{}; + float lightRange{}; + float coronaSize{}; + float shadowSize{}; + float shadowMult{}; + float shadowDist{}; + e2dCoronaFlashType coronaFlashType{e2dCoronaFlashType::DEFAULT}; + bool coronaReflection{false}; + float coronaFlareType{}; + + struct + { + std::int8_t x{}; + std::int8_t y{}; + std::int8_t z{}; + } offset; + + std::string coronaName{}; + std::string shadowName{}; + + // light & roadsign + std::uint16_t flags{}; + SColor color{}; + + // particle + std::string prt_name{}; + + // roadsign + CVector2D size{}; + CVector rot{}; + std::string text_1{}; + std::string text_2{}; + std::string text_3{}; + std::string text_4{}; + + // escalator + CVector bottom{}; + CVector top{}; + CVector end{}; + + std::uint8_t direction{}; + + bool operator==(const S2DEffectData& other) const + { + return position == other.position && drawDistance == other.drawDistance && lightRange == other.lightRange && coronaSize == other.coronaSize && + shadowSize == other.shadowSize && shadowMult == other.shadowMult && shadowDist == other.shadowDist && coronaFlashType == other.coronaFlashType && + coronaReflection == other.coronaReflection && coronaFlareType == other.coronaFlareType && offset.x == other.offset.x && + offset.y == other.offset.y && offset.z == other.offset.z && coronaName == other.coronaName && shadowName == other.shadowName && + flags == other.flags && color == other.color && prt_name == other.prt_name && size == other.size && rot == other.rot && text_1 == other.text_1 && + text_2 == other.text_2 && text_3 == other.text_3 && text_4 == other.text_4 && bottom == other.bottom && top == other.top && end == other.end && + direction == other.direction; + } +}; + +class C2DEffects +{ +public: + virtual void InitEffect(C2DEffectSAInterface* effect, std::uint32_t model, CEntitySAInterface* entity = nullptr, + std::vector* affectedEntities = nullptr) = 0; + virtual void DeInitEffect(C2DEffectSAInterface* effect, std::uint32_t model, bool isCustomEffect = false, CEntitySAInterface* entity = nullptr) = 0; + virtual void ReInitEffect(C2DEffectSAInterface* effect, std::uint32_t model, bool isCustomEffect = false, CEntitySAInterface* entity = nullptr) = 0; + virtual void ReInitEffect(std::uint32_t model, std::uint32_t index) = 0; + + virtual void Set2DFXEffectPosition(std::uint32_t model, std::uint32_t index, const CVector& position) = 0; + virtual CVector Get2DFXEffectPosition(std::uint32_t model, std::uint32_t index) const = 0; + + virtual e2dEffectType Get2DFXEffectType(C2DEffectSAInterface* effect) const = 0; + virtual e2dEffectType Get2DFXEffectType(std::uint32_t model, std::uint32_t index) const = 0; + + virtual void Set2DFXProperties(C2DEffectSAInterface* effectInterface, const S2DEffectData& effectProperties) = 0; + + virtual void Set2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, float value) = 0; + virtual void Set2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, bool value) = 0; + virtual void Set2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, std::string& value) = 0; + virtual void Set2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, e2dCoronaFlashType& value) = 0; + virtual void Set2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, const std::vector& value) = 0; + virtual void Reset2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, const S2DEffectData& originalProperties) = 0; + + virtual S2DEffectData Get2DFXPropertiesData(std::uint32_t model, std::uint32_t index) const = 0; + + virtual void Get2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, float& outValue) const = 0; + virtual void Get2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, bool& outValue) const = 0; + virtual void Get2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, std::string& outValue) const = 0; + virtual void Get2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, e2dCoronaFlashType& outValue) const = 0; + virtual void Get2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, SColor& outValue) const = 0; + virtual void Get2DFXProperty(std::uint32_t model, std::uint32_t index, const e2dEffectProperty& property, std::vector& outValue) const = 0; + + virtual void UpdatePropertiesData(S2DEffectData& dst, const S2DEffectData& src, const e2dEffectProperty& property) const = 0; + + virtual bool IsPropertyValueNumber(const e2dEffectProperty& property) const noexcept = 0; + virtual bool IsPropertyValueString(const e2dEffectProperty& property) const noexcept = 0; + virtual bool IsPropertyValueTable(const e2dEffectProperty& property) const noexcept = 0; +}; diff --git a/Client/sdk/game/CGame.h b/Client/sdk/game/CGame.h index baa6103e10..81cf4526b6 100644 --- a/Client/sdk/game/CGame.h +++ b/Client/sdk/game/CGame.h @@ -71,6 +71,7 @@ class CIplStore; class CBuildingRemoval; class CRenderer; class CVehicleAudioSettingsManager; +class C2DEffects; enum eEntityType; enum ePedPieceTypes; @@ -155,6 +156,7 @@ class __declspec(novtable) CGame virtual CRenderer* GetRenderer() const noexcept = 0; virtual CVehicleAudioSettingsManager* GetVehicleAudioSettingsManager() const noexcept = 0; + virtual C2DEffects* Get2DFXEffects() const noexcept = 0; virtual CWeaponInfo* GetWeaponInfo(eWeaponType weapon, eWeaponSkill skill = WEAPONSKILL_STD) = 0; virtual CModelInfo* GetModelInfo(DWORD dwModelID, bool bCanBeInvalid = false) = 0; @@ -261,6 +263,7 @@ class __declspec(novtable) CGame virtual void ResetAlphaTransparencies() = 0; virtual void DisableVSync() = 0; virtual void ResetModelTimes() = 0; + virtual void Reset2DFXEffects() const = 0; virtual void OnPedContextChange(CPed* pPedContext) = 0; virtual CPed* GetPedContext() = 0; diff --git a/Client/sdk/game/CModelInfo.h b/Client/sdk/game/CModelInfo.h index 3c111013f3..05f3f7c13e 100644 --- a/Client/sdk/game/CModelInfo.h +++ b/Client/sdk/game/CModelInfo.h @@ -24,6 +24,11 @@ class CColModel; class CPedModelInfo; struct RpClump; struct RwObject; +struct RpGeometry; +class C2DEffectSAInterface; +class CEntitySAInterface; +struct RwColor; +struct S2DEffectData; class CBoundingBox { @@ -256,4 +261,24 @@ class CModelInfo virtual unsigned int GetParentID() = 0; virtual bool IsDynamic() = 0; virtual bool IsDamageableAtomic() = 0; + + // 2dfx functions + virtual bool Add2DFXEffect(const e2dEffectType& effectType, const CVector& position, const S2DEffectData& effectProperties) = 0; + virtual bool Remove2DFXEffect(std::uint32_t index, bool keepIndex = true, CEntitySAInterface* entity = nullptr) = 0; + virtual void Restore2DFXEffect(std::uint32_t index) = 0; + virtual bool Reset2DFXEffectProperties(std::uint32_t index) = 0; + virtual void Reset2DFXEffects() = 0; + virtual void Store2DFXProperties(std::uint32_t index, const e2dEffectProperty& property) = 0; + virtual void Store2DFXDefaultProperties(std::uint32_t index) = 0; + virtual void Store2DFXRoadsignColor(std::uint32_t index, const RwColor& color) = 0; + virtual void Reset2DFXProperty(std::uint32_t index, const e2dEffectProperty& property) = 0; + + virtual std::uint32_t Get2DFXEffectIndex(C2DEffectSAInterface* effect) const = 0; + virtual C2DEffectSAInterface* Get2DFXEffectByIndex(std::uint32_t index) const = 0; + virtual C2DEffectSAInterface* Get2DFXEffect(CBaseModelInfoSAInterface* modelInfo, RpGeometry* geometry, std::uint32_t numPluginEffects, std::uint32_t index) = 0; + virtual std::uint32_t Get2DFXEffectsCount(bool includeCustomEffects = true) const = 0; + virtual RwColor GetStored2DFXRoadsignColor(std::uint32_t index) const = 0; + virtual bool IsCustom2DFXEffect(C2DEffectSAInterface* effect) = 0; + + virtual void On2DFXEffectsLoaded(CEntitySAInterface* entity) = 0; }; diff --git a/Client/sdk/game/Common.h b/Client/sdk/game/Common.h index 80810a7d24..872e301bd5 100644 --- a/Client/sdk/game/Common.h +++ b/Client/sdk/game/Common.h @@ -11,6 +11,7 @@ #pragma once #include +#include // Limits for MTA #define MAX_VEHICLES_MTA 64 // Real limit is 110 @@ -62,3 +63,116 @@ #define WEAPONTYPE_FORCE_FINISH_ANIM (0x040000) // force the anim to finish player after aim/fire rather than blending out #define WEAPONTYPE_EXPANDS (0x080000) // #define WEAPONTYPE_CHAINGUN WEAPONTYPE_MINIGUN + +enum class e2dEffectType : std::uint8_t +{ + LIGHT = 0, + PARTICLE, + UNKNOWN, + ATTRACTOR, + SUN_GLARE, + FURNITURE, + ENEX, + ROADSIGN, + TRIGGER_POINT, + COVER_POINT, + ESCALATOR, + + NONE, +}; + +enum class e2dCoronaFlashType : std::uint8_t +{ + DEFAULT = 0, + RANDOM, + RANDOM_WHEN_WET, + ANIM_SPEED_4X, + ANIM_SPEED_2X, + ANIM_SPEED_1X, + WARNLIGHT, // Used on model nt_roadblockci + TRAFFICLIGHT, + TRAINCROSSING, + BRIDGE, // Unused in SA, always false (CBridge::ShouldLightsBeFlashing) + ONLY_RAIN, + ON5_OFF5, + ON6_OFF4, + ON4_OFF6, +}; + +enum class e2dEffectProperty +{ + // light properties + FAR_CLIP_DISTANCE = 0, + LIGHT_RANGE, + CORONA_SIZE, + SHADOW_SIZE, + SHADOW_MULT, + FLASH_TYPE, + CORONA_REFLECTION, + FLARE_TYPE, + SHADOW_DISTANCE, + OFFSET, + COLOR, // for light & roadsign + CORONA_NAME, + SHADOW_NAME, + FLAGS, // for light & roadsign + + // particle properties + PRT_NAME, + + // roadsign properties + SIZE, + ROT, + TEXT_1, + TEXT_2, + TEXT_3, + TEXT_4, + + // escalator properties + BOTTOM, + TOP, + END, + DIRECTION, + + POSITION, +}; + +enum class e2dAttractorType : std::int8_t +{ + UNDEFINED = -1, + ATM = 0, + SEAT, + STOP, + PIZZA, + SHELTER, + TRIGGER_SCRIPT, + LOOK_AT, + SCRIPTED, + PARK, + STEP, +}; + +enum class e2dEffectFlags +{ + // light flags + CHECK_OBSTACLES, + FOG_TYPE, + FOG_TYPE2, // Unused in SA code + WITHOUT_CORONA, // Unused in SA code + ONLY_LONG_DISTANCE, + AT_DAY, + AT_NIGHT, + BLINKING1, + + // light flags2 + ONLY_FROM_BELOW, + BLINKING2, + UPDATE_HEIGHT_ABOVE_GROUND, + CHECK_DIRECTION, + BLINKING3, + + // roadsign flags + LINES_NUM, + CHARACTERS_PER_LINE, + TEXT_COLOR, // Overrided by our own implementation (C2DEffectsSA::SetRoadsignTextColor) +};