This file is indexed.

/usr/share/fceux/luaScripts/SMB3-RainbowRiding.lua is in fceux 2.2.2+dfsg0-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
SCRIPT_TITLE	= "Super Mario Bros. 3 Rainbow Riding"
SCRIPT_VERSION	= "0.1"

require "m_utils"
m_require("m_utils",0)

--[[
Super Mario Bros. 3 Rainbow Riding
version 0.1 by miau

Visit http://morphcat.de/lua/ for the most recent version and other scripts.

This script turns smb3 into a new game very similar to Kirby's Canvas Curse.
It's still incomplete, messy and full of bugs, so don't expect too much. Next version
will fix most of that... hopefully.
Probably slow on old computers, you may want to turn down emulator speed anyway to
decrease difficulty.

Controls
	Left-click on mario - jump
	Middle-click anywhere - run
	Draw vertical lines to make mario change his walking direction
	Draw horizontal lines to help mario move over obstacles

Supported roms
	Super Mario Bros 3 (J), Super Mario Bros 3 (U) (PRG 0), Super Mario Bros 3 (U) (PRG 1),
	Super Mario Bros 3 (E)

Known Bugs/TODO list
	Too many to list em all, actually...
	- game objects are occasionally catapulted out of screen - bad divisions!?
	- mario falling through lines (clean up collision detection)
	- long vertically scrolling levels won't work
	- boss battles are glitchy
	- disable auto move in mini games?
	- option to disable auto move?
	- make collision detection work with fire balls (fire mario or fire piranha plants)
	- improve map screen check
	- add easy mode: decrease mario's speed
	- improve enemy hit boxes
	- (add suicide button in case mario gets stuck)
-]]

--configurable vars
local show_cursor = false
--------------------------------------------------------------------------------------------



local Z_LSPAN = 400 --240
local Z_MAX = 128 --256 --maximum amount of lines on screen
local NUM_SPRITES = 20

local zbuf = {}
local zindex = 1
local timer = 0
local zprev = 0
local last_inp={}
local spr = {} --game's original sprites
local mario = {}
local clickbox={x1=-4,y1=4,x2=18,y2=32}
local collx = 0
local colly = 0
--local coll = {} --debug
local paintmeter = 0
local lastpaint = -100
local jp = {}


--accepts two tables containing these elements [1]=x1, [2]=y1, [3]=x2, [4]=y2
function get_line_intersection(a,b)
	local Asx,Asy,Bsx,Bsy,s,t
	local Ax1,Ay1,Ax2,Ay2
	local Bx1,By1,Bx2,By2
	Ax1 = a[1]
	Ay1 = a[2]
	Ax2 = a[3]
	Ay2 = a[4]
	Bx1 = b[1]
	By1 = b[2]
	Bx2 = b[3]
	By2 = b[4]
	Asx = Ax2 - Ax1
	Asy = Ay2 - Ay1
	Bsx = Bx2 - Bx1
	Bsy = By2 - By1
	
	s = (-Asy * (Ax1 - Bx1) + Asx * (Ay1 - By1)) / (-Bsx*Asy + Asx*Bsy)
	t = (Bsx * (Ay1 - By1) - Bsy * (Ax1 - Bx1)) / (-Bsx*Asy + Asx*Bsy)
	if(s>=0 and s<=1 and t>=0 and t<=1) then
		local Ix,Iy
		Ix = Ax1 + t * Asx
		Iy = Ay1 + t * Asy
		return Ix,Iy
	else
		return nil
	end
end

function close_to_line(Px,Py,a,range) --a[4] line segment
	local Ax=a[1]
	local Ay=a[2]
	local Bx=a[3]
	local By=a[4]
	local APx = Px - Ax
	local APy = Py - Ay
	local ABx = Bx - Ax
	local ABy = By - Ay
	local absq = ABx*ABx + ABy*ABy
	local apab = APx*ABx + APy*ABy
	local t = apab / absq
	
	if (t < 0.0) then
		t = 0.0
	elseif (t > 1.0) then
		t = 1.0
	end
	local Cx,Cy
	Cx = Ax + ABx * t
	Cy = Ay + ABy * t
	if(getdistance(Px,Py,Cx,Cy)<=range) then
		return true
	else
		return false
	end
end

function ride_line(s,xoffs,yoffs)

	local function move(zx1,zy1,zx2,zy2)
		local avx,avy,cvx,cvy
		cvx,cvy = getvdir(zx1,zy1,zx2,zy2)
		if(math.abs(spr[s].vx)<math.abs(spr[s].vy)) then
			avx = spr[s].vx
			avy = spr[s].vx
		else
			avx = spr[s].vy
			avy = spr[s].vy
		end
		if(spr[s].movetimer) then
			avx = cvx*5
			avy = cvy*5
		else
			avx = cvx*2.5
			avy = cvy*3
		end
		
		set_sprite_velocity(s,avx,avy)
		--coll.a = {spr[s].x+xoffs,spr[s].y+yoffs-1,spr[s].x+xoffs+avx*64,spr[s].y+yoffs+avy*64-1}
	end
	
	local function getcoords(i)
		local j
		j = i - 1
		if(j < 1) then
			j = Z_MAX
		end
		if(zbuf[i] and zbuf[j]) then
			return zbuf[j].x,zbuf[j].y,zbuf[i].x,zbuf[i].y
		else
			return nil
		end
	end
	
	local function domovemagic(x,y,firstline)
		local zx1,zy1,zx2,zy2 = getcoords(spr[s].riding)
		if(zx1 and close_to_line(x,y,{zx1,zy1,zx2,zy2},5)) then
			move(zx1,zy1,zx2,zy2)
		else
			--get next line.. check for collision
			spr[s].riding = spr[s].riding + 1
			if(spr[s].riding > Z_MAX) then
				spr[s].riding = 1
			end
			if(spr[s].riding==firstline) then
				spr[s].riding=nil
				return
			end
			domovemagic(x,y,firstline)
		end
	end
	
	if(spr[s].riding==nil) then
		return
	end
	
	local x = spr[s].x+xoffs
	local y = spr[s].y+yoffs
	domovemagic(x,y,spr[s].riding)
end

function collisioncheck(s)
	local c=false
	local cvx,cvy
	local avx,avy

	local function checkpixel(xoffs,yoffs,i,j)
		local x = spr[s].x+xoffs
		local y = spr[s].y+yoffs
		local px2 = x+spr[s].vx
		local py2 = y+spr[s].vy
		local px1 = x
		local py1 = y
		local zx = zbuf[i].x
		local zy = zbuf[i].y
		local zx2 = zbuf[j].x
		local zy2 = zbuf[j].y
		if(spr[s].vx~=0 or spr[s].vy~=0) then
			if(spr[s].vx>0) then --we need at least one pixel!!?
				px2 = px2 + 2
			elseif(spr[s].vx<0) then
				px2 = px2 - 2
			end
			if(spr[s].vy>0) then
				py2 = py2 + 2
			elseif(spr[s].vy<0) then
				py2 = py2 - 2
			end
			
			local cx,cy = get_line_intersection({px1,py1,px2,py2},{zx,zy,zx2,zy2})
			if(cx~=nil) then
				cx = math.floor(cx)
				cy = math.floor(cy)
				local avx,avy,cvx,cvy
				
				--coll = {--[[a={px1,py1,px2+spr[s].vx*64,py2+spr[s].vy*64},-]]b={zbuf[i].x,zbuf[i].y,zbuf[j].x,zbuf[j].y}} --debug
				collx = cx
				colly = cy
				
				avx,avy = getvdir(px1,py1,px2,py2)
				cvx,cvy = getvdir(zbuf[j].x,zbuf[j].y,zbuf[i].x,zbuf[i].y)
				
				
					local x,y
					x = cx-xoffs
					y = cy-yoffs
					if(spr[s].x==x and spr[s].y==y) then
						--FCEU.message("boo"..timer)
					else
						set_sprite_pos(s,x,y)
					end
					if(s==0) then --mario/luigi
						if(math.abs(cvy)==1 and math.abs(cvx) < 0.5) then
							change_sprite_dir(s)
							set_sprite_velocity(s,-avx,nil)
						else
							spr[s].riding = i
							set_sprite_velocity(s,0,0)
						end
					else --enemies, moving platforms
					end
					
					
				return true
			end
		else
			if(close_to_line(x,y,{zx,zy,zx2,zy2},4)) then
				if(s==0) then
					spr[s].riding = i
					set_sprite_velocity(s,0,0)
				end
				return true
			else
				spr[s].riding = nil
			end
		end
		return false
	end
	
	if(spr[s].riding) then
		ride_line(s,8,30)
		return
	end
	
	for i=1,Z_MAX do
		if(zbuf[i] and zbuf[i].connected) then
			--if(zbuf[i].x >= spr[s].x+hitbox.x1 and zbuf[i].y >= spr[s].y+hitbox.y1
			--	and zbuf[i].x <= spr[s].x+hitbox.x2 and zbuf[i].y <= spr[s].y+hitbox.y2) then
				--local px = spr[s].x-spr[s].vx
				--local py = spr[s].y-spr[s].vy
				local j
				j = i - 1
				if(j < 1) then
					j = Z_MAX
				end
				
				if(zbuf[j]) then --check if line is still valid (if nil, node disappeared)
					if(s==0) then --mario
						if(checkpixel(8,30,i,j)) then
							return
						end
					else
						if(checkpixel(0,0,i,j) or checkpixel(8,8,i,j)
							--[[or checkpixel(0,8,i,j) or checkpixel(8,0,i,j)-]]) then
							destroy_sprite(s)
							return
						end
					end
				end
				
			--end
		end
	end
	
end


function screen_to_game_pos(x,y)
	return x+scroll_x,y+scroll_y
end

function game_to_screen_pos(x,y)
	return x-scroll_x,y-scroll_y
end


function destroy_sprite(s)
	if(s<10) then
		memory.writebyte(0x660+s,0x06)
		--memory.writebyte(0x660+s,0x00)
		--set_sprite_velocity(s,10,10)
	else
		memory.writebyte(0x6C7+s-10,0x01)
		--set_sprite_pos(s,0,0)
	end
end

function set_sprite_velocity(s,vx,vy)
	if(s<10) then
		if(s==0) then
			memory.writebyte(0xD8,1) --air flag?
		end
		if(vx) then
			memory.writebyte(0xBD+s,vx*16)
			spr[s].vx = vx
		end
		if(vy) then
			memory.writebyte(0xCF+s,vy*16)
			spr[s].vy = vy
		end
	end
end

function set_sprite_pos(i,x,y)
	memory.writebyte(0x90+i,AND(x,255))
	memory.writebyte(0x75+i,math.floor(x/256))
	memory.writebyte(0xA2+i,AND(y,255))
	memory.writebyte(0x87+i,math.floor(y/256))
	spr[i].x = x
	spr[i].y = y
	spr[i].sx,spr[i].sy = game_to_screen_pos(spr[i].x,spr[i].y)
end

function change_sprite_dir(s)
	if(spr[s].dir == 1) then
		spr[s].dir = -1
	elseif(spr[s].dir == -1) then
		spr[s].dir = 1
	end
end


function add_rainbow_coord(cx,cy,connected)
	zbuf[zindex] = {t=timer,x=cx,y=cy,connected=connected}
	zprev = zbuf[zindex]
	zindex = zindex + 1
	if(zindex>Z_MAX) then
		zindex = 1
	end
end

function drawrainbow(x1,y1,x2,y2,coloffs)
	local cx,cy
	local vx,vy
	local color = coloffs
	vx,vy = getvdir(x1,y1,x2,y2)
	cx = x1
	cy = y1
	
	for i=1,200 do
		if(cx>=0 and cy>=0 and cx<=253 and cy<=253) then
			local rcolor = color/1.8+161 --165
			gui.drawpixel(cx,cy,rcolor)
			gui.drawpixel(cx,cy+1,rcolor)
			gui.drawpixel(cx+1,cy,rcolor)
			gui.drawpixel(cx+1,cy+1,rcolor)
			
			gui.drawpixel(cx+2,cy,rcolor)
			gui.drawpixel(cx,cy+2,rcolor)
			gui.drawpixel(cx+2,cy+1,rcolor)
			gui.drawpixel(cx+1,cy+2,rcolor)
			gui.drawpixel(cx+2,cy+2,rcolor)
		end
		if((x2>x1 and cx>x2) or (x2<x1 and cx<x2) or (y2>y1 and cy>y2) or (y2<y1 and cy<y2)) then
			break
		end
		cx = cx + vx
		cy = cy + vy
		color = color + 17
		color = AND(color,15)
	end
	return color
end


function initialize()
	for i=0,NUM_SPRITES-1 do
		spr[i] = {
					a=0,
					id=0,
					id2=0,
					x=0,
					y=0,
					hp=0,
				}
	end
	spr[0].dir = 1
	spr[0].lastposchange = -999
end


function update_sprites()
	mario = spr[0]
	--also change dir when colliding with game objects
	local x = memory.readbyte(0x90)+memory.readbyte(0x75)*256
	local y = memory.readbyte(0xA2)+memory.readbyte(0x87)*256
	if(x~=mario.x or y~=mario.y) then
		mario.lastposchange = timer
	end
	if(mario.dir~=0 and mario.riding==nil and timer-mario.lastposchange>60) then
		mario.lastposchange = timer
		change_sprite_dir(0)
	end
	
	--load game sprites from ram
	for i=0,NUM_SPRITES-1 do
		if(i<10) then
			spr[i].x = memory.readbyte(0x90+i)+memory.readbyte(0x75+i)*256
			spr[i].y = memory.readbyte(0xA2+i)+memory.readbyte(0x87+i)*256
			spr[i].vx = memory.readbytesigned(0xBD+i)/16
			spr[i].vy = memory.readbytesigned(0xCF+i)/16
			spr[i].a = (memory.readbytesigned(0x660+i)~=0)
			spr[i].id = memory.readbytesigned(0x670+i)
			spr[i].sx,spr[i].sy = game_to_screen_pos(spr[i].x,spr[i].y)
		else
			--TODO: 0x5D3?
			local xcomp = memory.readbyte(0xFD)
			local ycomp = memory.readbyte(0xFC)
			spr[i].x = memory.readbyte(0x5C9+i-10)
			spr[i].y = memory.readbyte(0x5BF+i-10)
			spr[i].a = true
			spr[i].vx = 0
			spr[i].vy = 0
			spr[i].sx = AND(spr[i].x-xcomp+256,255)
			spr[i].sy = AND(spr[i].y-ycomp+256,255)
			spr[i].x = spr[i].sx + scroll_x
			spr[i].y = spr[i].sy + scroll_y
		end
		if(spr[i].a) then
			collisioncheck(i)
		end
	end

end

function update_vars()
	--disabling input not possible anymore in FCEUX 2.1?
	jp = {}
	if(mario.riding==nil) then
		if(mario.movetimer) then
			jp.B = 1
			mario.movetimer = mario.movetimer - 1
			if(mario.movetimer == 0) then
				mario.movetimer = nil
			end
		end
		if(mario.dir==1) then
			jp.right=1
		elseif(mario.dir==-1) then
			jp.left=1
		end
		--if(AND(timer,1)==0) then --automatically enter doors and pipes... not a very good idea actually :P
			--jp.up=1
			--if(memory.readbyte(0xD8)==1) then --air flag set? try to stay in air as long as possible... makes it easier to rescue mario if collision detection screws up.. sucks in water levels
			--	jp.A=1
			--end
		--else
		--	jp.down=1 --automatically enter pipes
		--end
	end
	
	inp = input.get()
	
	scroll_x = memory.readbyte(0xFD)+memory.readbyte(0x12)*256
	scroll_y = memory.readbyte(0xFC)--+memory.readbyte(0x13)*256 --not quite right, long vertical scrolling levels won't work
	
	--0xD8 = 0 -> touch ground, 1 -> air
	
	update_sprites()
	
	
	if(inp.middleclick) then
		mario.movetimer = 30
	end
	
	if(inp.leftclick==nil) then
		mario.jumping=false
	end
	if(inp.leftclick and last_inp.leftclick==nil and 
		inp.xmouse>=mario.sx+clickbox.x1 and inp.xmouse<=mario.sx+clickbox.x2 and 
		inp.ymouse>=mario.sy+clickbox.y1 and inp.ymouse<=mario.sy+clickbox.y2)
		then
		jp.A = 1
		mario.jumping = true
	elseif(inp.leftclick and mario.jumping) then
		jp.A = 1
	elseif(inp.leftclick) then
		if(paintmeter>0) then
			local x,y=screen_to_game_pos(inp.xmouse,inp.ymouse)
			if(last_inp.leftclick==nil) then
				add_rainbow_coord(x,y,false)
				outofpaint = nil
			elseif(outofpaint==nil) then
				if(zprev and getdistance(x,y,zprev.x,zprev.y)>8) then
					add_rainbow_coord(x,y,true)
					paintmeter = paintmeter - 2
					lastpaint = timer
				end
			end
		else
			outofpaint = true
		end
	end
	
	joypad.set(1,jp)
	
	last_mario = mario
	last_inp = inp
end

function render()
	--bctext(0,20,string.format("Memory usage: %.2f KB",collectgarbage("count")))
	
	
	local j = 0
	for i=1,Z_MAX do
		if(zbuf[i]) then
			j = j + 1
		end
	end
	--bctext(0,20,"Lines: "..j)
	--bctext(0,20,mario.x..","..mario.y)
	--bctext(0,30,mario.sx..","..mario.sy)
	--bctext(0,40,mario.vx..","..mario.vy)
	--bctext(0,50,"D "..mario.dir)
	
	--bctext(0,70,"scroll_x: "..scroll_x)
	--bctext(0,80,"scroll_y: "..scroll_y)
	--bctext(0,90,AND(mario.x,255))
	--if(mario.riding) then
	--	bctext(0,100,"R")
	--end
	bcbox(mario.sx+clickbox.x1,mario.sy+clickbox.y1,mario.sx+clickbox.x2,mario.sy+clickbox.y2,"white")
	
	local prev_x, prev_y, prev_connected
	local i=zindex
	local color = AND(timer/2,15)
	for j=1,Z_MAX do
		if(zbuf[i]) then
			local ltime = timer-zbuf[i].t
			if(ltime<Z_LSPAN) then
				local x,y=game_to_screen_pos(zbuf[i].x,zbuf[i].y)
				if(prev_x and prev_connected) then
					color = drawrainbow(prev_x,prev_y,x,y,color)
				end
				prev_x = x
				prev_y = y
				prev_connected = zbuf[i].connected
			else
				zbuf[i] = nil
			end
		end
		i = i - 1
		if(i<1) then
			i = Z_MAX
		end
	end
	
	
	local cx,cy = game_to_screen_pos(mario.x+8,mario.y+30)
	bcpixel(cx,cy,string.format("#%02X%02X%02X",math.random(0,255),math.random(0,255),math.random(0,255)))
	
	local pmx=78
	local pmy=226
	if(paintmeter>0) then
		drawrainbow(pmx+paintmeter,pmy,pmx,pmy,timer/6)
	end
	bcbox(pmx-2,pmy-1,pmx+102,pmy+3,"white")
	
	if(timer-lastpaint>60) then
		paintmeter = paintmeter + 1
		if(paintmeter>100) then
			paintmeter=100
		end
	end
	
	if(show_cursor) then
		local col2
		if(inp.leftclick) then
			col2 = "#FFAA00"
		elseif(inp.rightclick) then
			col2 = "#0099EE"
		elseif(inp.middleclick) then
			col2 = "#AACC00"
		else
			col2 = "white"
		end
		drawcursor(inp.xmouse,inp.ymouse,"black",col2)
	end
end


function domagic()
	if(memory.readbyte(0x73)==0x20) then --map screen(?)
		update_vars()
		render()
	else
		bcpixel(1,10,"clear")
	end
end

initialize()
gui.register(domagic)

while(true) do
 	FCEU.frameadvance()
	timer = timer + 1
end