diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..90df4a5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# Python cache files +__pycache__/ +*.py[cod] +*$py.class +.pytest_cache/ +*.so + +# Distribution / packaging +dist/ +build/ +*.egg-info/ +*.egg + +# Virtual environments +venv/ +env/ +ENV/ +.env +.venv +env.bak/ +venv.bak/ + +# IDE specific files +.idea/ +.vscode/ +*.swp +*.swo +*~ +.DS_Store + +# Jupyter Notebook +.ipynb_checkpoints + +# Logs and databases +*.log +*.sqlite +*.db + +# Local development settings +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Coverage reports +htmlcov/ +.coverage +.coverage.* +coverage.xml +*.cover \ No newline at end of file diff --git a/README.md b/README.md index 2a296ca..8608a48 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ # ComfyUI_Doom Doom in ComfyUI. For Posterity. + +Main node suite: https://github.com/ryanontheinside/ComfyUI_RyanOnTheInside + +Follow me: https://linktr.ee/ryanontheinside + +Play Doom in ComfyUI: +![Doom in ComfyUI](assets/doom.gif) \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..eeb5a9f --- /dev/null +++ b/__init__.py @@ -0,0 +1,8 @@ +from .doom import Doom +WEB_DIRECTORY = "./web/js" +NODE_CLASS_MAPPINGS = { + "Doom": Doom +} +NODE_DISPLAY_NAME_MAPPINGS = { + "Doom": "Doom (RyanOnTheInside)" +} diff --git a/assets/doom.gif b/assets/doom.gif new file mode 100644 index 0000000..4842700 Binary files /dev/null and b/assets/doom.gif differ diff --git a/doom.py b/doom.py new file mode 100644 index 0000000..9cf7117 --- /dev/null +++ b/doom.py @@ -0,0 +1,49 @@ +import os +import folder_paths +from server import PromptServer +import numpy as np +import cv2 +import torch + +class Doom: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "run_doom" + CATEGORY = "DOOM" + + def run_doom(self): + img = np.zeros((512, 768, 3), dtype=np.uint8) + font = cv2.FONT_HERSHEY_SIMPLEX + + # Draw header + header = "RyanOnTheInside" + header_size = cv2.getTextSize(header, font, 2, 3)[0] + headerX = (img.shape[1] - header_size[0]) // 2 + headerY = img.shape[0] // 4 + cv2.putText(img, header, (headerX, headerY), font, 2, (255,255,255), 3) + + # Draw social links + links = [ + "@RyanOnTheInside everywhere", + "Node Suite:", + "github.com/ryanontheinside/ComfyUI_RyanOnTheInside" + ] + + y_offset = img.shape[0] // 2 + for text in links: + textsize = cv2.getTextSize(text, font, 0.7, 2)[0] + textX = (img.shape[1] - textsize[0]) // 2 + cv2.putText(img, text, (textX, y_offset), font, 0.7, (255,255,255), 2) + y_offset += 50 + + tensor = torch.from_numpy(img).float() / 255.0 + tensor = tensor.unsqueeze(0) + return (tensor,) + + diff --git a/web/DOOM-@evilution.zip b/web/DOOM-@evilution.zip new file mode 100644 index 0000000..8bf59a1 Binary files /dev/null and b/web/DOOM-@evilution.zip differ diff --git a/web/app.css b/web/app.css new file mode 100644 index 0000000..82208b1 --- /dev/null +++ b/web/app.css @@ -0,0 +1,97 @@ +.gray { + color: #AAA; +} + +.post-share { + float: right; +} + +.dosbox-default > .dosbox-container { width: 640px; height: 400px; } + +#digger > .dosbox-container > .dosbox-overlay { background: url(../../cdn/digger.png); } +#xonix > .dosbox-container > .dosbox-overlay { background: url(../../cdn/xonix.png); } +#dave > .dosbox-container > .dosbox-overlay { background: url(../../cdn/dave.png); } +#alley_cat > .dosbox-container > .dosbox-overlay { background: url(../../cdn/alley_cat.png); } + +.icon-resize-full, +.icon-resize-full:hover, +.icon-resize-full:visited { + color: black; + text-decoration: none !important; +} + +.icon-resize-full:hover { + color: #268bd2; +} + +/* FONTELLO */ + +@font-face { + font-family: 'icons'; + src: url('font/icons.eot?13187811'); + src: url('font/icons.eot?13187811#iefix') format('embedded-opentype'), + url('font/icons.woff?13187811') format('woff'), + url('font/icons.ttf?13187811') format('truetype'), + url('font/icons.svg?13187811#icons') format('svg'); + font-weight: normal; + font-style: normal; +} +/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ +/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ +/* +@media screen and (-webkit-min-device-pixel-ratio:0) { + @font-face { + font-family: 'icons'; + src: url('../font/icons.svg?13187811#icons') format('svg'); + } +} +*/ + + [class^="icon-"]:before, [class*=" icon-"]:before { + font-family: "icons"; + font-style: normal; + font-weight: normal; + speak: none; + + display: inline-block; + text-decoration: inherit; + width: 1em; + margin-right: .2em; + text-align: center; + /* opacity: .8; */ + + /* For safety - reset parent styles, that can break glyph codes*/ + font-variant: normal; + text-transform: none; + + /* fix buttons height, for twitter bootstrap */ + line-height: 1em; + + /* Animation center compensation - margins should be symmetric */ + /* remove if not needed */ + margin-left: .2em; + + /* you can be more comfortable with increased icons size */ + /* font-size: 120%; */ + + /* Font smoothing. That was taken from TWBS */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + /* Uncomment for 3D effect */ + /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ +} + +.icon-resize-full:before { content: '\e800'; } /* '' */ + +.upload-form { + display: none; + border: 1px solid gray; + margin: 0.5em; + padding: 0.5em; +} + +.upload-form label { + display: inline-block; + width: 50%; +} \ No newline at end of file diff --git a/web/js-dos-api.js b/web/js-dos-api.js new file mode 100644 index 0000000..097cf84 --- /dev/null +++ b/web/js-dos-api.js @@ -0,0 +1,347 @@ +/*! + * jQLite JavaScript Library v1.1.1 (http://code.google.com/p/jqlite/) + * Copyright (c) 2010 Brett Fattori (bfattori@gmail.com) + * Licensed under the MIT license + * http://www.opensource.org/licenses/mit-license.php + * + * Many thanks to the jQuery team's efforts. Some code is + * Copyright (c) 2010, John Resig. See + * http://jquery.org/license + * + * @author Brett Fattori (bfattori@gmail.com) + * @author $Author: bfattori $ + * @version $Revision: 145 $ + * + * Created: 03/29/2010 + * Modified: $Date: 2010-06-21 11:08:14 -0400 (Mon, 21 Jun 2010) $ + */ +(function(){function B(){return+new Date}var D=function(a,b){if(a===""&&b)return b;var d=a.split(" "),c=d.shift(),e;if(c.charAt(0)=="#"){var g=i.getElementById(c.substring(1));e=g?[g]:[]}else{e=c.charAt(0)!=="."?c.split(".")[0]:"*";var h=c.split("."),j=null;if(e.indexOf("[")!=-1){j=e;e=e.substr(0,e.indexOf("["))}g=function(o){var n=arguments.callee,k;if(!(k=!n.needClass)){k=n.classes;if(o.className.length==0)k=false;else{for(var r=o.className.split(" "),l=k.length,p=0;p0;g.needAttribute=j!=null;for(c=0;c0;)A.shift()()};var t="jQuery"+B(),S=0,O={};f.noData={embed:true,object:true,applet:true};f.cache={};f.data=function(a,b,d){if(!(a.nodeName&&jQuery.noData[a.nodeName.toLowerCase()])){a=a==window?O:a;var c=a[t];c||(c=a[t]=++S);if(b&&!jQuery.cache[c])jQuery.cache[c]={};if(d!==undefined)jQuery.cache[c][b]=d;return b?jQuery.cache[c][b]:c}};f.removeData= +function(a,b){a=a==window?O:a;var d=a[t];if(b){if(jQuery.cache[d]){delete jQuery.cache[d][b];b="";for(b in jQuery.cache[d])break;b||jQuery.removeData(a)}}else{try{delete a[t]}catch(c){a.removeAttribute&&a.removeAttribute(t)}delete jQuery.cache[d]}};f.ajax={status:-1,statusText:"",responseText:null,responseXML:null,send:function(a,b,d){if(f.isFunction(b)){d=b;b={}}if(a){var c=true,e=null,g=null;if(typeof b.async!=="undefined"){c=b.async;delete b.async}if(typeof b.username!=="undefined"){e=b.username; +delete b.username}if(typeof b.password!=="undefined"){g=b.password;delete b.password}b=f.param(b);if(b.length!=0)a+=(a.indexOf("?")==-1?"?":"&")+b;b=new XMLHttpRequest;b.open("GET",a,c,e,g);b.send();if(c){a=function(h){var j=arguments.callee;h.status==200?f.ajax.complete(h,j.cb):f.ajax.error(h,j.cb)};a.cb=d;d=function(){var h=arguments.callee;h.req.readyState!=4?setTimeout(h,250):h.xcb(h.req)};d.req=b;d.xcb=a;setTimeout(d,250)}}},complete:function(a,b){f.ajax.status=a.status;f.ajax.responseText=a.responseText; +f.ajax.responseXML=a.responseXML;f.isFunction(b)&&b(a.responseText,a.status)},error:function(a,b){f.ajax.status=a.status;f.ajax.statusText=a.statusText;f.isFunction(b)&&b(a.status,a.statusText)}};f.makeArray=function(a,b){var d=b||[];if(a!=null)a.length==null||typeof a==="string"||jQuery.isFunction(a)||typeof a!=="function"&&a.setInterval?L.call(d,a):f.merge(d,a);return d};f.inArray=function(a,b){for(var d=0;d")!=-1){d=f.trim(a).toLowerCase();d=d.indexOf("'); + this.canvas = $(''); + this.overlay = $('
'); + this.loaderMessage = $('
'); + this.loader = $('
').append($('
').append($(''))).append(this.loaderMessage); + this.start = $('
Click to start'); + this.div.append(this.wrapper); + this.wrapper.append(this.canvas); + this.wrapper.append(this.loader); + this.wrapper.append(this.overlay); + this.overlay.append(this.start); + } + + UI.prototype.onStart = function(fun) { + return this.start.click((function(_this) { + return function() { + fun(); + return _this.overlay.hide(); + }; + })(this)); + }; + + UI.prototype.appendCss = function() { + var head, style; + head = document.head || document.getElementsByTagName('head')[0]; + style = document.createElement('style'); + style.type = 'text/css'; + if (style.styleSheet) { + style.styleSheet.cssText = this.css; + } else { + style.appendChild(document.createTextNode(this.css)); + } + return head.appendChild(style); + }; + + UI.prototype.showLoader = function() { + this.loader.show(); + return this.loaderMessage.html(''); + }; + + UI.prototype.updateMessage = function(message) { + return this.loaderMessage.html(message); + }; + + UI.prototype.hideLoader = function() { + return this.loader.hide(); + }; + + UI.prototype.css = '.dosbox-container { position: relative; min-width: 320px; min-height: 200px; } .dosbox-canvas { } .dosbox-overlay, .dosbox-loader { position: absolute; left: 0; right: 0; top: 0; bottom: 0; background-color: #333; } .dosbox-start { text-align: center; position: absolute; left: 0; right: 0; bottom: 50%; color: #f80; font-size: 1.5em; text-decoration: underline; cursor: pointer; } .dosbox-overlay a { color: #f80; } .dosbox-loader { display: none; } .dosbox-powered { position: absolute; right: 1em; bottom: 1em; font-size: 0.8em; color: #9C9C9C; } .dosbox-loader-message { text-align: center; position: absolute; left: 0; right: 0; bottom: 50%; margin: 0 0 -3em 0; box-sizing: border-box; color: #f80; font-size: 1.5em; } @-moz-keyframes loading { 0% { left: 0; } 50% { left: 8.33333em; } 100% { left: 0; } } @-webkit-keyframes loading { 0% { left: 0; } 50% { left: 8.33333em; } 100% { left: 0; } } @keyframes loading { 0% { left: 0; } 50% { left: 8.33333em; } 100% { left: 0; } } .st-loader { width: 10em; height: 2.5em; position: absolute; top: 50%; left: 50%; margin: -1.25em 0 0 -5em; box-sizing: border-box; } .st-loader:before, .st-loader:after { content: ""; display: block; position: absolute; top: 0; bottom: 0; width: 1.25em; box-sizing: border-box; border: 0.25em solid #f80; } .st-loader:before { left: -0.76923em; border-right: 0; } .st-loader:after { right: -0.76923em; border-left: 0; } .st-loader .equal { display: block; position: absolute; top: 50%; margin-top: -0.5em; left: 4.16667em; height: 1em; width: 1.66667em; border: 0.25em solid #f80; box-sizing: border-box; border-width: 0.25em 0; -moz-animation: loading 1.5s infinite ease-in-out; -webkit-animation: loading 1.5s infinite ease-in-out; animation: loading 1.5s infinite ease-in-out; }'; + + return UI; + + })(); + +}).call(this); + +(function() { + Dosbox.Xhr = (function() { + function Xhr(url, options) { + var e; + this.success = options.success; + this.progress = options.progress; + if (window.ActiveXObject) { + try { + this.xhr = new ActiveXObject('Microsoft.XMLHTTP'); + } catch (error) { + e = error; + this.xhr = null; + } + } else { + this.xhr = new XMLHttpRequest(); + } + this.xhr.open('GET', url, true); + this.xhr.overrideMimeType('text/plain; charset=x-user-defined'); + this.xhr.addEventListener('progress', (function(_this) { + return function(evt) { + if (_this.progress) { + return _this.progress(evt.total, evt.loaded); + } + }; + })(this)); + this.xhr.onreadystatechange = (function(_this) { + return function() { + return _this._onReadyStateChange(); + }; + })(this); + this.xhr.send(); + } + + Xhr.prototype._onReadyStateChange = function() { + if (this.xhr.readyState === 4 && this.success) { + return this.success(this.xhr.responseText); + } + }; + + return Xhr; + + })(); + +}).call(this); diff --git a/web/js/doom_widget.js b/web/js/doom_widget.js new file mode 100644 index 0000000..198b3fa --- /dev/null +++ b/web/js/doom_widget.js @@ -0,0 +1,152 @@ +import { app } from '../../../scripts/app.js' + +class DoomWidget { + constructor(node) { + this.node = node; + this.element = document.createElement('div'); + this.element.style.backgroundColor = "#121212"; + this.element.style.color = "white"; + + // Create attribution link + this.attributionLink = document.createElement("p"); + this.attributionLink.innerHTML = `Follow RyanOnTheInside For Even More Useful Stuff 🎮`; + this.attributionLink.style.textAlign = "center"; + this.attributionLink.style.marginBottom = "10px"; + + // Create container for DOOM + this.doomContainer = document.createElement("div"); + this.doomContainer.id = "DOOM"; + this.doomContainer.className = "dosbox-default"; + this.doomContainer.style.marginTop = "20px"; + + // Add fullscreen button + this.fullscreenBtn = document.createElement("p"); + this.fullscreenBtn.innerHTML = `
  FULLSCREEN`; + + // Add styles + const style = document.createElement('style'); + style.textContent = ` + #DOOM > .dosbox-container { width: 640px; height: 400px; } + #DOOM > .dosbox-container > .dosbox-overlay { background: url("https://thedoggybrad.github.io/doom_on_js-dos/DOOM.png"); } + #DOOM > .dosbox-container > .dosbox-overlay > .dosbox-start { + border-radius: 17px; + background-color: rgba(90, 90, 90, 0.57); + padding: 5px; + text-align: center; + width: 10em; + margin: 0 auto; + } + a:link {color:white} + `; + + // Append elements + document.head.appendChild(style); + this.element.appendChild(this.attributionLink); + this.element.appendChild(this.doomContainer); + this.element.appendChild(this.fullscreenBtn); + + // Load required scripts + this.loadScripts().then(() => { + this.initDoom(); + }).catch(error => { + console.error("DoomWidget: Failed to load scripts:", error); + }); + } + + async loadScripts() { + try { + await this.loadStylesheet("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"); + await this.loadScript("https://code.jquery.com/jquery-3.7.1.min.js"); + await this.loadScript("https://thedoggybrad.github.io/doom_on_js-dos/js-dos-api.js"); + } catch (error) { + console.error("DoomWidget: Error in loadScripts:", error); + throw error; + } + } + + loadScript(src) { + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.src = src; + script.onload = () => { + resolve(); + }; + script.onerror = (error) => { + console.error("DoomWidget: Script failed to load:", src, error); + reject(error); + }; + document.head.appendChild(script); + }); + } + + loadStylesheet(href) { + return new Promise((resolve, reject) => { + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = href; + link.onload = () => { + resolve(); + }; + link.onerror = (error) => { + console.error("DoomWidget: Stylesheet failed to load:", href, error); + reject(error); + }; + document.head.appendChild(link); + }); + } + + initDoom() { + try { + if (typeof Dosbox === 'undefined') { + console.error("DoomWidget: Dosbox is not defined! js-dos API might not be loaded correctly"); + return; + } + + const dosbox = new Dosbox({ + id: "DOOM", + onload: (dosbox) => { + dosbox.run("https://thedoggybrad.github.io/doom_on_js-dos/DOOM-@evilution.zip", "./DOOM/DOOM.EXE"); + }, + onrun: (dosbox, app) => { + } + }); + + const fullscreenBtn = document.getElementById("fullscreen_DOOM"); + if (fullscreenBtn) { + fullscreenBtn.addEventListener("click", () => { + dosbox.requestFullScreen(); + }); + } else { + console.error("DoomWidget: Fullscreen button not found in DOM"); + } + } catch (error) { + console.error("DoomWidget: Error in initDoom:", error); + } + } +} + +app.registerExtension({ + name: 'Comfy.Doom', + async beforeRegisterNodeDef(nodeType, nodeData) { + + if (nodeData.name === 'Doom') { + const onNodeCreated = nodeType.prototype.onNodeCreated; + + nodeType.prototype.onNodeCreated = function() { + const r = onNodeCreated?.apply(this, arguments); + + // Create and add the DOOM widget + const doomWidget = new DoomWidget(this); + this.doom = this.addDOMWidget("doom", "DoomWidget", doomWidget.element, { + serialize: false, + hideOnZoom: false + }); + + // Set appropriate node size + this.setSize([660, 508]); + + return r; + } + } + } +}); \ No newline at end of file