diff --git a/mods/CsC.js b/mods/CsC.js new file mode 100644 index 00000000..4b1a36f9 --- /dev/null +++ b/mods/CsC.js @@ -0,0 +1,138 @@ +runAfterLoad(function(){ + const patternCache = {}; + let currentPatternID = null; + let nextPatternID = 1; + const undoStack = []; + const redoStack = []; + const maxHistory = 50; + + function saveAction(pixels){ + undoStack.push(pixels.map(px=>({x:px.x,y:px.y,element:px.element,color:px.color,patternID:px.patternID}))); + if(undoStack.length>maxHistory) undoStack.shift(); + redoStack.length=0; + } + + function undo(){ + if(!undoStack.length) return; + const action = undoStack.pop(); + const redoPixels = []; + action.forEach(a=>{ + const px = pixelMap[a.x][a.y]; + redoPixels.push({x:a.x,y:a.y,element:px.element,color:px.color,patternID:px.patternID}); + px.element=a.element; px.color=a.color; px.patternID=a.patternID; + }); + redoStack.push(redoPixels); + } + + function redo(){ + if(!redoStack.length) return; + const action = redoStack.pop(); + const undoPixels = []; + action.forEach(a=>{ + const px = pixelMap[a.x][a.y]; + undoPixels.push({x:a.x,y:a.y,element:px.element,color:px.color,patternID:px.patternID}); + px.element=a.element; px.color=a.color; px.patternID=a.patternID; + }); + undoStack.push(undoPixels); + } + + elements.undo_tool = { + color:"#f00", tool:undo, category:"tools", description:"Undo last action" + }; + elements.redo_tool = { + color:"#0f0", tool:redo, category:"tools", description:"Redo last undone action" + }; + + elements.pattern_painter = { + color:"#888", tool:function(pixel,x,y){ + if(!currentPatternID){ + currentPatternID="pattern_"+(nextPatternID++); + patternCache[currentPatternID]=Array(8).fill().map(()=>Array(8).fill(0)); + } + const pat = patternCache[currentPatternID]; + const mx=Math.floor((x*8)%8), my=Math.floor((y*8)%8); + pat[my][mx]=1-pat[my][mx]; + }, category:"tools", description:"Pattern painter (8x8)" + }; + + function drawPattern(ctx, pat, dx, dy, s){ + for(let py=0;py<8;py++)for(let px=0;px<8;px++){ + if(pat[py][px]){ + ctx.fillStyle="#444"; + ctx.fillRect(dx+px*s/8, dy+py*s/8, s/8, s/8); + } + } + } + + elements.decor_block={ + color:"#fff", behavior:behaviors.WALL, category:"solids", + onPlace:p=>{if(currentPatternID)p.patternID=currentPatternID; saveAction([p]);}, + renderer:function(p,ctx,dx,dy,s){ + if(p.patternID) drawPattern(ctx, patternCache[p.patternID], dx, dy, s); + else { ctx.fillStyle=p.color; ctx.fillRect(dx,dy,s,s); } + } + }; + + elements.batch_filler={ + color:"#66f", + tool:function(pixel){ + const area=[]; + for(let dx=-2;dx<=2;dx++)for(let dy=-2;dy<=2;dy++){ + const px=pixelMap[pixel.x+dx]?.[pixel.y+dy]; + if(px){px.element="decor_block"; if(currentPatternID) px.patternID=currentPatternID; area.push(px);} + } + saveAction(area); + }, + category:"tools", + description:"Fill 5x5 area with pattern" + }; + + elements.stamp_tool={ + color:"#ff0", + tool:function(pixel){ + if(!currentPatternID) return; + const area=[]; + for(let dx=0;dx<8;dx++)for(let dy=0;dy<8;dy++){ + const px=pixelMap[pixel.x+dx]?.[pixel.y+dy]; + if(px){px.element="decor_block"; px.patternID=currentPatternID; area.push(px);} + } + saveAction(area); + }, + category:"tools", + description:"Stamp 8x8 pattern" + }; + + elements.gradient_tool={ + color:"#0ff", + tool:function(pixel){ + const area=[]; + for(let dx=0;dx<5;dx++)for(let dy=0;dy<5;dy++){ + const px=pixelMap[pixel.x+dx]?.[pixel.y+dy]; + if(px){ + const r=Math.floor(255*dx/4), b=Math.floor(255*dy/4); + px.element="decor_block"; + px.color=`rgb(${r},0,${b})`; + area.push(px); + } + } + saveAction(area); + }, + category:"tools", + description:"Paint 5x5 gradient" + }; + + elements.curve_tool={ + color:"#f0f", + tool:function(pixel){ + const area=[]; + for(let i=0;i<5;i++){ + const px=pixelMap[pixel.x+i]?.[pixel.y+Math.floor(Math.sin(i/4*Math.PI)*4)]; + if(px){px.element="decor_block"; if(currentPatternID) px.patternID=currentPatternID; area.push(px);} + } + saveAction(area); + }, + category:"tools", + description:"Draw sine curve with pattern" + }; + +}); diff --git a/mods/Decor.js b/mods/Decor.js new file mode 100644 index 00000000..cf15ecaf --- /dev/null +++ b/mods/Decor.js @@ -0,0 +1,100 @@ +runAfterLoad(function(){ + const patternCache = {}; + let currentPatternID = null; + let nextPatternID = 1; + + elements.pattern_painter = { + color:"#888", tool:function(pixel,x,y){ + if(!currentPatternID){ + currentPatternID = "pattern_"+(nextPatternID++); + patternCache[currentPatternID] = Array(8).fill().map(()=>Array(8).fill(0)); + } + const pat = patternCache[currentPatternID]; + const mx = Math.floor((x*8)%8), my = Math.floor((y*8)%8); + pat[my][mx] = 1 - pat[my][mx]; + }, + category:"tools", + description:"Pattern painter (8x8)" + }; + + function drawPattern(ctx, pat, dx, dy, s){ + for(let py=0;py<8;py++){ + for(let px=0;px<8;px++){ + if(pat[py][px]){ + ctx.fillStyle = "#444"; + ctx.fillRect(dx + px*s/8, dy + py*s/8, s/8, s/8); + } + } + } + } + + function blendColors(c1,c2,t){ + const parse = c=>parseInt(c.slice(1),16); + const r1=((parse(c1)>>16)&255), g1=((parse(c1)>>8)&255), b1=(parse(c1)&255); + const r2=((parse(c2)>>16)&255), g2=((parse(c2)>>8)&255), b2=(parse(c2)&255); + const r=Math.floor(r1*(1-t)+r2*t), g=Math.floor(g1*(1-t)+g2*t), b=Math.floor(b1*(1-t)+b2*t); + return "#"+((1<<24)+(r<<16)+(g<<8)+b).toString(16).slice(1); + } + + elements.decor_block = { + color:"#fff", behavior:behaviors.WALL, category:"solids", + onPlace: p=>{ if(currentPatternID) p.patternID=currentPatternID; }, + renderer: function(p, ctx, dx, dy, s){ + if(p.patternID){ + drawPattern(ctx, patternCache[p.patternID], dx, dy, s); + } else { ctx.fillStyle=p.color; ctx.fillRect(dx, dy, s, s); } + } + }; + + elements.fader_block = { + color:"#fff", behavior:behaviors.WALL, category:"solids", + onPlace: p=>{ if(currentPatternID) p.patternID=currentPatternID; }, + renderer: function(p, ctx, dx, dy, s){ + const pat = p.patternID ? patternCache[p.patternID] : null; + if(!pat){ ctx.fillStyle=p.color; ctx.fillRect(dx, dy, s, s); return; } + for(let py=0;py<8;py++){ + const t = py/7; + for(let px=0;px<8;px++){ + const c = pat[py][px] ? blendColors("#000","#555",t) : blendColors("#ccc","#eee",t); + ctx.fillStyle=c; + ctx.fillRect(dx+px*s/8, dy+py*s/8, s/8, s/8); + } + } + } + }; + + elements.cloth = { + color:"#aaa", behavior:behaviors.POWDER, category:"solids", + onPlace: p=>{ if(currentPatternID) p.patternID=currentPatternID; p.vy=0; }, + tick: function(p){ + if(p.y+1>=pixelGridHeight) return; + const below = pixelMap[p.x][p.y+1]; + if(!below||elements[below.element].behavior!==behaviors.WALL) p.y++; + }, + renderer: function(p, ctx, dx, dy, s){ + if(p.patternID) drawPattern(ctx, patternCache[p.patternID], dx, dy, s); + else ctx.fillStyle=p.color; ctx.fillRect(dx, dy, s, s); + } + }; + + elements.fence_block = { + color:"#666", behavior:behaviors.WALL, category:"solids", + onPlace: p=>{ if(currentPatternID) p.patternID=currentPatternID; }, + renderer: function(p, ctx, dx, dy, s){ + if(p.patternID) drawPattern(ctx, patternCache[p.patternID], dx, dy, s); + else ctx.fillStyle=p.color; ctx.fillRect(dx, dy, s, s); + } + }; + + runEveryTick(function(){ + renderPrePixel(function(p, ctx, dx, dy, s){ + if(p.element==="decor_block"||p.element==="fader_block"||p.element==="cloth"||p.element==="fence_block"){ + const shade = ctx.createLinearGradient(dx, dy, dx, dy+s); + shade.addColorStop(0,"rgba(0,0,0,0)"); + shade.addColorStop(1,"rgba(0,0,0,0.3)"); + ctx.fillStyle=shade; + ctx.fillRect(dx, dy, s, s); + } + }); + }); +});