Skin Editor, Network Color Sync, and Enhanced World Sync - v2.1.0

This commit is contained in:
Michael Howard 2026-04-23 19:02:30 -05:00
parent ed11185fe3
commit 23f941247d
8 changed files with 111 additions and 20 deletions

View File

@ -13,10 +13,16 @@ MorriCraft is a high-performance voxel engine built with C++ and Raylib. It feat
## Version History ## Version History
### v2.0.9 - Identity Update (Latest) ### v2.1.0 - Personalization & Stability (Latest)
* **Humanoid Player Models**: Replaced placeholder "blue blobs" with multi-cube humanoid shapes (Head, Body, Arms, Legs). * **Integrated Skin Editor**: Personalize your shirt and pants colors directly from the Options menu.
* **Chat Input Isolation**: All movement (WASD, Space) and action keys are now fully inhibited while the chat box is active. * **Network Skin Sync**: Custom skin colors are now synchronized between all players via an updated handshake protocol.
* **Visual Polish**: Adjusted nameplate positioning for better clarity with the new player models. * **Enhanced World Sync**: Increased chunk rebuild priority and optimized block update broadcasting for real-time consistency.
* **Refined Nameplates**: Elevated nameplates to 2.1f for better visibility above the new humanoid models.
* **Diagnostic Logging**: Added developer-focused logging for world state changes to ensure parity.
### v2.0.9 - Identity Update
* **Humanoid Player Models**: Replaced placeholder "blue blobs" with multi-cube humanoid shapes.
* **Chat Input Isolation**: Inhibited movement and action keys while the chat interface is active.
### v2.0.8 - Real Update & LAN ### v2.0.8 - Real Update & LAN
* **Real Update System**: Implemented live file download via `curl` for seamless client updates. * **Real Update System**: Implemented live file download via `curl` for seamless client updates.

Binary file not shown.

View File

@ -1 +1 @@
v2.0.9 v2.1.0

Binary file not shown.

View File

@ -1 +1 @@
v2.0.9 v2.1.0

View File

@ -89,8 +89,10 @@ static std::string playerName = "Player";
static bool serverMode = false; static bool serverMode = false;
static float masterMusicVolume = 1.0f; static float masterMusicVolume = 1.0f;
static float masterSoundVolume = 1.0f; static float masterSoundVolume = 1.0f;
static Color myShirtColor = BLUE;
static Color myPantsColor = DARKBLUE;
enum MenuState { MAIN_MENU, OPTIONS_MENU, CREATE_WORLD_MENU, LOAD_WORLD_MENU, GAMEPLAY, PAUSE_MENU, CRAFTING_GUI, CHECKING_UPDATES, UPDATE_NOTES, UPDATE_FOUND, DOWNLOADING_UPDATE, CONNECT_MENU }; enum MenuState { MAIN_MENU, OPTIONS_MENU, CREATE_WORLD_MENU, LOAD_WORLD_MENU, GAMEPLAY, PAUSE_MENU, CRAFTING_GUI, CHECKING_UPDATES, UPDATE_NOTES, UPDATE_FOUND, DOWNLOADING_UPDATE, CONNECT_MENU, SKIN_EDITOR };
// Forward Declarations // Forward Declarations
bool IsExposedOptimized(int lx, int ly, int lz, Chunk* chunk, Chunk* nxM, Chunk* nxP, Chunk* nzM, Chunk* nzP); bool IsExposedOptimized(int lx, int ly, int lz, Chunk* chunk, Chunk* nxM, Chunk* nxP, Chunk* nzM, Chunk* nzP);
@ -107,6 +109,8 @@ struct RemotePlayer {
std::string name; std::string name;
Vector3 position; Vector3 position;
float yaw; float yaw;
Color shirtColor;
Color pantsColor;
}; };
static std::vector<RemotePlayer> remotePlayers; static std::vector<RemotePlayer> remotePlayers;
@ -313,11 +317,10 @@ void SetBlock(int x, int y, int z, int type) {
// Keep maxY up to date // Keep maxY up to date
if (type != AIR && y > worldChunks[key]->maxY) worldChunks[key]->maxY = y; if (type != AIR && y > worldChunks[key]->maxY) worldChunks[key]->maxY = y;
// Mark neighbors dirty as well since exposure changes
if (lx == 0) { auto n = worldChunks.find({cx-1, cz}); if (n != worldChunks.end()) n->second->dirty = true; }
if (lx == CHUNK_SIZE-1) { auto n = worldChunks.find({cx+1, cz}); if (n != worldChunks.end()) n->second->dirty = true; }
if (lz == 0) { auto n = worldChunks.find({cx, cz-1}); if (n != worldChunks.end()) n->second->dirty = true; }
if (lz == CHUNK_SIZE-1) { auto n = worldChunks.find({cx, cz+1}); if (n != worldChunks.end()) n->second->dirty = true; } if (lz == CHUNK_SIZE-1) { auto n = worldChunks.find({cx, cz+1}); if (n != worldChunks.end()) n->second->dirty = true; }
// Diagnostic log for block changes (helps debug sync)
TraceLog(LOG_INFO, "Block set at %d, %d, %d to %d", x, y, z, type);
} }
} }
@ -344,6 +347,8 @@ void SaveConfig() {
file << "playerName=" << playerName << "\n"; file << "playerName=" << playerName << "\n";
file << "music=" << masterMusicVolume << "\n"; file << "music=" << masterMusicVolume << "\n";
file << "sound=" << masterSoundVolume << "\n"; file << "sound=" << masterSoundVolume << "\n";
file << "shirt=" << (int)myShirtColor.r << "," << (int)myShirtColor.g << "," << (int)myShirtColor.b << "\n";
file << "pants=" << (int)myPantsColor.r << "," << (int)myPantsColor.g << "," << (int)myPantsColor.b << "\n";
file.close(); file.close();
} }
} }
@ -775,7 +780,7 @@ int main(void)
// By default, windows have minimize, maximize, and close buttons on the top bar. // By default, windows have minimize, maximize, and close buttons on the top bar.
SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT); SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT);
InitWindow(screenWidth, screenHeight, "MorriCraft v2.0.9"); InitWindow(screenWidth, screenHeight, "MorriCraft v2.1.0");
LoadConfig(); LoadConfig();
SetExitKey(KEY_NULL); // Prevent ESC from closing the window SetExitKey(KEY_NULL); // Prevent ESC from closing the window
@ -998,6 +1003,8 @@ int main(void)
PacketHeader head = { (uint8_t)PACKET_HANDSHAKE, (uint32_t)sizeof(PacketHandshake) }; PacketHeader head = { (uint8_t)PACKET_HANDSHAKE, (uint32_t)sizeof(PacketHandshake) };
PacketHandshake hand; PacketHandshake hand;
strncpy(hand.name, playerName.c_str(), 31); strncpy(hand.name, playerName.c_str(), 31);
hand.shirtR = myShirtColor.r; hand.shirtG = myShirtColor.g; hand.shirtB = myShirtColor.b;
hand.pantsR = myPantsColor.r; hand.pantsG = myPantsColor.g; hand.pantsB = myPantsColor.b;
SendAll(clientSocket, (char*)&head, sizeof(head)); SendAll(clientSocket, (char*)&head, sizeof(head));
SendAll(clientSocket, (char*)&hand, sizeof(hand)); SendAll(clientSocket, (char*)&hand, sizeof(hand));
} }
@ -1054,6 +1061,8 @@ int main(void)
rp.id = newID; rp.id = newID;
rp.name = hand.name; rp.name = hand.name;
rp.position = (Vector3){0,0,0}; rp.position = (Vector3){0,0,0};
rp.shirtColor = (Color){ hand.shirtR, hand.shirtG, hand.shirtB, 255 };
rp.pantsColor = (Color){ hand.pantsR, hand.pantsG, hand.pantsB, 255 };
remotePlayers.push_back(rp); remotePlayers.push_back(rp);
chatLog.push_back({ std::string(hand.name) + " joined the game", 5.0f }); chatLog.push_back({ std::string(hand.name) + " joined the game", 5.0f });
@ -1109,6 +1118,8 @@ int main(void)
rp.name = "Remote Player"; rp.name = "Remote Player";
rp.position = (Vector3){ pu.x, pu.y, pu.z }; rp.position = (Vector3){ pu.x, pu.y, pu.z };
rp.yaw = pu.yaw; rp.yaw = pu.yaw;
rp.shirtColor = BLUE; // Default until handshake syncs
rp.pantsColor = DARKBLUE;
remotePlayers.push_back(rp); remotePlayers.push_back(rp);
} }
if (isServer) { if (isServer) {
@ -1693,7 +1704,68 @@ int main(void)
DrawTextEx(customFont, "EXIT GAME", (Vector2){ exitBtn.x + 30, exitBtn.y + 10 }, 20, 1.0f, WHITE); DrawTextEx(customFont, "EXIT GAME", (Vector2){ exitBtn.x + 30, exitBtn.y + 10 }, 20, 1.0f, WHITE);
} }
} }
} else if (currentState == CONNECT_MENU) { } else if (currentState == SKIN_EDITOR) {
DrawRectangle(0, 0, currentWidth, currentHeight, (Color){ 20, 20, 20, 255 });
int panelWidth = 700;
int panelHeight = 500;
int panelX = (currentWidth / 2) - (panelWidth / 2);
int panelY = (currentHeight / 2) - (panelHeight / 2);
DrawRectangle(panelX, panelY, panelWidth, panelHeight, (Color){ 40, 40, 40, 240 });
DrawRectangleLinesEx((Rectangle){ (float)panelX, (float)panelY, (float)panelWidth, (float)panelHeight }, 4.0f, BLUE);
DrawTextEx(customFont, "Skin Editor", (Vector2){ (float)panelX + 20, (float)panelY + 20 }, 32, 1.0f, WHITE);
// Preview Model (3D)
static float previewRot = 0.0f;
previewRot += GetFrameTime() * 30.0f;
Camera3D previewCam = { 0 };
previewCam.position = (Vector3){ 3.0f, 2.0f, 3.0f };
previewCam.target = (Vector3){ 0.0f, 1.0f, 0.0f };
previewCam.up = (Vector3){ 0.0f, 1.0f, 0.0f };
previewCam.fovy = 45.0f;
previewCam.projection = CAMERA_PERSPECTIVE;
BeginMode3D(previewCam);
rlPushMatrix();
rlRotatef(previewRot, 0, 1, 0);
// Render Humanoid
DrawCube((Vector3){0, 0.7f, 0}, 0.6f, 0.9f, 0.3f, myShirtColor);
DrawCube((Vector3){0, 1.4f, 0}, 0.45f, 0.45f, 0.45f, (Color){220, 180, 150, 255});
DrawCube((Vector3){-0.45f, 0.7f, 0}, 0.2f, 0.8f, 0.2f, (Color){200, 160, 130, 255});
DrawCube((Vector3){0.45f, 0.7f, 0}, 0.2f, 0.8f, 0.2f, (Color){200, 160, 130, 255});
DrawCube((Vector3){-0.15f, 0.15f, 0}, 0.25f, 0.4f, 0.25f, myPantsColor);
DrawCube((Vector3){0.15f, 0.15f, 0}, 0.25f, 0.4f, 0.25f, myPantsColor);
rlPopMatrix();
EndMode3D();
// Color Pickers
Color shirtPresets[] = { RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, WHITE, BLACK };
Color pantsPresets[] = { DARKBLUE, BROWN, DARKGRAY, BLACK, DARKGREEN, MAROON };
DrawTextEx(customFont, "Shirt Color", (Vector2){ (float)panelX + 400, (float)panelY + 100 }, 20, 1.0f, LIGHTGRAY);
for (int i = 0; i < 8; i++) {
Rectangle r = { (float)panelX + 400 + (i % 4) * 50, (float)panelY + 130 + (i / 4) * 50, 40, 40 };
DrawRectangleRec(r, shirtPresets[i]);
if (CheckCollisionPointRec(mousePos, r) && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) myShirtColor = shirtPresets[i];
if (myShirtColor.r == shirtPresets[i].r && myShirtColor.g == shirtPresets[i].g && myShirtColor.b == shirtPresets[i].b) DrawRectangleLinesEx(r, 2.0f, WHITE);
}
DrawTextEx(customFont, "Pants Color", (Vector2){ (float)panelX + 400, (float)panelY + 250 }, 20, 1.0f, LIGHTGRAY);
for (int i = 0; i < 6; i++) {
Rectangle r = { (float)panelX + 400 + (i % 3) * 50, (float)panelY + 280 + (i / 3) * 50, 40, 40 };
DrawRectangleRec(r, pantsPresets[i]);
if (CheckCollisionPointRec(mousePos, r) && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) myPantsColor = pantsPresets[i];
if (myPantsColor.r == pantsPresets[i].r && myPantsColor.g == pantsPresets[i].g && myPantsColor.b == pantsPresets[i].b) DrawRectangleLinesEx(r, 2.0f, WHITE);
}
Rectangle doneBtn = { (float)panelX + panelWidth - 150, (float)panelY + panelHeight - 60, 120, 40 };
if (CheckCollisionPointRec(mousePos, doneBtn) && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
SaveConfig();
currentState = OPTIONS_MENU;
}
DrawRectangleRec(doneBtn, GREEN);
DrawTextEx(customFont, "SAVE", (Vector2){ doneBtn.x + 35, doneBtn.y + 10 }, 20, 1.0f, WHITE);
DrawRectangle(0, 0, currentWidth, currentHeight, (Color){ 0, 0, 0, 220 }); DrawRectangle(0, 0, currentWidth, currentHeight, (Color){ 0, 0, 0, 220 });
int cw = 500, ch = 350; int cw = 500, ch = 350;
Rectangle cBox = { (float)currentWidth/2 - cw/2, (float)currentHeight/2 - ch/2, (float)cw, (float)ch }; Rectangle cBox = { (float)currentWidth/2 - cw/2, (float)currentHeight/2 - ch/2, (float)cw, (float)ch };
@ -1795,8 +1867,8 @@ int main(void)
DrawTexturePro(titleTexture, sourceRec, destRec, origin, 0.0f, WHITE); DrawTexturePro(titleTexture, sourceRec, destRec, origin, 0.0f, WHITE);
EndMode2D(); EndMode2D();
// Show Version Number (v2.0.9) in Red // Show Version Number (v2.1.0) in Red
DrawTextEx(customFont, "v2.0.9", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED); DrawTextEx(customFont, "v2.1.0", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED);
// --- PLAYER NAME POPUP (IF MISSING) --- // --- PLAYER NAME POPUP (IF MISSING) ---
if (playerName == "") { if (playerName == "") {
@ -2120,6 +2192,17 @@ int main(void)
DrawRectangleRec(soundHandle, LIGHTGRAY); DrawRectangleRec(soundHandle, LIGHTGRAY);
DrawRectangleLinesEx(soundHandle, 2.0f, WHITE); DrawRectangleLinesEx(soundHandle, 2.0f, WHITE);
// Edit Skin Button
Rectangle skinBtn = { (float)panelX + panelWidth/2 - 100, (float)panelY + 310, 200, 40 };
bool isSkinHovered = CheckCollisionPointRec(mousePos, skinBtn);
DrawRectangleRec(skinBtn, isSkinHovered ? BLUE : DARKBLUE);
DrawRectangleLinesEx(skinBtn, 2.0f, isSkinHovered ? WHITE : GRAY);
DrawTextEx(customFont, "EDIT SKIN", (Vector2){ skinBtn.x + 55, skinBtn.y + 10 }, 20, 1.0f, WHITE);
if (isSkinHovered && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
currentState = SKIN_EDITOR;
}
// No Multiplayer options here anymore, moved to Connect Menu // No Multiplayer options here anymore, moved to Connect Menu
// Done Button // Done Button
@ -2344,19 +2427,19 @@ int main(void)
// Simple Humanoid Shape (Cube-based "Skin") // Simple Humanoid Shape (Cube-based "Skin")
// Body // Body
DrawCube((Vector3){0, 0.7f, 0}, 0.6f, 0.9f, 0.3f, BLUE); DrawCube((Vector3){0, 0.7f, 0}, 0.6f, 0.9f, 0.3f, rp.shirtColor);
// Head // Head
DrawCube((Vector3){0, 1.4f, 0}, 0.45f, 0.45f, 0.45f, (Color){220, 180, 150, 255}); DrawCube((Vector3){0, 1.4f, 0}, 0.45f, 0.45f, 0.45f, (Color){220, 180, 150, 255});
// Arms // Arms
DrawCube((Vector3){-0.45f, 0.7f, 0}, 0.2f, 0.8f, 0.2f, (Color){200, 160, 130, 255}); DrawCube((Vector3){-0.45f, 0.7f, 0}, 0.2f, 0.8f, 0.2f, (Color){200, 160, 130, 255});
DrawCube((Vector3){0.45f, 0.7f, 0}, 0.2f, 0.8f, 0.2f, (Color){200, 160, 130, 255}); DrawCube((Vector3){0.45f, 0.7f, 0}, 0.2f, 0.8f, 0.2f, (Color){200, 160, 130, 255});
// Legs // Legs
DrawCube((Vector3){-0.15f, 0.15f, 0}, 0.25f, 0.4f, 0.25f, DARKBLUE); DrawCube((Vector3){-0.15f, 0.15f, 0}, 0.25f, 0.4f, 0.25f, rp.pantsColor);
DrawCube((Vector3){0.15f, 0.15f, 0}, 0.25f, 0.4f, 0.25f, DARKBLUE); DrawCube((Vector3){0.15f, 0.15f, 0}, 0.25f, 0.4f, 0.25f, rp.pantsColor);
rlPopMatrix(); rlPopMatrix();
// Draw Nameplate // Draw Nameplate
Vector2 namePos = GetWorldToScreen((Vector3){ rp.position.x, rp.position.y + 1.8f, rp.position.z }, camera3D); Vector2 namePos = GetWorldToScreen((Vector3){ rp.position.x, rp.position.y + 2.1f, rp.position.z }, camera3D);
DrawTextEx(customFont, rp.name.c_str(), (Vector2){ namePos.x - MeasureTextEx(customFont, rp.name.c_str(), 20, 1.0f).x/2, namePos.y }, 20, 1.0f, WHITE); DrawTextEx(customFont, rp.name.c_str(), (Vector2){ namePos.x - MeasureTextEx(customFont, rp.name.c_str(), 20, 1.0f).x/2, namePos.y }, 20, 1.0f, WHITE);
} }

View File

@ -64,6 +64,8 @@ struct PacketHeader {
struct PacketHandshake { struct PacketHandshake {
char name[32]; char name[32];
uint8_t shirtR, shirtG, shirtB;
uint8_t pantsR, pantsG, pantsB;
}; };
struct PacketPlayerUpdate { struct PacketPlayerUpdate {

View File

@ -1 +1 @@
v2.0.9 v2.1.0