xxxxxxxxxx
// This project is extremely unfinished and may always be. It was never meant to be a full game.
//
// Initially when I built Crypt, it was a proof of concept for my custom physics engine, which handled 2d
// collisions between objects and polygonal terrain, which generates dynamically using the Marching Squares
// algorithm. I copied that over from my earlier sketch https://openprocessing.org/sketch/1542109.
//
// Later as I added more player dynamics, I wanted to play around with vehicles that could be dragged around the world
// to carry objects or materials. The rigid-body physics system I built for that wasn't revolutionary, but due to the
// low processing power of the replit server I was running off of, I couldn't simulate too many objects on the server itself.
// I implemented a new system in which nearby players simulate the crafts within their render distance, and seamlessly
// transfer control to other nearby players when connection is lost, or a different player requests to transfer control
// so that they can drag the cart, etc. All of this was handled without any server-side controlling code besides a simple
// relaying of socket events. It was seamless and lag free, even when a controlling player dropped connection while a cart
// was still in motion.
//
// I started planning more features, such as a threat dynamic such as asteroids raining from the sky, and a new and improved
// 3d printing system, but never got around to them. And then I moved on to other projects, like my fully written from scratch
// 3d physics engine that incorporated aerodynamic forces, which could be used to simulate planes with properly functioning
// engines that turned propellers, and so on. If you're confused about how to play this game, that's to be expected ;)
let hmap=[],resl=25,ld=0,debug=0
let pc=[0,0],npc=[0,0],dim=[900,100],pcd=[0,-20],didMove=0,moveDir=[],allDir=[],jmp=[0,0,0],djmp=[0,0,0],sjmp=0,edt=[0,0,0],
bt=[2,1,1,1,3],bp=[0.2,0.05,0,0,0],walk=0
let holding=0,dir=1,holdAmt=0,nhold=0
let socket=io('https://Crypt.lrcr.repl.co'),players=[],hold=0,holds=5,atc=[]
let carts=[],printer=[0,0,0,1,{step:0,item:1,fill:0}],items=[],clickTime=0
let printUI=[0,{tab:0,tabs:4,sel:0}]
let messages=[],typing={t:0,m:''}
setup=_=>{
pc=[Math.random()*(dim[0]-200)+100,0]
npc=[pc]
createCanvas(700,500)
fillMap()
//genMap()
socket.on('connect',_=>{
socket.on('change',x=>{
editMap2(x)
})
socket.on('setMap',_=>{
genMap()
console.log('set')
socket.emit('setMap',hmap)
})
socket.on('map',x=>{
hmap=[]
for(let i=0;i<dim[0];i++){
hmap.push([])
for(let j=0;j<dim[1];j++){
hmap[i].push(x[i][j])
}
}
let pdata=localStorage.getItem('crypt_pos')
if(pdata){
pdata=JSON.parse(pdata)
if(pdata.pc)pc=[pdata.pc[0],pdata.pc[1]]
if(pdata.pcd)pcd=[pdata.pcd[0],pdata.pcd[1]]
if(pdata.printer)printer=pdata.printer
if(pdata.hold)hold=pdata.hold
if(pdata.holdAmt)holdAmt=pdata.holdAmt
if(pdata.holding)holding=pdata.holding
if(pdata.printUI)printUI=pdata.printUI
if(pdata.items){
items=[]
for(let i=0;i<pdata.items.length;i++){
items.push([pdata.items[i]])
}
}
if(inMap(pc)){
pc[1]=-dim[1]/2+5
pcd[1]=pc[1]
}
}
ld+=1
if(ld==2)loop()
})
socket.on('carts',x=>{
carts=[]
for(let i=0;i<x.length;i++){
carts.push(x[i])
}
ld+=1
if(ld==2)loop()
})
socket.on('changeProp',(i,n)=>{
let a=carts.filter(x=>x[6]==i)
if(a.length){
eval('a[0]'+n)
}
})
socket.on('player',x=>{
if(x.i==socket.id)return
let a=players.filter(i=>i.i==x.i)
if(a.length){
if(x.fr>a[0].fr){
a[0].fr=x.fr
a[0].pc=[x.pc]
a[0].walk=x.walk
a[0].djmp1=x.djmp1
a[0].hitbox=x.hitbox
a[0].hold=x.hold
a[0].dir=x.dir
a[0].hold2=x.hold2
a[0].a=x.a
}else a[0].a=x.a
}else{players.push(x)}
})
socket.on('cart',x=>{
let a=carts.filter(n=>n[6]==x[6])
if(a.length){
if(x[7].ctrl!=socket.id){
a[0][0][0]=x[0][0]
a[0][0][1]=x[0][1]
a[0][1][0]=x[1][0]
a[0][1][1]=x[1][1]
a[0][2][0]=x[2][0]
a[0][2][1]=x[2][1]
a[0][3][0]=x[3][0]
a[0][3][1]=x[3][1]
}
let m=a[0][7].p
a[0][7]=x[7]
if(x[7].p&&x[7].ctrl==socket.id)a[0][7].p=m
}else{
carts.push(x)
}
})
socket.on('newCart',x=>{
carts.push(x)
})
socket.on('emptyCart',x=>{
if(x==0)return
if(holding<1)holding=1
holdAmt=x
})
socket.on('fillCart',x=>{
holdAmt-=x
if(holdAmt<0.001){
holding=0
}
})
socket.on('deleteCart',x=>{
carts=carts.filter(n=>n[6]!=x)
})
socket.on('message',x=>{
messages.push(x)
})
})
noLoop()
}
draw=_=>{
if(hold!=4)atc=[]
else if(frameCount%30==0){
atc=atc.filter(x=>{
if(typeof x=='number')return (items[x][1]-pc[0])*(items[x][1]-pc[0])+(items[x][2]-pc[1])*(items[x][2]-pc[1])<7.2*7.2
else return carts.filter(r=>r[6]==x).length>0
})
}
background(0)
//pcd[0]=pc[0];pcd[1]=pc[1]
didMove=0;moveDir=[];allDir=[]
if(!inMap(pc[0],pc[1])){
//collideMap()
let b=collideVec(pc,collideSize);pc=[b[0]];didMove=b[1];moveDir=b[2];allDir=b[3]
}
renderMap()
stroke(255);fill(0);strokeWeight(1)
for(let i=0;i<players.length;i++){
drawPlayer(players[i])
players[i].a-=1
}
players=players.filter(x=>x.a>0)
drawPlayer({walk:walk&&(keyIsDown(65)||keyIsDown(68)),djmp1:djmp[1]+djmp[2],hold:holding,hitbox:0,pc:pc,dir:dir,hold2:hold})
npc=[pc]
drawCarts()
drawPrinter()
drawItems()
noStroke();strokeWeight(1);if(debug)noFill()
rect(0,0,700,50)
rect(0,0,150,500)
rect(0,450,700,50)
rect(550,0,150,500)
stroke(255)
line(150,50,550,50)
line(550,50,550,450)
line(550,450,150,450)
line(150,450,150,50)
line(0,0,0,500)
line(0,500,700,500)
line(700,500,700,0)
line(700,0,0,0)
drawMessages()
pc=[npc]
if(typing.t){
textAlign(CENTER,TOP)
text(typing.m+(frameCount%30>15?' •':'|'),width/2-100,height/2-40,200,200)
}
pc[1]+=0.01
walk=Math.max(0,walk-1)
if(didMove){
if(moveDir.filter(x=>Math.abs(x)<Math.PI/3).length>0)walk=20
//console.log(moveDir)
pc[0]=pcd[0]+(pc[0]-pcd[0])*0.9
let cw=moveDir.filter(x=>Math.abs(x)<Math.PI/3).length>0
if(keyIsDown(65)&&!typing.t){pc[0]=pcd[0]+(pc[0]-0.04-pcd[0])*0.9;dir=-1}
if(keyIsDown(68)&&!typing.t){pc[0]=pcd[0]+(pc[0]+0.04-pcd[0])*0.9;dir=1}
if(keyIsDown(87)&&!typing.t&&cw){
sjmp=1
djmp[1]=Math.min(98,djmp[1]+1)
if(djmp[1]>30)pc[0]=pcd[0]+(pc[0]-pcd[0])*0.7
}
if(keyIsDown(83)||typing.t){jmp=[1,pc[0],pc[1]];djmp[2]=Math.min(98,djmp[2]+10)}
}else{
if(keyIsDown(65)&&!typing.t){pc[0]=Math.min(pc[0],pcd[0]+(pc[0]-0.01-pcd[0])*0.9);dir=-1}
if(keyIsDown(68)&&!typing.t){pc[0]=Math.max(pc[0],pcd[0]+(pc[0]+0.01-pcd[0])*0.9);dir=1}
}
collideSize=18-djmp[2]/24
if((keyIsDown(83)||typing.t)&&allDir.filter(x=>Math.abs(x)<Math.PI/3).length>0){jmp[2]+=0.02}
djmp[2]=Math.max(0,djmp[2]-5)
if(djmp[0]&&sjmp){pc[1]-=0.3*Math.min(1.7,1+djmp[1]/140);djmp=[0,0,0]}
if(!keyIsDown(87))djmp[1]=0
if(socket.id)socket.emit('player',{
fr:frameCount,walk:walk&&(keyIsDown(65)||keyIsDown(68)),djmp1:djmp[1]+djmp[2],hold:holding,hitbox:0,pc:pc,dir:dir,hold2:hold,a:50,i:socket.id
})
if(keyIsDown(83)||typing.t){if(jmp[0]){
pc=[jmp[1],jmp[2]]
pcd=[jmp[1],jmp[2]]
if(allDir.length==0)jmp=[0,0,0]
}}else{jmp=[0,0,0]}
let n=[pc[0]-pcd[0],pc[1]-pcd[1]]
if(0){
if(n[0]<-0.01)dir=-1
else if(n[0]<0.01)dir=0
else dir=1
}
if(inMap(pc[0],pc[1])){
//n=[0,-0.1]
pc[1]=-dim[1]/2+5
pcd[1]=pc[1]
}
pcd[0]+=n[0];pcd[1]+=n[1];pc[0]+=n[0];pc[1]+=n[1]
if(holding<1&&holding){
holding=Math.min(1,holding+1/60/bt[hold])
if(holding==1){
if(hold==0||hold==1){
editMap(edt)
}else if(hold==4){
socket.emit('deleteCart',edt[1])
holding=0
}
}
}
textAlign(LEFT,TOP);textSize(15);fill(0);if(keyIsDown(90))fill(255);noStroke();text('x '+Math.floor(pc[0]).toString()+' y '+Math.floor(dim[1]/2-pc[1]).toString()+
'\nFPS: '+Math.round(frameRate()).toString(),160,60)
if(frameCount>10){
if(frameCount%60==0)
localStorage.setItem('crypt_pos',JSON.stringify({pc:pc,pcd:pcd,printer:printer,holding:holding==1?1:0,hold:hold,holdAmt:holdAmt,items:items,printUI:printUI}))
}else{push();noStroke();fill(255);textSize(30);textAlign(CENTER,BOTTOM);text('Loading...',width/2,height-70);pop()}
if(pc[0]>dim[0]/2-1.1)pc[0]=dim[0]/2-1.1
if(pc[0]<1.1-dim[0]/2)pc[0]=1.1-dim[0]/2
if(pc[1]>dim[1]/2-1.1)pc[1]=dim[1]/2-1.1
if(pc[1]<1.1-dim[1]/2)pc[1]=1.1-dim[1]/2
if(printUI[0]){
push();translate(-100,-100)
if(renderItem(prints[printUI[1].tab][1+printUI[1].sel])){
if(printUI[1].pointer=='self'){
printer[4].item=prints[printUI[1].tab][1+printUI[1].sel]
}else{
socket.emit('changeProp',printUI[1].pointer,'[7].p.item='+prints[printUI[1].tab][1+printUI[1].sel].toString())
}
}
pop()
fill(0);stroke(255)
rect(width/2-150,height/2-100,300,200)
textAlign(LEFT,TOP)
noStroke()
for(let i=0;i<printUI[1].tabs;i++){
if(i==printUI[1].tab)fill(0,255,0)
else fill(255)
text(prints[i][0],width/2-140,height/2-90+20*i)
}
fill(0);stroke(255)
rect(width/2-70,height/2-80,200,160)
for(let i=0;i<20;i++){
push()
translate(width/2-50+40*(i%5),height/2-60+40*Math.floor(i/5));scale(2)
if(renderItem(prints[printUI[1].tab][i+1])){noFill();stroke(255);strokeWeight(1/2);if(i==printUI[1].sel)strokeWeight(1);rect(-10,-10,20,20)}
pop()
}
}
}
var socket = io.connect($OP.getEchoServerURL(1544798));
Learn more See an example