From 937639082b2b351e9e46b09b71449596152601d4 Mon Sep 17 00:00:00 2001 From: GESUS <105702323+DevAlexandre0@users.noreply.github.com> Date: Tue, 12 Aug 2025 17:27:06 +0700 Subject: [PATCH 01/10] Update script.js --- nui/script.js | 625 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 554 insertions(+), 71 deletions(-) diff --git a/nui/script.js b/nui/script.js index 632ca73..d942e8a 100644 --- a/nui/script.js +++ b/nui/script.js @@ -1,4 +1,3 @@ -// Global state const state = { isVisible: false, recipes: [], @@ -7,16 +6,30 @@ const state = { skill: { level: 1, xp: 0, nextLevelXP: 100 }, expandedRecipes: {}, activeTab: "all", + searchQuery: "", + sortBy: "name", + filterBy: "all", + favorites: JSON.parse(localStorage.getItem("campfire-favorites") || "[]"), + cookingQueue: [], + theme: localStorage.getItem("campfire-theme") || "dark", + notifications: [], + isCreatingRecipe: false, + customRecipes: JSON.parse(localStorage.getItem("campfire-custom-recipes") || "[]"), } -// Initialize when DOM is loaded document.addEventListener("DOMContentLoaded", () => { + // Apply saved theme + document.body.className = `${state.theme}-theme` + + // Load saved data + loadSavedData() + // Listen for messages from the game client window.addEventListener("message", ({ data }) => { switch (data.action) { case "openCookingMenu": state.isVisible = true - state.recipes = data.recipes || [] + state.recipes = [...(data.recipes || []), ...state.customRecipes] state.inventory = data.inventory || {} state.fuelLevel = data.fuelLevel || 0 if (data.skill) state.skill = data.skill @@ -34,38 +47,365 @@ document.addEventListener("DOMContentLoaded", () => { case "updateSkill": if (data.skill) state.skill = data.skill break + case "cookingComplete": + handleCookingComplete(data.recipeId) + break default: break } renderUI() }) + setInterval(saveData, 30000) // Save every 30 seconds + + setInterval(updateCookingQueue, 1000) + // Initial render renderUI() }) -// Format time helper -function formatTime(ms) { - const seconds = Math.floor(ms / 1000) - if (seconds < 60) return `${seconds}s` - const minutes = Math.floor(seconds / 60) - const remainingSeconds = seconds % 60 - return `${minutes}m ${remainingSeconds}s` +function loadSavedData() { + const savedFavorites = localStorage.getItem("campfire-favorites") + if (savedFavorites) { + state.favorites = JSON.parse(savedFavorites) + } + + const savedCustomRecipes = localStorage.getItem("campfire-custom-recipes") + if (savedCustomRecipes) { + state.customRecipes = JSON.parse(savedCustomRecipes) + } } -// Escape HTML to prevent injection -function escapeHtml(text = "") { - const map = { - "&": "&", - "<": "<", - ">": ">", - '"': """, - "'": "'", +function saveData() { + localStorage.setItem("campfire-favorites", JSON.stringify(state.favorites)) + localStorage.setItem("campfire-custom-recipes", JSON.stringify(state.customRecipes)) + localStorage.setItem("campfire-theme", state.theme) +} + +function getFilteredRecipes() { + const filtered = state.recipes.filter((recipe) => { + // Tab filter + if (state.activeTab !== "all" && recipe.category !== state.activeTab) { + return false + } + + // Search filter + if (state.searchQuery) { + const query = state.searchQuery.toLowerCase() + const matchesName = recipe.label.toLowerCase().includes(query) + const matchesDescription = recipe.description.toLowerCase().includes(query) + const matchesIngredients = recipe.ingredients.some((ing) => ing.name.toLowerCase().includes(query)) + if (!matchesName && !matchesDescription && !matchesIngredients) { + return false + } + } + + // Additional filters + if (state.filterBy === "favorites" && !state.favorites.includes(recipe.id)) { + return false + } + if (state.filterBy === "cookable") { + const hasAllIngredients = recipe.ingredients.every( + (ingredient) => (state.inventory[ingredient.name] || 0) >= ingredient.count, + ) + const requiredFuel = (recipe.cookTime / 1000) * 0.5 + const hasFuel = state.fuelLevel >= requiredFuel + if (!hasAllIngredients || !hasFuel) { + return false + } + } + + return true + }) + + // Sort recipes + filtered.sort((a, b) => { + switch (state.sortBy) { + case "name": + return a.label.localeCompare(b.label) + case "cookTime": + return a.cookTime - b.cookTime + case "difficulty": + return (a.difficulty || 1) - (b.difficulty || 1) + default: + return 0 + } + }) + + return filtered +} + +function toggleFavorite(recipeId) { + const index = state.favorites.indexOf(recipeId) + if (index === -1) { + state.favorites.push(recipeId) + showNotification("Added to favorites!", "success") + } else { + state.favorites.splice(index, 1) + showNotification("Removed from favorites", "success") } - return String(text).replace(/[&<>"']/g, (m) => map[m]) + saveData() + renderUI() +} + +function toggleTheme() { + state.theme = state.theme === "dark" ? "light" : "dark" + document.body.className = `${state.theme}-theme` + saveData() + showNotification(`Switched to ${state.theme} theme`, "success") +} + +function showNotification(message, type = "info", duration = 3000) { + const notification = { + id: Date.now(), + message, + type, + timestamp: Date.now(), + } + + state.notifications.push(notification) + + // Create notification element + const notificationEl = document.createElement("div") + notificationEl.className = `notification ${type}` + notificationEl.innerHTML = ` +
+ + ${escapeHtml(message)} +
+ ` + + document.body.appendChild(notificationEl) + + // Remove after duration + setTimeout(() => { + if (notificationEl.parentNode) { + notificationEl.parentNode.removeChild(notificationEl) + } + state.notifications = state.notifications.filter((n) => n.id !== notification.id) + }, duration) +} + +function getNotificationIcon(type) { + switch (type) { + case "success": + return "fa-check-circle" + case "error": + return "fa-exclamation-circle" + case "warning": + return "fa-exclamation-triangle" + default: + return "fa-info-circle" + } +} + +function addToCookingQueue(recipeId) { + const recipe = state.recipes.find((r) => r.id === recipeId) + if (!recipe) return + + const cookingItem = { + id: Date.now(), + recipeId: recipeId, + recipeName: recipe.label, + startTime: Date.now(), + duration: recipe.cookTime, + progress: 0, + } + + state.cookingQueue.push(cookingItem) + showNotification(`Started cooking ${recipe.label}`, "success") + renderUI() +} + +function updateCookingQueue() { + const now = Date.now() + let updated = false + + state.cookingQueue = state.cookingQueue.filter((item) => { + const elapsed = now - item.startTime + item.progress = Math.min(100, (elapsed / item.duration) * 100) + + if (elapsed >= item.duration) { + // Cooking complete + showNotification(`${item.recipeName} is ready!`, "success") + handleCookingComplete(item.recipeId) + updated = true + return false + } + return true + }) + + if (updated) { + renderUI() + } +} + +function handleCookingComplete(recipeId) { + // Add XP and handle completion + const recipe = state.recipes.find((r) => r.id === recipeId) + if (recipe) { + const xpGain = Math.floor(recipe.cookTime / 1000) * 2 + state.skill.xp += xpGain + + // Level up check + while (state.skill.xp >= state.skill.nextLevelXP) { + state.skill.xp -= state.skill.nextLevelXP + state.skill.level++ + state.skill.nextLevelXP = Math.floor(state.skill.nextLevelXP * 1.2) + showNotification(`Cooking skill level up! Now level ${state.skill.level}`, "success") + } + } +} + +function openRecipeCreator() { + state.isCreatingRecipe = true + renderRecipeCreator() +} + +function closeRecipeCreator() { + state.isCreatingRecipe = false + renderUI() +} + +function createCustomRecipe(recipeData) { + const newRecipe = { + id: `custom_${Date.now()}`, + label: recipeData.name, + description: recipeData.description, + category: recipeData.category, + cookTime: Number.parseInt(recipeData.cookTime) * 1000, + ingredients: recipeData.ingredients.map((ing) => ({ + name: ing.name, + count: Number.parseInt(ing.count), + })), + custom: true, + difficulty: Number.parseInt(recipeData.difficulty) || 1, + } + + state.customRecipes.push(newRecipe) + state.recipes.push(newRecipe) + saveData() + closeRecipeCreator() + showNotification(`Created recipe: ${newRecipe.label}`, "success") + renderUI() +} + +function renderRecipeCreator() { + const rootElement = document.getElementById("root") + + const html = ` +
+
+
+

Create Custom Recipe

+

Design your own campfire recipe

+
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ + + +
+
+ +
+ + +
+
+
+ ` + + rootElement.innerHTML = html + + // Add form submission handler + document.getElementById("recipe-form").addEventListener("submit", (e) => { + e.preventDefault() + const formData = new FormData(e.target) + + // Collect ingredients + const ingredientNames = formData.getAll("ingredient-name").filter((name) => name.trim()) + const ingredientCounts = formData.getAll("ingredient-count").filter((count) => count) + + const ingredients = ingredientNames.map((name, index) => ({ + name: name.trim(), + count: Number.parseInt(ingredientCounts[index]) || 1, + })) + + if (ingredients.length === 0) { + showNotification("Please add at least one ingredient", "error") + return + } + + const recipeData = { + name: formData.get("name"), + description: formData.get("description"), + category: formData.get("category"), + cookTime: formData.get("cookTime"), + difficulty: formData.get("difficulty"), + ingredients: ingredients, + } + + createCustomRecipe(recipeData) + }) +} + +// Helper functions for recipe creator +function addIngredientInput() { + const container = document.getElementById("ingredients-list") + const div = document.createElement("div") + div.className = "ingredient-input" + div.innerHTML = ` + + + + ` + container.appendChild(div) +} + +function removeIngredient(button) { + button.parentElement.remove() } -// Render the UI function renderUI() { const rootElement = document.getElementById("root") if (!rootElement) { @@ -77,33 +417,39 @@ function renderUI() { return } - // Filter recipes based on active tab - const filteredRecipes = state.recipes.filter((recipe) => { - if (state.activeTab === "all") return true - return recipe.category === state.activeTab - }) - - const fuelClass = state.fuelLevel < 20 ? "low" : "" + if (state.isCreatingRecipe) { + renderRecipeCreator() + return + } - // Build the HTML - let html = ` -
-