Add player viewmodel and fix item icons - v1.6.1

This commit is contained in:
Michael Howard 2026-04-23 15:14:01 -05:00
parent 224b80311a
commit 3d23e0c896
9 changed files with 146 additions and 16 deletions

BIN
assets/stick.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

BIN
assets/wooden_axe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 KiB

View File

@ -18,7 +18,8 @@
enum BlockType {
AIR = 0, GRASS = 1, DIRT = 2, COBBLESTONE = 3, LOG = 4, LEAVES = 5, PLANK = 6,
STONE = 7, BEDROCK = 8, DIAMOND_ORE = 9, IRON_ORE = 10, GRAVEL = 11, CRAFTING_TABLE = 12, SAND = 13
STONE = 7, BEDROCK = 8, DIAMOND_ORE = 9, IRON_ORE = 10, GRAVEL = 11, CRAFTING_TABLE = 12, SAND = 13,
STICK = 14, WOOD_AXE = 15
};
// Simple 2D Perlin Noise implementation
@ -622,7 +623,11 @@ int main(void)
MenuState optionsReturnState = MAIN_MENU;
InventorySlot mouseHeldItem(AIR, 0);
float gameTime = 0.0f; // Total game time for day/night cycle
float gameTime = 75.0f; // Start at 6:00 AM
float breakProgress = 0.0f;
int lastHitX = -1, lastHitY = -1, lastHitZ = -1;
float swingTime = 0.0f;
bool isSwinging = false;
float targetZoom = 1.1f;
float currentZoom = 1.1f;
@ -676,6 +681,10 @@ int main(void)
// Safety Fallback: Use Planks if custom textures failed to load (prevents crash)
if (craftingSideTexture.id == 0) craftingSideTexture = blockTextures[PLANK];
if (craftingTopTexture.id == 0) craftingTopTexture = blockTextures[PLANK];
blockTextures[CRAFTING_TABLE] = craftingSideTexture; // Preview for inventory
blockTextures[STICK] = LoadTexture("assets/stick.png");
blockTextures[WOOD_AXE] = LoadTexture("assets/wooden_axe.png");
// Inventory Crafting State
InventorySlot craftingSlots[4]; // Default to AIR/0
@ -690,14 +699,44 @@ int main(void)
if (craftingSlots[0].blockType == PLANK && craftingSlots[1].blockType == PLANK &&
craftingSlots[2].blockType == PLANK && craftingSlots[3].blockType == PLANK) {
craftingResult = InventorySlot(CRAFTING_TABLE, 1);
} else {
}
// Recipe: 2 Planks (vertical) -> 4 Sticks
else if ((craftingSlots[0].blockType == PLANK && craftingSlots[2].blockType == PLANK) ||
(craftingSlots[1].blockType == PLANK && craftingSlots[3].blockType == PLANK)) {
craftingResult = InventorySlot(STICK, 4);
}
else {
craftingResult = InventorySlot(AIR, 0);
}
};
auto UpdateTableCrafting = [&]() {
// Example: 1 plank -> 4 buttons? No, let's just keep it empty for now or add one.
tableResult = InventorySlot(AIR, 0);
// Wooden Axe Recipe
// [P P .]
// [P S .]
// [. S .]
if (tableSlots[0].blockType == PLANK && tableSlots[1].blockType == PLANK &&
tableSlots[3].blockType == PLANK && tableSlots[4].blockType == STICK &&
tableSlots[7].blockType == STICK) {
tableResult = InventorySlot(WOOD_AXE, 1);
}
// Sticks (vertical anywhere)
else if ((tableSlots[0].blockType == PLANK && tableSlots[3].blockType == PLANK) ||
(tableSlots[1].blockType == PLANK && tableSlots[4].blockType == PLANK) ||
(tableSlots[2].blockType == PLANK && tableSlots[5].blockType == PLANK) ||
(tableSlots[3].blockType == PLANK && tableSlots[6].blockType == PLANK) ||
(tableSlots[4].blockType == PLANK && tableSlots[7].blockType == PLANK) ||
(tableSlots[5].blockType == PLANK && tableSlots[8].blockType == PLANK)) {
tableResult = InventorySlot(STICK, 4);
}
// 4 Planks -> 1 Table (3x3 grid)
else if (tableSlots[0].blockType == PLANK && tableSlots[1].blockType == PLANK &&
tableSlots[3].blockType == PLANK && tableSlots[4].blockType == PLANK) {
tableResult = InventorySlot(CRAFTING_TABLE, 1);
}
else {
tableResult = InventorySlot(AIR, 0);
}
};
// Block Selection State (for wireframe)
bool hitBlock = false;
@ -903,13 +942,41 @@ int main(void)
}
if (hitBlock) {
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
int targetBlock = GetBlock(hitX, hitY, hitZ);
if (targetBlock != BEDROCK) {
AddToInventory(targetBlock);
SetBlock(hitX, hitY, hitZ, AIR);
if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
if (hitX != lastHitX || hitY != lastHitY || hitZ != lastHitZ) {
breakProgress = 0.0f;
lastHitX = hitX; lastHitY = hitY; lastHitZ = hitZ;
}
} else if (IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) {
int targetBlock = GetBlock(hitX, hitY, hitZ);
if (targetBlock != AIR && targetBlock != BEDROCK) {
float breakSpeed = 2.0f; // Seconds to break
// Axe logic: Wood/Plank/Logs are faster
if (targetBlock == LOG || targetBlock == PLANK || targetBlock == CRAFTING_TABLE) {
if (hotbar[activeHotbarSlot].blockType == WOOD_AXE) breakSpeed = 0.5f; // Fast
else breakSpeed = 1.5f; // Normal
} else if (targetBlock == LEAVES) {
breakSpeed = 0.2f; // Very fast
}
breakProgress += GetFrameTime();
if (breakProgress >= breakSpeed) {
AddToInventory(targetBlock);
SetBlock(hitX, hitY, hitZ, AIR);
breakProgress = 0.0f;
isSwinging = true; // Swing when finishing
}
// Visual swing while mining
if (fmodf(breakProgress, 0.4f) < 0.1f) isSwinging = true;
}
} else {
breakProgress = 0.0f;
}
if (IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) {
int targetBlock = GetBlock(hitX, hitY, hitZ);
if (targetBlock == CRAFTING_TABLE) {
currentState = CRAFTING_GUI;
@ -922,6 +989,7 @@ int main(void)
hotbar[activeHotbarSlot].count--;
if (hotbar[activeHotbarSlot].count == 0)
hotbar[activeHotbarSlot].blockType = AIR;
isSwinging = true; // Swing when placing
}
}
}
@ -986,8 +1054,8 @@ int main(void)
DrawTexturePro(titleTexture, sourceRec, destRec, origin, 0.0f, WHITE);
EndMode2D();
// Show Version Number (v1.5.2) in Red
DrawTextEx(customFont, "v1.5.2", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED);
// Show Version Number (v1.6.1) in Red
DrawTextEx(customFont, "v1.6.1", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED);
}
Vector2 mousePos = GetMousePosition();
@ -1417,7 +1485,7 @@ int main(void)
}
// Draw Gameplay overlay if we entered gameplay
if (currentState == GAMEPLAY || currentState == PAUSE_MENU) {
if (currentState == GAMEPLAY || currentState == PAUSE_MENU || currentState == CRAFTING_GUI) {
// ---- Day / Night Cycle Calculations ----
float cycleLength = 300.0f; // 5 minutes
float timeOfDay = fmodf(gameTime, cycleLength) / cycleLength;
@ -1556,8 +1624,66 @@ int main(void)
}
}
// Draw Block Selection Wireframe
if (hitBlock && !inventoryOpen) {
DrawCubeWires((Vector3){(float)hitX, (float)hitY, (float)hitZ}, 1.02f, 1.02f, 1.02f, BLACK);
if (hitBlock && !inventoryOpen && currentState != CRAFTING_GUI) {
float size = 1.02f;
DrawCubeWires((Vector3){(float)hitX, (float)hitY, (float)hitZ}, size, size, size, BLACK);
// Draw breaking progress indicator
// Draw breaking progress indicator
if (breakProgress > 0.0f) {
float pSize = (breakProgress / 1.5f) * 1.05f; // Scale visual based on progress
if (pSize > 1.05f) pSize = 1.05f;
DrawCubeWires((Vector3){(float)hitX, (float)hitY, (float)hitZ}, pSize, pSize, pSize, RED);
}
}
// --- DRAW VIEWMODEL (ARM/HELD ITEM) ---
if (!inventoryOpen && currentState != CRAFTING_GUI) {
if (isSwinging) {
swingTime += GetFrameTime() * 8.0f;
if (swingTime >= 1.0f) {
swingTime = 0.0f;
isSwinging = false;
}
}
float swingVal = sinf(swingTime * PI);
rlDisableDepthTest(); // Draw on top
rlPushMatrix();
// Transform to camera space manually for the viewmodel
Vector3 forwardVM = Vector3Subtract(camera3D.target, camera3D.position);
forwardVM = Vector3Normalize(forwardVM);
Vector3 rightVM = Vector3CrossProduct(forwardVM, camera3D.up);
rightVM = Vector3Normalize(rightVM);
Vector3 upVM = Vector3CrossProduct(rightVM, forwardVM);
upVM = Vector3Normalize(upVM);
Vector3 handPos = Vector3Add(camera3D.position, Vector3Scale(forwardVM, 0.4f));
handPos = Vector3Add(handPos, Vector3Scale(rightVM, 0.25f));
handPos = Vector3Add(handPos, Vector3Scale(upVM, -0.2f - swingVal * 0.1f));
// Draw Arm
DrawCube(handPos, 0.08f, 0.2f, 0.08f, (Color){220, 180, 150, 255});
// Draw Held Item
int heldBT = hotbar[activeHotbarSlot].blockType;
if (heldBT != AIR) {
Vector3 itemPos = Vector3Add(handPos, Vector3Scale(upVM, 0.1f));
itemPos = Vector3Add(itemPos, Vector3Scale(forwardVM, 0.1f));
if (heldBT < STICK) {
// Block
DrawCube(itemPos, 0.15f, 0.15f, 0.15f, blockTint);
DrawCubeWires(itemPos, 0.151f, 0.151f, 0.151f, BLACK);
} else {
// Item (Stick/Axe) - use small thin cube for now
Color itemColor = (heldBT == STICK) ? BROWN : DARKGRAY;
DrawCube(itemPos, 0.03f, 0.25f, 0.03f, itemColor);
}
}
rlPopMatrix();
rlEnableDepthTest();
}
EndMode3D();
@ -1662,6 +1788,10 @@ int main(void)
Rectangle dst = { (float)(px+5),(float)(py+5), (float)(invSlotSize-10),(float)(invSlotSize-10) };
Color tint = (bt == GRASS) ? (Color){124,189,107,255} : WHITE;
DrawTexturePro(tex, src, dst, (Vector2){0,0}, 0.0f, tint);
} else if (bt >= STICK) {
// Fallback rendering for items if texture failed
Color itemCol = (bt == STICK) ? BROWN : DARKGRAY;
DrawRectangle(px + 10, py + 10, invSlotSize - 20, invSlotSize - 20, itemCol);
}
Vector2 cSize = MeasureTextEx(customFont, TextFormat("%i", slot.count), 12, 1.0f);
DrawTextEx(customFont, TextFormat("%i", slot.count),