# GitHub Copilot Instructions for OpenMB This document provides context and instructions for GitHub Copilot and other AI coding assistants when working on the OpenMB project. ## Project Overview OpenMB is a C++20 game engine project that follows strict coding standards and conventions. When generating code or suggestions, always adhere to the guidelines outlined in this document. ## Core Principles 1. **Consistency over personal preference** - Follow existing patterns in the codebase 2. **Modern C++20** - Use modern C++ features appropriately 3. **Cross-platform compatibility** - Code must work on Windows, Linux, and macOS 4. **Clear documentation** - All public APIs should have Doxygen comments 5. **Memory safety** - Prefer RAII and smart pointers over manual memory management ## Code Style Requirements ### Formatting **CRITICAL: All code must follow these formatting rules:** ```cpp // Braces - ALWAYS on their own line (Allman/BSD style) void myFunction() { if (condition) { doSomething(); } else { doSomethingElse(); } } // NOT this: void myFunction() { if (condition) { doSomething(); } else { doSomethingElse(); } } ``` - **Indentation**: 4 spaces (NEVER tabs) - **Line length**: Maximum 120 columns - **Pointer alignment**: Left-aligned with type (`Ptr* ptr`, not `Ptr *ptr`) - **Spacing**: Space after control keywords (`if (`, `for (`, `while (`), no space after function names ### Naming Conventions **CRITICAL: Follow these naming rules exactly:** ```cpp // Classes and Structs - PascalCase class PlayerManager { }; struct GameState { }; // Functions and Methods - camelCase void processInput(); bool isGameRunning() const; // Member Variables - 'm' prefix + PascalCase class MyClass { private: int mHealthPoints; std::string mPlayerName; bool mIsActive; }; // Local Variables and Parameters - camelBack void updatePlayer(const Ptr& playerPtr, int deltaTime) { int currentHealth = getHealth(playerPtr); float movementSpeed = calculateSpeed(); } // Namespaces - PascalCase with MB prefix for project namespaces namespace MBWorld { } namespace MBMechanics { } namespace MBBase { } // Constants and Enums - PascalCase enum class GameState { Loading, Running, Paused }; constexpr int MaxPlayers = 64; ``` ### Include Guards **ALWAYS use this format for include guards:** ```cpp #ifndef OPENMB__H #define OPENMB__H // Header content #endif ``` Example: For file `apps/openmb/mbworld/player.hpp` ```cpp #ifndef OPENMB_APPS_OPENMB_MBWORLD_PLAYER_H #define OPENMB_APPS_OPENMB_MBWORLD_PLAYER_H ``` ### Include Order **ALWAYS organize includes in this order:** ```cpp #include "relatedheader.hpp" // Related header first (for .cpp files) #include // C system headers #include #include // C++ standard library #include #include #include // External libraries #include #include // Project headers #include "../mbbase/environment.hpp" ``` ### Namespace Structure ```cpp namespace MBWorld { // All namespace content indented class MyClass { public: // Access modifiers outdented by 4 spaces MyClass(); private: int mData; }; void freeFunction(); } ``` ## Code Generation Guidelines ### When Creating New Classes **ALWAYS generate headers like this:** ```cpp #ifndef OPENMB_APPS_OPENMB_MBWORLD_MYCLASS_H #define OPENMB_APPS_OPENMB_MBWORLD_MYCLASS_H #include #include namespace MBWorld { /// Brief description of what this class does class MyClass { public: MyClass(); ~MyClass() = default; /// Brief description of method /// \param input Description of parameter /// \return Description of return value bool processData(int input); /// Get the current value int getValue() const; private: int mValue; std::string mName; std::vector mData; }; } #endif ``` **And implementations like this:** ```cpp #include "myclass.hpp" #include #include namespace MBWorld { MyClass::MyClass() : mValue(0) , mName() , mData() { } bool MyClass::processData(int input) { if (input < 0) { throw std::runtime_error("Invalid input"); } mValue = input; return true; } int MyClass::getValue() const { return mValue; } } ``` ### Modern C++ Patterns to Use ```cpp // Smart pointers instead of raw pointers std::unique_ptr mDialog; std::shared_ptr mResource; // Range-based for loops for (const auto& item : container) { process(item); } // Auto for complex types auto iterator = myMap.find(key); auto result = complexFunction(); // nullptr instead of NULL Ptr* ptr = nullptr; // override keyword for virtual functions void myMethod() override; // constexpr for compile-time constants constexpr int BufferSize = 1024; // Const correctness void processData(const std::string& input) const; // Structured bindings (C++17) for (const auto& [key, value] : myMap) { // ... } ``` ### Patterns to AVOID ```cpp // DON'T use manual memory management MyClass* obj = new MyClass(); // BAD delete obj; // BAD // DO use smart pointers or stack allocation auto obj = std::make_unique(); // GOOD MyClass obj; // GOOD (when appropriate) // DON'T omit braces for single-line blocks if (condition) doSomething(); // BAD // DO always use braces if (condition) { doSomething(); // GOOD } // DON'T use raw pointers for ownership void takeOwnership(MyClass* ptr); // BAD // DO use smart pointers void takeOwnership(std::unique_ptr ptr); // GOOD // DON'T ignore const correctness void getData(Ptr& ptr); // BAD if not modifying // DO use const when appropriate void getData(const Ptr& ptr) const; // GOOD ``` ### Virtual Functions and Inheritance ```cpp class Base { public: virtual ~Base() = default; /// Pure virtual function virtual void mustImplement() = 0; /// Virtual function with default implementation virtual void canOverride(); }; class Derived : public Base { public: // Always use override keyword void mustImplement() override; void canOverride() override; // Use final to prevent further overriding void finalMethod() final; }; ``` ### Exception Handling ```cpp // Throw exceptions for error conditions void MyClass::validate() { if (!isValid()) { throw std::runtime_error("Invalid state detected"); } if (mData.empty()) { throw std::logic_error("Data cannot be empty"); } } // Use specific exception types void processFile(const std::string& filename) { if (!fileExists(filename)) { throw std::invalid_argument("File does not exist: " + filename); } } ``` ### Documentation Comments **ALWAYS add Doxygen comments for:** - All public classes - All public methods - All public member variables - Complex private methods ```cpp /// Manages the game's resource loading and caching /// /// This class handles asynchronous loading of game resources /// and maintains a cache to avoid redundant disk access. class ResourceManager { public: /// Load a resource by its identifier /// /// \param resourceId The unique identifier of the resource /// \param async If true, load asynchronously /// \return Pointer to the loaded resource, or nullptr if failed /// \throws std::runtime_error if resource format is invalid Resource* loadResource(const ESM::RefId& resourceId, bool async = false); int mCacheSize; ///< Maximum number of cached resources }; ``` ## Common OpenMB Patterns ### Using Project Types ```cpp // ESM::RefId for game object identifiers ESM::RefId mItemId; ESM::RefId mSpellId; // Ptr and ConstPtr for game object references void processObject(const MBWorld::ConstPtr& ptr); void modifyObject(MBWorld::Ptr& ptr); // Common project classes MBWorld::CellStore* cell; MBBase::Environment& env = MBBase::Environment::get(); ``` ### Initialization Lists **ALWAYS use initialization lists for constructors:** ```cpp MyClass::MyClass(int value, const std::string& name) : mValue(value) , mName(name) , mCounter(0) , mIsActive(false) { // Constructor body for additional logic only } ``` ### Const Correctness ```cpp class DataManager { public: // Non-const methods for modification void addData(int value); void clearData(); // Const methods for reading only int getDataCount() const; bool hasData() const; const std::vector& getData() const; // Return const reference when not modifying const std::string& getName() const { return mName; } private: std::vector mData; std::string mName; }; ``` ### Error Handling in Base Classes ```cpp // Use exceptions for unsupported operations in base classes class GameObjectBase { public: virtual InventoryStore& getInventoryStore(const Ptr& ptr) const { throw std::runtime_error("This object type does not have an inventory"); } virtual float getWeight(const Ptr& ptr) const { throw std::runtime_error("This object type does not have weight"); } }; ``` ## File Organization ### Header File Template ```cpp #ifndef OPENMB__H #define OPENMB__H // Includes in proper order #include #include #include // Forward declarations namespace MBWorld { class Ptr; } namespace { /// Class documentation class ClassName { public: // Public interface protected: // Protected members private: // Private members // All member variables with 'm' prefix }; } #endif ``` ### Implementation File Template ```cpp #include "classname.hpp" #include #include #include "../other/project/headers.hpp" namespace { ClassName::ClassName() : mMember1() , mMember2(0) { } void ClassName::method() { // Implementation } } ``` ## Platform Considerations ```cpp // Avoid platform-specific code when possible // If necessary, use clear preprocessor guards #ifdef _WIN32 // Windows-specific code #elif defined(__linux__) // Linux-specific code #elif defined(__APPLE__) // macOS-specific code #else #error "Unsupported platform" #endif // Prefer cross-platform solutions #include // C++17 cross-platform filesystem namespace fs = std::filesystem; ``` ## Testing and Validation When generating code, consider: 1. **Is the code cross-platform compatible?** 2. **Are all member variables initialized?** 3. **Is const correctness maintained?** 4. **Are there proper Doxygen comments?** 5. **Does it follow the naming conventions?** 6. **Is memory managed safely (RAII/smart pointers)?** 7. **Are exceptions used appropriately?** 8. **Does it follow the brace style (Allman)?** ## Quick Reference Checklist Before generating code, verify: - [ ] Braces on their own line (Allman style) - [ ] 4 space indentation (no tabs) - [ ] Member variables have 'm' prefix - [ ] Functions use camelCase - [ ] Classes use PascalCase - [ ] Include guards follow OPENMB__H format - [ ] Includes are in correct order - [ ] Const correctness is maintained - [ ] Smart pointers used instead of raw pointers for ownership - [ ] Override keyword used for virtual functions - [ ] Doxygen comments on public APIs - [ ] Initialization lists used in constructors - [ ] Namespaces properly indented - [ ] Line length under 120 columns - [ ] Exception handling for error conditions ## Example: Complete Class Generation When asked to create a new class, generate both header and implementation following this pattern: **Header (playermanager.hpp):** ```cpp #ifndef OPENMB_APPS_OPENMB_MBWORLD_PLAYERMANAGER_H #define OPENMB_APPS_OPENMB_MBWORLD_PLAYERMANAGER_H #include #include #include #include namespace MBWorld { class Ptr; class CellStore; /// Manages player state and interactions /// /// This class handles player initialization, state updates, /// and interactions with the game world. class PlayerManager { public: PlayerManager(); ~PlayerManager() = default; /// Initialize the player in the game world /// \param playerId The unique identifier for the player /// \param startCell The starting cell for the player /// \return True if initialization succeeded bool initialize(const ESM::RefId& playerId, CellStore* startCell); /// Update player state /// \param deltaTime Time elapsed since last update in seconds void update(float deltaTime); /// Get the current player pointer /// \return Const reference to the player pointer const Ptr& getPlayer() const; /// Check if player is initialized bool isInitialized() const; private: /// Load player data from save void loadPlayerData(const ESM::RefId& playerId); /// Validate player state bool validateState() const; Ptr mPlayer; ESM::RefId mPlayerId; bool mIsInitialized; float mTimeSinceLastUpdate; }; } #endif ``` **Implementation (playermanager.cpp):** ```cpp #include "playermanager.hpp" #include #include "../mbbase/environment.hpp" #include "cellstore.hpp" #include "ptr.hpp" namespace MBWorld { PlayerManager::PlayerManager() : mPlayer() , mPlayerId() , mIsInitialized(false) , mTimeSinceLastUpdate(0.0f) { } bool PlayerManager::initialize(const ESM::RefId& playerId, CellStore* startCell) { if (!startCell) { throw std::invalid_argument("Start cell cannot be null"); } if (playerId.empty()) { throw std::invalid_argument("Player ID cannot be empty"); } mPlayerId = playerId; loadPlayerData(playerId); // Additional initialization logic here mIsInitialized = validateState(); return mIsInitialized; } void PlayerManager::update(float deltaTime) { if (!mIsInitialized) { throw std::runtime_error("Cannot update uninitialized player"); } mTimeSinceLastUpdate += deltaTime; // Update logic here } const Ptr& PlayerManager::getPlayer() const { if (!mIsInitialized) { throw std::runtime_error("Player not initialized"); } return mPlayer; } bool PlayerManager::isInitialized() const { return mIsInitialized; } void PlayerManager::loadPlayerData(const ESM::RefId& playerId) { // Load implementation } bool PlayerManager::validateState() const { // Validation implementation return true; } } ``` ## Summary When generating code for OpenMB: 1. Follow Allman brace style (braces on own lines) 2. Use proper naming (mMemberVars, camelCaseFunctions, PascalCaseClasses) 3. Include Doxygen documentation 4. Use modern C++20 features 5. Maintain const correctness 6. Use smart pointers and RAII 7. Follow include order and guards 8. Keep code cross-platform **Remember: Consistency with existing code is paramount. When in doubt, follow the patterns already established in the codebase.**