Skin Editor, Network Color Sync, and Enhanced World Sync - v2.1.0
This commit is contained in:
parent
ed11185fe3
commit
23f941247d
14
README.md
14
README.md
|
|
@ -13,10 +13,16 @@ MorriCraft is a high-performance voxel engine built with C++ and Raylib. It feat
|
|||
|
||||
## Version History
|
||||
|
||||
### v2.0.9 - Identity Update (Latest)
|
||||
* **Humanoid Player Models**: Replaced placeholder "blue blobs" with multi-cube humanoid shapes (Head, Body, Arms, Legs).
|
||||
* **Chat Input Isolation**: All movement (WASD, Space) and action keys are now fully inhibited while the chat box is active.
|
||||
* **Visual Polish**: Adjusted nameplate positioning for better clarity with the new player models.
|
||||
### v2.1.0 - Personalization & Stability (Latest)
|
||||
* **Integrated Skin Editor**: Personalize your shirt and pants colors directly from the Options menu.
|
||||
* **Network Skin Sync**: Custom skin colors are now synchronized between all players via an updated handshake protocol.
|
||||
* **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
|
||||
* **Real Update System**: Implemented live file download via `curl` for seamless client updates.
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1 +1 @@
|
|||
v2.0.9
|
||||
v2.1.0
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1 +1 @@
|
|||
v2.0.9
|
||||
v2.1.0
|
||||
|
|
|
|||
109
src/main.cpp
109
src/main.cpp
|
|
@ -89,8 +89,10 @@ static std::string playerName = "Player";
|
|||
static bool serverMode = false;
|
||||
static float masterMusicVolume = 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
|
||||
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;
|
||||
Vector3 position;
|
||||
float yaw;
|
||||
Color shirtColor;
|
||||
Color pantsColor;
|
||||
};
|
||||
static std::vector<RemotePlayer> remotePlayers;
|
||||
|
||||
|
|
@ -313,11 +317,10 @@ void SetBlock(int x, int y, int z, int type) {
|
|||
// Keep maxY up to date
|
||||
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; }
|
||||
|
||||
// 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 << "music=" << masterMusicVolume << "\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();
|
||||
}
|
||||
}
|
||||
|
|
@ -775,7 +780,7 @@ int main(void)
|
|||
// By default, windows have minimize, maximize, and close buttons on the top bar.
|
||||
SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT);
|
||||
|
||||
InitWindow(screenWidth, screenHeight, "MorriCraft v2.0.9");
|
||||
InitWindow(screenWidth, screenHeight, "MorriCraft v2.1.0");
|
||||
LoadConfig();
|
||||
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) };
|
||||
PacketHandshake hand;
|
||||
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*)&hand, sizeof(hand));
|
||||
}
|
||||
|
|
@ -1054,6 +1061,8 @@ int main(void)
|
|||
rp.id = newID;
|
||||
rp.name = hand.name;
|
||||
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);
|
||||
|
||||
chatLog.push_back({ std::string(hand.name) + " joined the game", 5.0f });
|
||||
|
|
@ -1109,6 +1118,8 @@ int main(void)
|
|||
rp.name = "Remote Player";
|
||||
rp.position = (Vector3){ pu.x, pu.y, pu.z };
|
||||
rp.yaw = pu.yaw;
|
||||
rp.shirtColor = BLUE; // Default until handshake syncs
|
||||
rp.pantsColor = DARKBLUE;
|
||||
remotePlayers.push_back(rp);
|
||||
}
|
||||
if (isServer) {
|
||||
|
|
@ -1693,7 +1704,68 @@ int main(void)
|
|||
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 });
|
||||
int cw = 500, ch = 350;
|
||||
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);
|
||||
EndMode2D();
|
||||
|
||||
// Show Version Number (v2.0.9) in Red
|
||||
DrawTextEx(customFont, "v2.0.9", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED);
|
||||
// Show Version Number (v2.1.0) in Red
|
||||
DrawTextEx(customFont, "v2.1.0", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED);
|
||||
|
||||
// --- PLAYER NAME POPUP (IF MISSING) ---
|
||||
if (playerName == "") {
|
||||
|
|
@ -2120,6 +2192,17 @@ int main(void)
|
|||
DrawRectangleRec(soundHandle, LIGHTGRAY);
|
||||
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
|
||||
|
||||
// Done Button
|
||||
|
|
@ -2344,19 +2427,19 @@ int main(void)
|
|||
|
||||
// Simple Humanoid Shape (Cube-based "Skin")
|
||||
// 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
|
||||
DrawCube((Vector3){0, 1.4f, 0}, 0.45f, 0.45f, 0.45f, (Color){220, 180, 150, 255});
|
||||
// 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});
|
||||
// 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, 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, rp.pantsColor);
|
||||
rlPopMatrix();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ struct PacketHeader {
|
|||
|
||||
struct PacketHandshake {
|
||||
char name[32];
|
||||
uint8_t shirtR, shirtG, shirtB;
|
||||
uint8_t pantsR, pantsG, pantsB;
|
||||
};
|
||||
|
||||
struct PacketPlayerUpdate {
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
v2.0.9
|
||||
v2.1.0
|
||||
|
|
|
|||
Loading…
Reference in New Issue