Skip to content

Conversation

@UTengine
Copy link
Contributor

@UTengine UTengine commented Oct 1, 2025

This block belongs to Twostars (Credits).
char szEffectFN[MAX_PATH] = ""; // TCHAR szEffectFN[_MAX_PATH + 1] = {}; Credits to Twostars anyway.. Vector3 vEffectPos(0, 0, 0);
float fEffectScale = 0;
__Quaternion qtEffectRot; qtEffectRot.Identity();

szEffectFN,&vEffectPos.x, &vEffectPos.y, &vEffectPos.z, &fEffectScale,&qtEffectRot.x, &qtEffectRot.y, &qtEffectRot.z, &qtEffectRot.w);
&pPart->m_Mtl.nRenderFlags, &pPart->m_Mtl.dwSrcBlend, &pPart->m_Mtl.dwDestBlend, &pPart->m_Mtl.dwColorOp, &pPart->m_Mtl.dwColorArg1, &pPart->m_Mtl.dwColorArg2

Looked like one of his early implementations that I used as a cheat sheet. It would be unfair to claim I did the MapMng.cpp totally on my part.
What I have added is double checks to see if the line has enough arguments to fit the criteria and show a messagebox this might be too intrusive.
And we might want to change the loadfxb to return a false or true changing it to a boolean function for error checking.
Also saving or moving an object and updating its position.
Also adding a dialog box for FXB implementation would be neat, but first things first...
AI was involved in n3scene/shape files, could have been cleaner but its a step in the right direction.

Pull request type

Please check the type of change your PR introduces:

  • Bugfix
  • Feature
  • Code style update (formatting, renaming)
  • Refactoring (no functional changes, no API changes)
  • Build related changes
  • Other (please describe):

What is the current behaviour?

doesnt exist

What is the new behaviour?

atleast it loads SDT files but isn't contained in N3Tools def

Why and how did I change this?

Contribution even if it gets denied IDC.

Demo

image

Checklist

  • I have performed a self-review of my own code.
  • Where applicable, I have checked to make sure that this doesn't introduce incompatible behaviour with the official 1.298 server (e.g. unofficial opcodes or behavioural differences).
  • I have checked to make sure that this change does not already exist in the codebase in some fashion (e.g. UI already implemented under a different name).

This block belongs to Twostars (Credits).
char szEffectFN[MAX_PATH] = ""; // TCHAR szEffectFN[_MAX_PATH + 1] = {}; Credits to Twostars anyway..
Vector3 vEffectPos(0, 0, 0);
float fEffectScale = 0;
__Quaternion qtEffectRot; qtEffectRot.Identity();

szEffectFN,&vEffectPos.x, &vEffectPos.y, &vEffectPos.z,
&fEffectScale,&qtEffectRot.x, &qtEffectRot.y, &qtEffectRot.z, &qtEffectRot.w);

&pPart->m_Mtl.nRenderFlags, &pPart->m_Mtl.dwSrcBlend, &pPart->m_Mtl.dwDestBlend, &pPart->m_Mtl.dwColorOp, &pPart->m_Mtl.dwColorArg1, &pPart->m_Mtl.dwColorArg2

Looked like one of his early implementations that I used as a cheat sheet. It would be unfair to claim I did the MapMng.cpp totally on my part. What I have added is double checks to see if the line has enough arguments to fit the criteria and show a messagebox this might be too intrusive.. Andwe might want to change the loadfxb to return a false or true changing it to a boolean function for error checking...
Also saving or moving an object and updating its position.
Also adding a dialog box for FXB implementation would be neat.
But first things first...
AI was involved in n3scene/shape files, could have been cleaner but its a step in the right direction.

Co-Authored-By: twostars <twostars@users.noreply.github.com>
@twostars
Copy link
Collaborator

Will you be coming back to this?
This also isn't really a map editor specific thing; the logic should be shared by the client as well, they'd just be loaded differently (map editor format vs client format). But they'd both tick and render the same.

@UTengine
Copy link
Contributor Author

I would like to but I have my hands full on my current project.
I do want to contribute but I would have to resetup everything (server sided and sync tools client again), which currently is just a tad too much.
The code I am currently using for my own "client" and that I shared with Serif Gungor.

bool TerrainData::OpdSubLoad(const std::string& path, TerrainData& out, std::string& error)
{
    // Open file using Windows API for consistency with OpdLoad
    HANDLE hFile = CreateFileA(path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
                               nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        error = "Failed to open file: " + path + " (Error code: " + std::to_string(GetLastError()) + ")";
        return false;
    }

    DWORD bytesRead = 0;

   // Read file header (4 bytes, int32) doesn't seem to be the case for 1298 but higher clients do use it.
   // int32_t fileHeader = 0;
   // if (!ReadFile(hFile, &fileHeader, sizeof(int32_t), &bytesRead, nullptr) || bytesRead != sizeof(int32_t))
   // {
   //     error = "Failed to read file header";
   //     CloseHandle(hFile);
   //    return false;
   // }

    // Read entry count (4 bytes, int32)
    int32_t entryCount = 0;
    if (!ReadFile(hFile, &entryCount, sizeof(int32_t), &bytesRead, nullptr) || bytesRead != sizeof(int32_t))
    {
        error = "Failed to read entry count";
        CloseHandle(hFile);
        return false;
    }

    // Verify entry count matches shape count
    if (entryCount != static_cast<int32_t>(out.m_opdShapes.size()))
    {
        error = "Entry count (" + std::to_string(entryCount) + ") does not match shape count (" + 
                std::to_string(out.m_opdShapes.size()) + ")";
        CloseHandle(hFile);
        return false;
    }

    // Read each entry and map to shapes (1:1 mapping)
    for (int32_t i = 0; i < entryCount; ++i)
    {
        // Read data size (4 bytes, uint32)
        uint32_t dataSize = 0;
        if (!ReadFile(hFile, &dataSize, sizeof(uint32_t), &bytesRead, nullptr) || bytesRead != sizeof(uint32_t))
        {
            error = "Failed to read data size for entry " + std::to_string(i);
            CloseHandle(hFile);
            return false;
        }

        // Ensure shape index is valid
        if (i >= static_cast<int32_t>(out.m_opdShapes.size()))
        {
            error = "Entry index " + std::to_string(i) + " exceeds shape count";
            CloseHandle(hFile);
            return false;
        }

        OpdShape& shape = out.m_opdShapes[i];

        if (dataSize > 0)
        {
            // This shape has an FXB assigned
            shape.m_bHasFxb = true;

            // Read FXB filename (dataSize bytes, null-terminated)
            std::vector<char> buffer(dataSize + 1);
            if (!ReadFile(hFile, buffer.data(), dataSize, &bytesRead, nullptr) || bytesRead != dataSize)
            {
                error = "Failed to read FXB filename for entry " + std::to_string(i);
                CloseHandle(hFile);
                return false;
            }
            buffer[dataSize] = '\0';
            shape.m_szFxbFileName = buffer.data();

            // Read EffectEntry structure: Position (Vector3), Scale (float), Rotation (Quaternion)
            // Total: 12 bytes (3 floats) + 4 bytes (1 float) + 16 bytes (4 floats) = 32 bytes

            // Read Position: Vector3 (3 floats = 12 bytes)
            if (!ReadFile(hFile, &shape.m_vFxbPosition, sizeof(__Vector3), &bytesRead, nullptr) || 
                bytesRead != sizeof(__Vector3))
            {
                error = "Failed to read FXB position for entry " + std::to_string(i);
                CloseHandle(hFile);
                return false;
            }

            // Read Scale: float (4 bytes)
            if (!ReadFile(hFile, &shape.m_fFxbScale, sizeof(float), &bytesRead, nullptr) || 
                bytesRead != sizeof(float))
            {
                error = "Failed to read FXB scale for entry " + std::to_string(i);
                CloseHandle(hFile);
                return false;
            }

            // Read Rotation: Quaternion (4 floats = 16 bytes)
            if (!ReadFile(hFile, &shape.m_qFxbRotation, sizeof(__Quaternion), &bytesRead, nullptr) || 
                bytesRead != sizeof(__Quaternion))
            {
                error = "Failed to read FXB rotation for entry " + std::to_string(i);
                CloseHandle(hFile);
                return false;
            }
        }
        else
        {
            // dataSize == 0, no FXB for this shape
            shape.m_bHasFxb = false;
            shape.m_szFxbFileName.clear();
            shape.m_vFxbPosition.Set(0.0f, 0.0f, 0.0f);
            shape.m_fFxbScale = 0.0f;
            shape.m_qFxbRotation.x = 0.0f;
            shape.m_qFxbRotation.y = 0.0f;
            shape.m_qFxbRotation.z = 0.0f;
            shape.m_qFxbRotation.w = 1.0f; // Identity quaternion
        }

        // Progress indicator
        if ((i + 1) % 64 == 0)
        {
            int percent = (100 * (i + 1)) / entryCount;
            Logger::LogPrintf("Loading Objects Effect... %d %%\n", percent);
        }
    }

    CloseHandle(hFile);
    return true;
}

@UTengine
Copy link
Contributor Author

UTengine commented Jan 18, 2026

^ acquired that with the tips you gave me the other day thanks for that

Conflicts:
	Client/N3Base/N3Shape.cpp
	Client/N3Base/N3Shape.h
	src/N3Base/N3Scene.cpp
	src/Tools/N3ME/MapMng.cpp
@twostars
Copy link
Collaborator

I updated your PR against the master branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants