diff --git a/extensions/Eaielectronic/Finger-mutli-mobile.js b/extensions/Eaielectronic/Finger-mutli-mobile.js new file mode 100644 index 0000000000..311a103acb --- /dev/null +++ b/extensions/Eaielectronic/Finger-mutli-mobile.js @@ -0,0 +1,172 @@ +// Name: Finger ios/android +// ID: multiTouchDetection +// Description: Detect multiple fingers on mobile devices. Unlike sensing+ works on all mobile devices. +// By: Eaielectronic +// By: SERPENT1867 { + const canvas = Scratch.renderer.canvas; // Récupère le canvas de Scratch + const rect = canvas.getBoundingClientRect(); // Récupère la position du canvas + const scratchX = ((touch.clientX - rect.left) / rect.width) * 480 - 240; + const scratchY = 180 - ((touch.clientY - rect.top) / rect.height) * 360; + return { x: scratchX, y: scratchY }; + }; + + const touchHandler = (event) => { + // Met à jour les coordonnées des doigts dans le tableau + for (let i = 0; i < event.touches.length && i < 10; i++) { + const touch = event.touches[i]; + const { x, y } = convertToScratchCoords(touch); + this.touches[i] = { x, y }; + } + + // Réinitialise les positions restantes si moins de doigts + for (let i = event.touches.length; i < 10; i++) { + this.touches[i] = { x: 0, y: 0 }; + } + }; + + window.addEventListener("touchstart", touchHandler); + window.addEventListener("touchmove", touchHandler); + window.addEventListener("touchend", touchHandler); + } + + // Obtenir la position X du toucher d'un doigt donné + getTouchX(args) { + const index = Math.max(0, Math.min(9, args.INDEX - 1)); // Limite à 10 doigts + return this.touches[index].x; + } + + // Obtenir la position Y du toucher d'un doigt donné + getTouchY(args) { + const index = Math.max(0, Math.min(9, args.INDEX - 1)); // Limite à 10 doigts + return this.touches[index].y; + } + + // Nouveau: Obtenir la position X d'un doigt touchant un sprite + getTouchXOnSprite(args, util) { + const spriteBounds = util.target.getBounds(); + + // Cherche le premier doigt touchant le sprite et renvoie sa position X + for (let i = 0; i < this.touches.length; i++) { + const touch = this.touches[i]; + if ( + spriteBounds.left <= touch.x && + touch.x <= spriteBounds.right && + spriteBounds.bottom <= touch.y && + touch.y <= spriteBounds.top + ) { + return touch.x; + } + } + return 0; // Retourne 0 si aucun doigt ne touche le sprite + } + + // Nouveau: Obtenir la position Y d'un doigt touchant un sprite + getTouchYOnSprite(args, util) { + const spriteBounds = util.target.getBounds(); + + // Cherche le premier doigt touchant le sprite et renvoie sa position Y + for (let i = 0; i < this.touches.length; i++) { + const touch = this.touches[i]; + if ( + spriteBounds.left <= touch.x && + touch.x <= spriteBounds.right && + spriteBounds.bottom <= touch.y && + touch.y <= spriteBounds.top + ) { + return touch.y; + } + } + return 0; // Retourne 0 si aucun doigt ne touche le sprite + } + } + + Scratch.extensions.register(new MultiTouchDetection()); +})(Scratch); diff --git a/extensions/Eaielectronic/Fps-returne.js b/extensions/Eaielectronic/Fps-returne.js new file mode 100644 index 0000000000..2d7ce09a71 --- /dev/null +++ b/extensions/Eaielectronic/Fps-returne.js @@ -0,0 +1,77 @@ +// Name: FPS returne +// ID: fpsbasedreturn +// Description: allows you to create games that run at the same speed, even if the FPS changes. +// By: Eaielectronic +// By: SERPENT1867 +// License: MPL-2.0 +(function (Scratch) { + "use strict"; + class FPSBasedReturn { + constructor() { + this.previousTime = null; // Pour stocker l'heure de la dernière frame + this.fpsValue = 30; // Initialiser avec une valeur par défaut (30 FPS) + } + + getInfo() { + return { + id: "fpsbasedreturn", + name: "FPS Based Return", + blocks: [ + { + opcode: "getFPSMultiplier", + blockType: Scratch.BlockType.REPORTER, + text: "FPS multiplier (based on [REFERENCE] FPS", + arguments: { + REFERENCE: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 30, + }, + }, + }, + { + opcode: "getCurrentFPS", + blockType: Scratch.BlockType.REPORTER, + text: "current FPS", + }, + ], + }; + } + + // Méthode pour obtenir le delta time et calculer une fois les FPS + calculateFPS() { + const currentTime = performance.now(); + + if (this.previousTime === null) { + this.previousTime = currentTime; + return; // Attendre jusqu'à la prochaine frame pour calculer le delta time + } + + let deltaTime = (currentTime - this.previousTime) / 1000; // Convertir en secondes + this.previousTime = currentTime; + + if (deltaTime > 1 / 30) { + deltaTime = 1 / 30; // Limiter à 30 FPS max pour éviter des pics + } + + this.fpsValue = 1 / deltaTime; // Calculer les FPS actuels + } + + // Méthode pour retourner le FPS actuel + getCurrentFPS() { + this.calculateFPS(); // Mettre à jour le FPS + return this.fpsValue; // Retourner la valeur des FPS actuels + } + + // Méthode pour calculer le multiplicateur en fonction des FPS + getFPSMultiplier(args) { + const referenceFPS = args.REFERENCE; // FPS de référence choisi par l'utilisateur + const fps = this.getCurrentFPS(); // Obtenir les FPS actuels + + const multiplier = referenceFPS / fps; // Le rapport par rapport au FPS de référence + return multiplier; + } + } + + // Enregistrer l'extension dans TurboWarp + Scratch.extensions.register(new FPSBasedReturn()); +})(Scratch); diff --git a/extensions/Eaielectronic/Login-page.js b/extensions/Eaielectronic/Login-page.js new file mode 100644 index 0000000000..0caa5663f3 --- /dev/null +++ b/extensions/Eaielectronic/Login-page.js @@ -0,0 +1,271 @@ +// Name: Login +// ID: usermanagement +// Description: Allows you to create login forms. +// By: Eaielectronic +// By: SERPENT1867 +// License: MPL-2.0 +(function (Scratch) { + "use strict"; + class UserManagement { + constructor() { + this.users = []; + this.username = ""; + this.password = ""; + this.email = ""; + this.turbowarp = ""; // Variable for the form state + } + + getInfo() { + return { + id: "usermanagement", + name: "User Management", + color1: "#ff3f00", // Slightly darker blue + blocks: [ + { + opcode: "openForm", + blockType: Scratch.BlockType.COMMAND, + text: "open form for [ACTION]", + arguments: { + ACTION: { + type: Scratch.ArgumentType.STRING, + menu: "actionsMenu", + defaultValue: "login", + }, + }, + }, + { + opcode: "hideForm", + blockType: Scratch.BlockType.COMMAND, + text: "hide form", + }, + { + opcode: "getUsername", + blockType: Scratch.BlockType.REPORTER, + text: "username", + }, + { + opcode: "getPassword", + blockType: Scratch.BlockType.REPORTER, + text: "password", + }, + { + opcode: "getEmail", + blockType: Scratch.BlockType.REPORTER, + text: "email", + }, + { + opcode: "getFormState", + blockType: Scratch.BlockType.REPORTER, + text: "form state", + }, + { + opcode: "clear", + blockType: Scratch.BlockType.COMMAND, + text: "clear values", + }, + ], + menus: { + actionsMenu: { + items: ["login", "create account", "recover password"], + }, + }, + }; + } + + openForm(args) { + const { ACTION } = args; + this.turbowarp = ACTION; // Update the form state + this.hideForm(false); // Hide the previous form if open + + const form = document.createElement("form"); + form.style.position = "absolute"; + form.style.top = "50%"; + form.style.left = "50%"; + form.style.transform = "translate(-50%, -50%)"; + form.style.padding = "20px"; + form.style.backgroundColor = "#f9f9f9"; + form.style.boxShadow = "0 0 10px rgba(0, 0, 0, 0.1)"; + form.style.borderRadius = "8px"; + form.style.width = "300px"; + form.style.fontFamily = "Arial, sans-serif"; + + const closeButton = document.createElement("span"); + closeButton.textContent = "✖"; + closeButton.style.position = "absolute"; + closeButton.style.top = "10px"; + closeButton.style.right = "10px"; + closeButton.style.cursor = "pointer"; + closeButton.style.color = "#000"; + closeButton.addEventListener("click", () => { + this.hideForm(); + }); + form.appendChild(closeButton); + + const title = document.createElement("h2"); + title.textContent = ACTION.charAt(0).toUpperCase() + ACTION.slice(1); + title.style.textAlign = "center"; + title.style.color = "#333"; + form.appendChild(title); + + if (ACTION === "login" || ACTION === "create account") { + const usernameInput = document.createElement("input"); + usernameInput.type = "text"; + usernameInput.name = "username"; + usernameInput.placeholder = "Username"; + usernameInput.style.width = "90%"; + usernameInput.style.padding = "10px"; + usernameInput.style.margin = "10px 0"; + usernameInput.style.border = "1px solid #ccc"; + usernameInput.style.borderRadius = "4px"; + form.appendChild(usernameInput); + + const passwordInput = document.createElement("input"); + passwordInput.type = "password"; + passwordInput.name = "password"; + passwordInput.placeholder = "Password"; + passwordInput.style.width = "90%"; + passwordInput.style.padding = "10px"; + passwordInput.style.margin = "10px 0"; + passwordInput.style.border = "1px solid #ccc"; + passwordInput.style.borderRadius = "4px"; + form.appendChild(passwordInput); + } + + if (ACTION === "create account" || ACTION === "recover password") { + const emailInput = document.createElement("input"); + emailInput.type = "email"; + emailInput.name = "email"; + emailInput.placeholder = "Email"; + emailInput.style.width = "95%"; + emailInput.style.padding = "10px"; + emailInput.style.margin = "10px 0"; + emailInput.style.border = "1px solid #ccc"; + emailInput.style.borderRadius = "4px"; + form.appendChild(emailInput); + } + + const submitButton = document.createElement("button"); + submitButton.type = "submit"; + submitButton.textContent = "Send"; + submitButton.style.width = "100%"; + submitButton.style.padding = "10px"; + submitButton.style.margin = "10px 0"; + submitButton.style.backgroundColor = "#4CAF50"; + submitButton.style.color = "#fff"; + submitButton.style.border = "none"; + submitButton.style.borderRadius = "4px"; + submitButton.style.cursor = "pointer"; + form.appendChild(submitButton); + + const resetPasswordLink = document.createElement("a"); + resetPasswordLink.textContent = "Reset password"; + resetPasswordLink.style.display = "block"; + resetPasswordLink.style.textAlign = "center"; + resetPasswordLink.style.marginTop = "10px"; + resetPasswordLink.style.color = "#007BFF"; + resetPasswordLink.style.cursor = "pointer"; + resetPasswordLink.addEventListener("click", () => { + this.hideForm(false); + this.openForm({ ACTION: "recover password" }); + }); + form.appendChild(resetPasswordLink); + + const createAccountLink = document.createElement("a"); + createAccountLink.textContent = "Account creation"; + createAccountLink.style.display = "block"; + createAccountLink.style.textAlign = "center"; + createAccountLink.style.marginTop = "10px"; + createAccountLink.style.color = "#007BFF"; + createAccountLink.style.cursor = "pointer"; + createAccountLink.addEventListener("click", () => { + this.hideForm(false); + this.openForm({ ACTION: "create account" }); + }); + form.appendChild(createAccountLink); + + document.body.appendChild(form); + + form.addEventListener("submit", (event) => { + event.preventDefault(); + const formData = new FormData(form); + this.username = formData.get("username"); + this.password = formData.get("password"); + this.email = formData.get("email"); + + if (ACTION === "login") { + this.login({ USERNAME: this.username, PASSWORD: this.password }); + } else if (ACTION === "create account") { + this.createAccount({ + USERNAME: this.username, + PASSWORD: this.password, + EMAIL: this.email, + }); + } else if (ACTION === "recover password") { + this.recoverPassword({ EMAIL: this.email }); + } + + this.hideForm(false); + }); + } + + hideForm() { + const form = document.querySelector("form"); + if (form) { + document.body.removeChild(form); + this.turbowarp = ""; // Reset the form state + } + } + + createAccount(args) { + const { USERNAME, PASSWORD, EMAIL } = args; + const user = { username: USERNAME, password: PASSWORD, email: EMAIL }; + this.users.push(user); + console.log(`Account created for ${USERNAME}`); + } + + login(args) { + const { USERNAME, PASSWORD } = args; + const user = this.users.find( + (u) => u.username === USERNAME && u.password === PASSWORD + ); + console.log( + user + ? `Login successful for ${USERNAME}` + : "Incorrect username or password." + ); + } + + recoverPassword(args) { + const { EMAIL } = args; + const user = this.users.find((u) => u.email === EMAIL); + console.log( + user ? `Your password is: ${user.password}` : "Email not found." + ); + } + + getUsername() { + return this.username; + } + + getPassword() { + return this.password; + } + + getEmail() { + return this.email; + } + + getFormState() { + return this.turbowarp; + } + + clear() { + this.username = ""; + this.password = ""; + this.email = ""; + console.log("Values have been cleared."); + } + } + + Scratch.extensions.register(new UserManagement()); +})(Scratch); diff --git a/extensions/Eaielectronic/ScrollingTextBubble.js b/extensions/Eaielectronic/ScrollingTextBubble.js new file mode 100644 index 0000000000..2442876a6b --- /dev/null +++ b/extensions/Eaielectronic/ScrollingTextBubble.js @@ -0,0 +1,276 @@ +// Name: Scrolling bull +// ID: scrollingTextBubble +// Description: Create scrolling text bubbles and apply styles with Html tags. +// By: Eaielectronic +// By: SERPENT1867 +// License: MPL-2.0 +(function (Scratch) { + "use strict"; + + class ScrollingTextBubble { + constructor() { + this.bubbles = {}; + this.defaultBubbleStyle = { + backgroundColor: "rgba(255, 255, 255, 0.8)", + borderColor: "black", + borderWidth: "2px", + borderRadius: "10px", + boxShadow: "0 4px 8px rgba(0, 0, 0, 0.2)", + padding: "15px", // Valeur par défaut du padding + }; + } + + getInfo() { + return { + id: "scrollingTextBubble", + name: "Scrolling Text Bubble", + blocks: [ + { + opcode: "showTextBubble", + blockType: Scratch.BlockType.COMMAND, + text: "show text bubble with text [TEXT] next to sprite with speed [SPEED], font [FONT], width [WIDTH], offsetX [OFFSETX], offsetY [OFFSETY]", + arguments: { + TEXT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Hello, World!", + }, + SPEED: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 50, + }, + FONT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Arial", + }, + WIDTH: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 200, + }, + OFFSETX: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 0, + }, + OFFSETY: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: -60, + }, + }, + filter: [Scratch.TargetType.SPRITE], + }, + { + opcode: "hideTextBubble", + blockType: Scratch.BlockType.COMMAND, + text: "hide text bubble", + filter: [Scratch.TargetType.SPRITE], + }, + { + opcode: "hideAllTextBubbles", + blockType: Scratch.BlockType.COMMAND, + text: "hide all text bubbles", + }, + { + opcode: "setBubbleColor", + blockType: Scratch.BlockType.COMMAND, + text: "set bubble color to [COLOR]", + arguments: { + COLOR: { + type: Scratch.ArgumentType.COLOR, + defaultValue: "#ffffff", + }, + }, + }, + { + opcode: "setBubbleStyle", + blockType: Scratch.BlockType.COMMAND, + text: "set bubble style to [STYLE]", + arguments: { + STYLE: { + type: Scratch.ArgumentType.STRING, + menu: "bubbleStyleMenu", + defaultValue: "default", + }, + }, + }, + { + opcode: "setBubblePadding", + blockType: Scratch.BlockType.COMMAND, + text: "set bubble padding to [PADDING]", + arguments: { + PADDING: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 15, // Valeur par défaut pour le padding + }, + }, + }, + ], + menus: { + bubbleStyleMenu: { + acceptReporters: true, + items: ["default", "rounded", "sharp", "shadow", "no-border"], + }, + }, + }; + } + + showTextBubble(args, util) { + const sprite = util.target; + const text = args.TEXT; + const speed = args.SPEED; + const font = args.FONT; + const width = args.WIDTH; + const offsetX = args.OFFSETX; + const offsetY = args.OFFSETY; + + if (this.bubbles[sprite.id]) { + clearInterval(this.bubbles[sprite.id].intervalId); + this.bubbles[sprite.id].bubbleDiv.remove(); + } + + const bubbleDiv = document.createElement("div"); + bubbleDiv.style.position = "absolute"; + this.applyBubbleStyle(bubbleDiv); + bubbleDiv.style.maxWidth = `${width}px`; + bubbleDiv.style.overflow = "hidden"; + bubbleDiv.style.whiteSpace = "pre-wrap"; + bubbleDiv.style.fontFamily = font; + bubbleDiv.style.color = "black"; + bubbleDiv.style.fontSize = "16px"; + bubbleDiv.style.lineHeight = "1.5"; + + const canvas = Scratch.renderer.canvas; + const rect = canvas.getBoundingClientRect(); + + const textContainer = document.createElement("span"); + bubbleDiv.appendChild(textContainer); + document.body.appendChild(bubbleDiv); + + const updateBubblePosition = () => { + const { x, y } = sprite; + + const proportionX = rect.width / 480; + const proportionY = rect.height / 360; + + const adjustedOffsetX = offsetX * proportionX; + const adjustedOffsetY = offsetY * proportionY; + + bubbleDiv.style.left = `${rect.left + ((x + 240) / 480) * rect.width + adjustedOffsetX}px`; + bubbleDiv.style.top = `${rect.top + ((180 - y) / 360) * rect.height + adjustedOffsetY}px`; + }; + + updateBubblePosition(); + window.addEventListener("mousemove", updateBubblePosition); + + const formattedText = this.formatText(text); + const textParts = this.splitText(formattedText); + + let index = 0; + let currentHTML = ""; + + const intervalId = setInterval(() => { + if (index < textParts.length) { + currentHTML += textParts[index]; + textContainer.innerHTML = currentHTML; + index++; + } else { + clearInterval(intervalId); + } + }, speed); + + this.bubbles[sprite.id] = { intervalId, bubbleDiv }; + } + + hideTextBubble(args, util) { + const sprite = util.target; + + if (this.bubbles[sprite.id]) { + clearInterval(this.bubbles[sprite.id].intervalId); + this.bubbles[sprite.id].bubbleDiv.remove(); + delete this.bubbles[sprite.id]; + } + } + + hideAllTextBubbles() { + for (const spriteId in this.bubbles) { + if (Object.prototype.hasOwnProperty.call(this.bubbles, spriteId)) { + clearInterval(this.bubbles[spriteId].intervalId); + this.bubbles[spriteId].bubbleDiv.remove(); + delete this.bubbles[spriteId]; + } + } + } + + setBubbleColor(args) { + const color = args.COLOR; + this.defaultBubbleStyle.backgroundColor = color; + } + + setBubblePadding(args) { + const padding = args.PADDING; + this.defaultBubbleStyle.padding = `${padding}px`; + } + + setBubbleStyle(args) { + const style = args.STYLE; + + switch (style) { + case "rounded": + this.defaultBubbleStyle.borderRadius = "20px"; + break; + case "sharp": + this.defaultBubbleStyle.borderRadius = "0px"; + break; + case "shadow": + this.defaultBubbleStyle.boxShadow = "0 8px 16px rgba(0, 0, 0, 0.5)"; + break; + case "no-border": + this.defaultBubbleStyle.borderWidth = "0px"; + break; + default: + this.defaultBubbleStyle = { + backgroundColor: "rgba(255, 255, 255, 0.8)", + borderColor: "black", + borderWidth: "2px", + borderRadius: "10px", + boxShadow: "0 4px 8px rgba(0, 0, 0, 0.2)", + padding: "15px", + }; + break; + } + } + + applyBubbleStyle(bubbleDiv) { + bubbleDiv.style.backgroundColor = this.defaultBubbleStyle.backgroundColor; + bubbleDiv.style.border = `${this.defaultBubbleStyle.borderWidth} solid ${this.defaultBubbleStyle.borderColor}`; + bubbleDiv.style.borderRadius = this.defaultBubbleStyle.borderRadius; + bubbleDiv.style.boxShadow = this.defaultBubbleStyle.boxShadow; + bubbleDiv.style.padding = this.defaultBubbleStyle.padding; + } + + formatText(text) { + // Retourne le texte brut, seules les balises HTML sont interprétées + return text; + } + + splitText(text) { + const parts = []; + const tagRegex = /<\/?[^>]+>/g; + let lastIndex = 0; + + text.replace(tagRegex, (match, index) => { + if (index > lastIndex) { + parts.push(...text.slice(lastIndex, index).split("")); + } + parts.push(match); + lastIndex = index + match.length; + }); + + if (lastIndex < text.length) { + parts.push(...text.slice(lastIndex).split("")); + } + + return parts; + } + } + + Scratch.extensions.register(new ScrollingTextBubble()); +})(Scratch); diff --git a/images/Eaielectronic/Finger-mutli-mobile.svg b/images/Eaielectronic/Finger-mutli-mobile.svg new file mode 100644 index 0000000000..b2cdbbc5fc --- /dev/null +++ b/images/Eaielectronic/Finger-mutli-mobile.svg @@ -0,0 +1 @@ + diff --git a/images/Eaielectronic/Login-page 2.svg b/images/Eaielectronic/Login-page 2.svg new file mode 100644 index 0000000000..1200b23acb --- /dev/null +++ b/images/Eaielectronic/Login-page 2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/Eaielectronic/ScrollingTextBubble.svg b/images/Eaielectronic/ScrollingTextBubble.svg new file mode 100644 index 0000000000..3d1777a899 --- /dev/null +++ b/images/Eaielectronic/ScrollingTextBubble.svg @@ -0,0 +1 @@ + \ No newline at end of file