Combat Update: PvP, Health Hearts, and Spawn Fix - v2.1.2

This commit is contained in:
Michael Howard 2026-04-23 19:37:48 -05:00
parent 2bdfbd9fbc
commit f169322dc0
4 changed files with 111 additions and 23 deletions

Binary file not shown.

Binary file not shown.

View File

@ -91,6 +91,9 @@ static float masterMusicVolume = 1.0f;
static float masterSoundVolume = 1.0f;
static Color myShirtColor = BLUE;
static Color myPantsColor = DARKBLUE;
static float playerHealth = 16.0f;
static uint32_t localPlayerID = 0;
static Sound hitSound;
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 };
@ -780,7 +783,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.1.1");
InitWindow(screenWidth, screenHeight, "MorriCraft v2.1.2");
LoadConfig();
SetExitKey(KEY_NULL); // Prevent ESC from closing the window
@ -844,6 +847,10 @@ int main(void)
int activeNetField = 0; // 0=none, 1=IP, 2=Port
InitNetworking();
InitAudioDevice();
hitSound = LoadSound("assets/hit.wav");
if (hitSound.frameCount == 0) TraceLog(LOG_WARNING, "Hit sound NOT LOADED! Make sure assets/hit.wav exists.");
InventorySlot mouseHeldItem(AIR, 0);
// Chat State
@ -1093,6 +1100,11 @@ int main(void)
SendAll(sock, (char*)&pData, sizeof(pData));
}
}
// Tell the new client its own ID (its socket value)
PacketHeader idHead = { (uint8_t)PACKET_PLAYER_UPDATE, (uint32_t)sizeof(PacketPlayerUpdate) };
PacketPlayerUpdate idData = { camera3D.position.x, camera3D.position.y - 1.6f, camera3D.position.z, camYaw, (uint32_t)sock };
SendAll(sock, (char*)&idHead, sizeof(idHead));
PacketHeader hHead = { (uint8_t)PACKET_PLAYER_UPDATE, (uint32_t)sizeof(PacketPlayerUpdate) };
PacketPlayerUpdate hData = { camera3D.position.x, camera3D.position.y - 1.6f, camera3D.position.z, camYaw, 0 };
SendAll(sock, (char*)&hHead, sizeof(hHead));
@ -1112,16 +1124,21 @@ int main(void)
break;
}
}
if (!found && !isServer) {
if (pu.playerID != 0 && !isServer && pu.playerID != (uint32_t)clientSocket) {
// If it's a new player we haven't seen yet
if (!found) {
RemotePlayer rp;
rp.id = pu.playerID;
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.shirtColor = BLUE;
rp.pantsColor = DARKBLUE;
remotePlayers.push_back(rp);
}
} else if (!isServer && pu.playerID == (uint32_t)clientSocket) {
localPlayerID = pu.playerID;
}
if (isServer) {
pu.playerID = (uint32_t)sock;
for (auto& other : clientSockets) {
@ -1154,6 +1171,13 @@ int main(void)
globalSeedHash = ss.seed;
for (auto& pair : worldChunks) delete pair.second;
worldChunks.clear();
// Force spawn generation to fix "falling through ground"
int scx = (int)floorf(camera3D.position.x / CHUNK_SIZE);
int scz = (int)floorf(camera3D.position.z / CHUNK_SIZE);
GenerateChunk(scx, scz);
float sy = FindSpawnY((int)camera3D.position.x, (int)camera3D.position.z);
camera3D.position.y = sy + 1.6f;
}
} else if (head.type == PACKET_CHAT) {
PacketChat pc;
@ -1167,6 +1191,30 @@ int main(void)
}
}
}
} else if (head.type == PACKET_PLAYER_HIT) {
PacketPlayerHit ph;
if (RecvAll(sock, (char*)&ph, sizeof(ph)) <= 0) break;
if (ph.targetID == localPlayerID) {
// I got hit!
playerHealth -= ph.damage;
playerVelocityY = 6.0f; // Small jump
Vector3 pushDir = Vector3Normalize((Vector3){ camera3D.position.x - ph.attackerX, 0, camera3D.position.z - ph.attackerZ });
camera3D.position.x += pushDir.x * 0.8f;
camera3D.position.z += pushDir.z * 0.8f;
PlaySound(hitSound);
chatLog.push_back({ "You were hit!", 2.0f });
}
if (isServer) {
// Broadcast hit to all OTHER clients
for (auto& other : clientSockets) {
if (other != sock) {
SendAll(other, (char*)&head, sizeof(head));
SendAll(other, (char*)&ph, sizeof(ph));
}
}
}
} else {
if (head.size > 0 && head.size < 2048) {
std::vector<char> discard(head.size);
@ -1452,6 +1500,34 @@ int main(void)
// Block Raycasting (moved outside to update every frame for wireframe)
hitBlock = false;
if (!inventoryOpen && !isChatting) {
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
isSwinging = true;
swingTime = 0.0f;
// PvP Attack Raycast
Ray ray = GetMouseRay((Vector2){ (float)currentWidth / 2, (float)currentHeight / 2 }, camera3D);
for (auto& rp : remotePlayers) {
BoundingBox pBox = { (Vector3){ rp.position.x - 0.4f, rp.position.y, rp.position.z - 0.4f },
(Vector3){ rp.position.x + 0.4f, rp.position.y + 1.8f, rp.position.z + 0.4f } };
RayCollision rCol = GetRayCollisionBox(ray, pBox);
if (rCol.hit && rCol.distance < 4.5f) {
PacketHeader hitH = { (uint8_t)PACKET_PLAYER_HIT, (uint32_t)sizeof(PacketPlayerHit) };
PacketPlayerHit hitD = { rp.id, 1.0f, camera3D.position.x, camera3D.position.z };
if (clientSocket != INVALID_SOCKET_VAL) {
SendAll(clientSocket, (char*)&hitH, sizeof(hitH));
SendAll(clientSocket, (char*)&hitD, sizeof(hitD));
} else if (serverMode) {
for (auto& s : clientSockets) {
if (s == (Socket)rp.id) {
SendAll(s, (char*)&hitH, sizeof(hitH));
SendAll(s, (char*)&hitD, sizeof(hitD));
}
}
}
break;
}
}
}
Ray ray = GetMouseRay((Vector2){ (float)currentWidth / 2, (float)currentHeight / 2 }, camera3D);
float closestDist = 8.0f;
@ -1868,8 +1944,8 @@ int main(void)
DrawTexturePro(titleTexture, sourceRec, destRec, origin, 0.0f, WHITE);
EndMode2D();
// Show Version Number (v2.1.1) in Red
DrawTextEx(customFont, "v2.1.1", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED);
// Show Version Number (v2.1.2) in Red
DrawTextEx(customFont, "v2.1.2", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED);
// --- PLAYER NAME POPUP (IF MISSING) ---
if (playerName == "") {
@ -2602,16 +2678,6 @@ int main(void)
camera3D.position.x, camera3D.position.y - 1.6f, camera3D.position.z),
(Vector2){ 10, 80 }, 16, 1.0f, DARKGRAY);
// --- In-Game Clock ---
int totalMinutes = (int)(timeOfDay * 24 * 60);
int hours = (totalMinutes / 60) % 24;
int minutes = totalMinutes % 60;
const char* ampm = (hours >= 12) ? "PM" : "AM";
int displayHours = hours % 12;
if (displayHours == 0) displayHours = 12;
DrawTextEx(customFont, TextFormat("Time: %i:%02i %s", displayHours, minutes, ampm), (Vector2){ 10, 110 }, 18, 1.0f, (dayFactor > 0.5f) ? BLACK : WHITE);
DrawTextEx(customFont, (dayFactor > 0.5f) ? "Status: Day" : "Status: Night", (Vector2){ 10, 130 }, 16, 1.0f, (dayFactor > 0.5f) ? (Color){180, 150, 0, 255} : (Color){100, 100, 255, 255});
// Draw Chat Log (Bottom Left, above inventory)
int chatY = currentHeight - 120;
@ -2640,6 +2706,21 @@ int main(void)
int hotbarX = currentWidth / 2 - hotbarW / 2;
int hotbarY = currentHeight - slotSize - 12;
// --- Health Hearts ---
int heartX = currentWidth / 2 - 225;
int heartY = hotbarY - 35;
for (int i = 0; i < 8; i++) {
Rectangle heartRect = { (float)heartX + i * 28, (float)heartY, 24, 24 };
float h = playerHealth - (i * 2);
if (h >= 2.0f) DrawRectangleRec(heartRect, RED); // Full heart
else if (h >= 1.0f) {
DrawRectangle((int)heartRect.x, (int)heartRect.y, 12, 24, RED); // Half heart
DrawRectangleLinesEx(heartRect, 2.0f, DARKGRAY);
} else {
DrawRectangleLinesEx(heartRect, 2.0f, GRAY); // Empty heart
}
}
// Hotbar background panel
DrawRectangle(hotbarX - 6, hotbarY - 6, hotbarW + 12, slotSize + 12,
(Color){ 30, 30, 30, 180 });

View File

@ -54,7 +54,14 @@ enum PacketType {
PACKET_TIME_SYNC = 3,
PACKET_SEED_SYNC = 4,
PACKET_CHAT = 5,
PACKET_DISCONNECT = 6
PACKET_DISCONNECT = 6,
PACKET_PLAYER_HIT = 7
};
struct PacketPlayerHit {
uint32_t targetID;
float damage;
float attackerX, attackerZ;
};
struct PacketHeader {