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
|
## 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.
|
|
@ -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 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
v2.0.9
|
v2.1.0
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue