Booster's Lab - It's Pretty Good Now

Nov 19, 2012 at 2:53 PM
In my body, in my head
Forum Moderator
"Life begins and ends with Nu."
Join Date: Aug 28, 2009
Location: The Purple Zone
Posts: 5998
Alright.
Thanks for the help guys, I'll post the next version.. sometime soon-ish.
 
Nov 19, 2012 at 4:54 PM
Neophyte Member
"Fresh from the Bakery"
Join Date: Nov 14, 2012
Location: Stuck between dreams and real life
Posts: 7
Age: 28
Am I just blind or is there not a way to change starting point? D: I'm probably just blind.
 
Nov 19, 2012 at 5:03 PM
In my body, in my head
Forum Moderator
"Life begins and ends with Nu."
Join Date: Aug 28, 2009
Location: The Purple Zone
Posts: 5998
Oh right, I keep forgetting about that. I was going to make it a module for the (as-yet unfinished) asm patcher because A) the start location, health etc. is all hard-coded into the exe and B) it can't be changed in CS+ so I didn't want to integrate it too closely with the main UI.

As a workaround, you can set event #0200 in map 13 (Start) to <TRA to whichever map you want and run the "real" start event. The game should start faded out so the end-user shouldn't be able to tell.
 
Nov 19, 2012 at 5:08 PM
Neophyte Member
"Fresh from the Bakery"
Join Date: Nov 14, 2012
Location: Stuck between dreams and real life
Posts: 7
Age: 28
I didn't even think of that. Great idea, thank you.
 
Nov 19, 2012 at 9:00 PM
Not anymore
"Run, rabbit run. Dig that hole, forget the sun."
Join Date: Jan 28, 2010
Location: Internet
Posts: 1369
Age: 32
The patcher is just an autohacker. Or a thing that takes hex codes and hex-edits them into the exe.

However, Noxid, you should take all of the Doukutsu Assembler's source code and put it into BL. That way, it will be a fully featured compiler / assembling thingy as well as the best editor for Cave Story evar.
 
Nov 19, 2012 at 9:20 PM
In my body, in my head
Forum Moderator
"Life begins and ends with Nu."
Join Date: Aug 28, 2009
Location: The Purple Zone
Posts: 5998
Yeah, that's all it is right now, but I want to be able to generate parameterized hacks with a user-friendly frontend, something along the lines of a hack file that's xml-formatted with something like

Code:
<hack>
<label>My Osm H4xXxXxX!!1!</label>
<text id="speed">
 <len>4</len>
 <init>
  <readmem type="int">0x414CB0</readmem>
 </init>
</text>
<check>
 <text>coolOption</text>
 <init>true</init>
</check>
<button>
 <text>Commit</text>
 <action>
  <write address="0x414CB0">
   <formvalue>speed</formvalue>
  </write>
  <write address="0x451234">
   <file type="asm">hackfile.asm</file>
  </write>
 </action>
</button>
</hack>

but I couldn't come up with a good set of tags or a format that wasn't balls to write out and I had better things to do so I sidelined it.

Also I tried to look at the assembler sourcecode but I didn't really understand how to integrate it instantly so I also sidelined that.
 
Nov 19, 2012 at 9:36 PM
Not anymore
"Run, rabbit run. Dig that hole, forget the sun."
Join Date: Jan 28, 2010
Location: Internet
Posts: 1369
Age: 32
Hm....

I could try Java *.class file injection into the JAR, but you have to set up the source code.
Or you could give me the source code and I would do it by trying to not look at any of the BL source while only modifying the necessary parts.

So you are going to have XML support eventually? Also you already have support for rendering HTML files and running JavaScript. Wowzers!
This is key for Booster's Lab: Zawinski's Law
 
Nov 19, 2012 at 9:41 PM
In my body, in my head
Forum Moderator
"Life begins and ends with Nu."
Join Date: Aug 28, 2009
Location: The Purple Zone
Posts: 5998
I only tried for like 5 minutes tops so I am sure if I spent even a little bit more time on it I'm sure I could figure something out myself.

BL will probably be a fully-fledged OS by 2014
 
Nov 19, 2012 at 9:50 PM
Not anymore
"Run, rabbit run. Dig that hole, forget the sun."
Join Date: Jan 28, 2010
Location: Internet
Posts: 1369
Age: 32
Booster's Lab OS X - Available for sale on the app store and at electronics warehouses.
 
Nov 23, 2012 at 4:56 AM
In my body, in my head
Forum Moderator
"Life begins and ends with Nu."
Join Date: Aug 28, 2009
Location: The Purple Zone
Posts: 5998
Alright, I had to give a miss on V0221 due to some things coming up, so you'll go straight to V0222!

I think this will be a very good release and I've added tons and tons of things to make the editor *nicer*, so please check out the changelog in the OP. And use it! I can't fix bugs that I don't know exist because nobody is using the stupid program and reporting things.

It's very nearly complete and all that's left is more polishing, ironing out bugs, and a few supplementary features to push it above and beyond any other editors out there.
Thanks to everyone who's supported the development so far. It's come a long way since the first V0001.
 
Nov 23, 2012 at 7:30 AM
Not anymore
"Run, rabbit run. Dig that hole, forget the sun."
Join Date: Jan 28, 2010
Location: Internet
Posts: 1369
Age: 32
Okay then...

You can actually run the game from the editor!
It now has the only feature of SW that was useful and also possesses all the features of CE that were useful too!
How does the game tell if you've accepted the license? I don't see a save-file.
Does it do some sort of registry hack?
This is so sweet, I will try to mod for real with it.
I want to suggest something: plugins.
Users should be able to write plugins for BL just like they can do so for the PhysicsEditor.
Plugins are like Chrome extensions or Firefox Addons.
They extend the functionality of BL.
You can choose to have the plugin language be Javascript, C, or anything else really.
(Notice that this is another attempt to get Booster's Lab to act more like a web browser, which will in turn make it act more like an operating system.)
EDIT: By the way, I love the new graphics and backgrounds. Very classy.
 
Nov 23, 2012 at 11:57 AM
In my body, in my head
Forum Moderator
"Life begins and ends with Nu."
Join Date: Aug 28, 2009
Location: The Purple Zone
Posts: 5998
It uses a feature called "Preferences", which associates small bits of data (like an integer or a string) with the classfile on the OS. I've used them since wayback to store some things like the background color of the tileset and as far as I know it's magic.

As for plugins, I might consider it, but I don't know how. Can you link a good tutorial / guide to doing that?
 
Nov 23, 2012 at 3:31 PM
The Eternal Darkness
"Wacka-Wacka-Wacka-Wacka-Wacka-Wacka-Wacka-Wacka-BLEIUP"
Join Date: Feb 12, 2012
Location: The Enemy of the Truth
Posts: 314
Age: 106
I really like Booster's Lab now.
Would it be possible to make the NPC list resize-able like the map listing?



I like the dialog box that comes up when no exe has been loaded yet and one tries to generate the flag list.
But what does the Japanese text in the About dialog box say?
And what's CSe?
(it's not something like "CS Eternal" is it?)

Also, the Binary Patcher has a button that says some sort of weird stuff (probably also in the same language as above but messed up).
I like the changeable text in the white box of the Binary Patcher.

Also, there are some things that are missing from the Tilesets guide, should it remain so?
 
Nov 23, 2012 at 4:12 PM
In my body, in my head
Forum Moderator
"Life begins and ends with Nu."
Join Date: Aug 28, 2009
Location: The Purple Zone
Posts: 5998
Shane said:
I really like Booster's Lab now.
Would it be possible to make the NPC list resize-able like the map listing?
I'm not sure what you mean by this. You can adjust the proportions if you drag the edges (to make the entity info / entity list the larger of the two, or to make both wider at cost to the width of the map display) as well as double-clicking the list to pop it out into its own window. Same with tilesets.
I like the dialog box that comes up when no exe has been loaded yet and one tries to generate the flag list.
But what does the Japanese text in the About dialog box say?
And what's CSe?
(it's not something like "CS Eternal" is it?)
Also, the Binary Patcher has a button that says some sort of weird stuff (probably also in the same language as above but messed up).
I like the changeable text in the white box of the Binary Patcher.
It says "Booster's Love version <whatever>"
that and the other thing are artifacts of me testing japanese language compatibility. I should clean it up.
CSe stands for CS Engine, which is not the cave story engine but another one based on it of my own design.
Also, there are some things that are missing from the Tilesets guide, should it remain so?
there's stuff missing??
 
Nov 23, 2012 at 5:12 PM
Not anymore
"Run, rabbit run. Dig that hole, forget the sun."
Join Date: Jan 28, 2010
Location: Internet
Posts: 1369
Age: 32
Plugins Guide

Here is a tutorial on how to make plugins.
It's a Prezi, which is similar to a PowerPoint but better.

If you want, I can just give you an existing language implemented in Java and you can use that for your plugins.
However, you are free to make your own programming language or install something such as Jython (Java Python) to serve as your plugin language.

EDIT: Use the back / forward buttons on the website's bar below the presentation to navigate the Prezi.
 
Nov 23, 2012 at 5:32 PM
Neophyte Member
"Fresh from the Bakery"
Join Date: Aug 23, 2012
Location:
Posts: 3
Veggie Carrot Lord... since your GitHub mint programming language seems to be large... (over 9000 lines of code...) I would suggest a simpler language.

Python is also too complicated. Also it's kind of slow for a real programming language.
I suggest Lisp. Lisp is a clean and beautiful language...

Interpreter follows... (you need to change it to Java D: )
Code:
# Python Lisp: Common Lisp in Python 3.3.0!
# @author Shinobi N.
macros = ["if", "define", "defun", "setf", "let"]
_globals = {}
func_locals = [{}]
from fractions import Fraction
class Function(object):
    def __init__(self, name, parameters, body):
	    self.name = name
	    self.params = parameters
	    self.body = body
    def execute(self, arguments):
	    self.saved_body = str(self.body)
	    global func_locals
	    func_locals.append({})
	    i = 0
	    for p in self.params:
		    func_locals[-1][p] = arguments[i]
		    i += 1
	    for line in self.body:
		    result = evaluate_lisp(line)
	    self.body = eval(self.saved_body)
	    func_locals.pop()
	    return result
def find_matching_close_paren(lst, start=0):
    i = start
    open_parens = 0
    while i < len(lst):
	    if lst[i] == "(":
		    open_parens += 1
	    if lst[i] == ")":
		    open_parens -= 1
		    if open_parens < 0:
			    return i
	    i += 1
    return -1
def eval_parens(lst):
    i = 0
    while i < len(lst):
	    item = lst[i]
	    if item == "(" and lst[i + 1] not in macros:
		    end = find_matching_close_paren(lst, i + 1)
		    lst[i:end + 1] = [eval_function(lst[i + 1], get_args(lst, i + 2))]
	    elif item == "(":
		    end = find_matching_close_paren(lst, i + 1)
		    lst[i:end + 1] = [eval_macro(lst[i + 1], get_raw_args(lst, i + 2))]
	    elif item == "/":
		    lst[i - 1:i + 2] = [Fraction(evaluate(lst[i - 1]), evaluate(lst[i + 1]))]
	    i += 1
    return lst
def eval_expr(lst, index):
    index += 1
    i = 0
    last_i = 0
    while i < len(lst):
	    item = lst[i]
	    if item == "(":
		    last_i = i
		    i = find_matching_close_paren(lst, i + 1)
	    index -= 1
	    if index == 0 and item == "(":
		    if lst[last_i + 1] not in macros:
			    args = get_args(lst, last_i + 2)
			    i = 0
			    while i < len(args):
				    args[i] = evaluate(args[i])
				    i += 1
			    return eval_function(lst[last_i + 1], args)
		    else:
			    return eval_macro(lst[last_i + 1], get_raw_args(lst, last_i + 2))
	    elif index == 0:
		    return item
	    i += 1
    return None
def get_expr(lst, index):
    index += 1
    i = 0
    last_i = 0
    while i < len(lst):
	    item = lst[i]
	    if item == "(":
		    last_i = i
		    i = find_matching_close_paren(lst, i + 1)
	    index -= 1
	    if index == 0 and item == "(":
		    return lst[last_i + 1:i]
	    elif index == 0:
		    return item
	    i += 1
    return None
def get_raw_args(tokens, i):
    end = find_matching_close_paren(tokens, i)
    args = tokens[i:end]
    return args
def get_args(tokens, i):
    end = find_matching_close_paren(tokens, i)
    args = tokens[i:end]
    args = eval_parens(args)
    i = 0
    while i < len(args):
	    args[i] = evaluate(args[i])
	    i += 1
    return args
def to_boolean(value):
    if type(value) == bool:
	    return value
    if value == []:
	    return False
    return bool(value)
def to_lisp_boolean(value):
    if value is False:
	    return []
    return value
def eval_function(func_name, args):
    global _globals
    global func_locals
    i = 0
    while i < len(args):
	    args[i] = evaluate(args[i])
	    i += 1
    if func_name == "+":
	    for arg in args:
		    if type(arg) is str:
			    concat = ""
			    for arg in args:
				    concat += arg
			    return concat
	    return sum(args)
    elif func_name == "*":
	    product = 1
	    for arg in args:
		    product *= arg
	    return product
    elif func_name == "/":
	    if type(args[0]) is int and type(args[1]) is int:
		    return Fraction(args[0], args[1])
	    return args[0] / args[1]
    elif func_name == "-":
	    if len(args) == 1:
		    return -args[0]
	    return args[0] - args[1]
    elif func_name == "%":
	    return args[0] % args[1]
    elif func_name == "1+":
	    return args[0] + 1
    elif func_name == "-1+" or func_name == "1-":
	    return args[0] - 1
    elif func_name == "upper":
	    return args[0].upper()
    elif func_name == "lower":
	    return args[0].lower()
    elif func_name == "reverse":
	    if type(args[0]) is list:
		    new_list = []
		    i = len(args[0]) - 1
		    while i >= 0:
			    new_list.append(args[0][i])
			    i -= 1
		    return new_list
	    elif type(args[0]) is str:
		    new_str = ""
		    i = len(args[0]) - 1
		    while i >= 0:
			    new_str += args[0][i]
			    i -= 1
		    return new_str
    elif func_name in ["pow", "**", "exp"]:
	    result = args[0]
	    for arg in args[1:]:
		    result **= arg
	    return result
    elif func_name == "=":
	    return [] if args[0] != args[1] else True
    elif func_name == "!=":
	    return True if args[0] != args[1] else []
    elif func_name == ">":
	    return True if args[0] > args[1] else []
    elif func_name == "<":
	    return True if args[0] < args[1] else []
    elif func_name == ">=":
	    return True if args[0] >= args[1] else []
    elif func_name == "<=":
	    return True if args[0] <= args[1] else []
    elif func_name == "and":
	    return to_lisp_boolean(to_boolean(args[0]) and to_boolean(args[1]))
    elif func_name == "or":
	    return to_lisp_boolean(to_boolean(args[0]) or to_boolean(args[1]))
    elif func_name == "not":
	    return to_lisp_boolean(not to_boolean(args[0]))
    elif func_name == "xor":
	    bool1 = to_boolean(args[0])
	    bool2 = to_boolean(args[1])
	    return to_lisp_boolean((bool1 and not bool2) or (not bool1 and bool2))
    elif func_name == "length":
	    return len(args[0])
    elif func_name == "sqrt":
	    from math import sqrt
	    return sqrt(args[0])
    elif func_name == "numberp":
	    return to_lisp_boolean(type(args[0]) is int or type(args[0]) is float)
    elif func_name == "stringp":
	    return to_lisp_boolean(type(args[0]) is str)
    elif func_name == "evenp":
	    return to_lisp_boolean((args[0] % 2) == 0)
    elif func_name == "oddp":
	    return to_lisp_boolean((args[0] % 2) != 0)
    elif func_name == "ln":
	    from math import log
	    return log(args[0])
    elif func_name == "log":
	    from math import log
	    return log(args[0], args[1])
    elif func_name == "sin":
	    from math import sin
	    return sin(args[0])
    elif func_name == "cos":
	    from math import cos
	    return cos(args[0])
    elif func_name == "block" or func_name == "progn":
	    return args[-1]
    elif func_name == "list":
	    return args
    elif func_name == "cons":
	    return [args[0]] + args[1]
    elif func_name == "car" or func_name == "first":
	    return args[0][0]
    elif func_name == "cdr" or func_name == "rest":
	    return args[0][1:]
    elif func_name == "append":
	    result = []
	    for item in args:
		    result += item
	    return result
    elif func_name in _globals.keys() and type(_globals[func_name]) is Function:
	    return _globals[func_name].execute(args)
    else:
	    if func_name in macros:
		    return eval_macro(func_name, args)
	    print("Undefined function " + func_name)
	    return
def evaluate(token, _locals=None):
    if type(token) is not str:
	    return token
    try:
	    return int(token)
    except ValueError:
	    try:
		    return float(token)
	    except ValueError:
		    if token.startswith('"') and token.endswith('"'):
			    return token[1:-1]
		    global _globals
		    global func_locals
		    if token in _globals.keys():
			    return _globals[token]
		    if _locals is not None and token in _locals.keys():
			    return _locals[token]
		    i = len(func_locals) - 1
		    while i >= 0:
			    frame = func_locals[i]
			    if token in frame.keys():
				    return frame[token]
			    i -= 1
		    return token
def eval_macro(macro_name, raw_args):
    global _globals
    if macro_name == "if":
	    if to_boolean(evaluate(eval_expr(raw_args, 0))):
		    return eval_expr(raw_args, 1)
	    else:
		    return eval_expr(raw_args, 2)
    elif macro_name == "setf":
	    result = evaluate(eval_expr(raw_args, 1))
	    _globals[eval_expr(raw_args, 0)] = result
	    return result
    elif macro_name == "let":
	    _locals = {}
	    declarations = get_expr(raw_args, 0)
	    i = 0
	    declaration = 1
	    while True:
		    declaration = get_expr(declarations, i)
		    if declaration is None:
			    break
		    _locals[get_expr(declaration, 0)] = eval_expr(declaration, 1)
		    i += 1
	    i = 1
	    expr = 1
	    j = 0
	    while j < len(raw_args):
		    raw_args[j] = evaluate(raw_args[j], _locals)
		    j += 1
	    while expr is not None:
		    last_expr = expr
		    expr = eval_expr(raw_args, i)
		    i += 1
	    return last_expr
    elif macro_name == "defun" or macro_name == "define":
	    func_name = get_expr(raw_args, 0)
	    parameters = get_expr(raw_args, 1)
	    i = 2
	    body = []
	    next_line = ""
	    while True:
		    next_line = get_expr(raw_args, i)
		    if next_line is None:
			    break
		    if type(next_line) is list:
			    body.append(['('] + next_line + [')'])
		    else:
			    body.append(next_line)
		    i += 1
	    _globals[func_name] = Function(func_name, parameters, body)
	    return func_name
def split_on_no_quotes(line, operators):
    def interleave_quotes(lst):
	    new_list = []
	    odd = False
	    for each_elem in lst:
		    if odd:
			    new_list.append('"' + each_elem + '"')
		    else:
			    new_list.append(each_elem)
		    odd = not odd
	    return new_list
    lst = line.split('"')
    lst = interleave_quotes(lst)
    new_list = []
    for each_elem in lst:
	    if '"' in each_elem:
		    new_list.append(each_elem)
	    else:
		    for each_operator in operators:
			    each_elem = each_elem.replace(each_operator, " " + each_operator + " ")
		    new_list += each_elem.split()
		   
    return new_list
def evaluate_lisp(code):
    if type(code) is str:
	    operators = ["(", ")", "/"]
	    tokens = split_on_no_quotes(code, operators)
    else:
	    tokens = code
    i = 0
    simplification_level = 0
    simplification_limit = 10000
    while type(tokens) is list and len(tokens) > 1:
	    if simplification_level > simplification_limit:
		    return "Error: Unable to simplify exp[b][/b]ression " + str(tokens)
	    simplification_level += 1
	    token = tokens[i]
	    if token == "(" and tokens[i + 1] not in macros:
		    end = find_matching_close_paren(tokens, i + 1)
		    tokens[i:end + 1] = [eval_function(tokens[i + 1], get_args(tokens, i + 2))]
	    elif token == "(":
		    end = find_matching_close_paren(tokens, i + 1)
		    tokens[i:end + 1] = [eval_macro(tokens[i + 1], get_raw_args(tokens, i + 2))]
	    elif token == "/":
		    tokens[i - 1:i + 2] = [Fraction(evaluate(tokens[i - 1]), evaluate(tokens[i + 1]))]
	    i += 1
	    if i >= len(tokens):
		    i = 0
    return evaluate(tokens[0]) if type(tokens) is list else evaluate(tokens)
def read_eval_print_loop():
    while True:
	    command = input("PyLisp> ")
	    result = None
	    lines = command.split(";")
	    for line in lines:
		    if len(command) > 0:
			    result = evaluate_lisp(line)
		    if result is True:
			    print('T')
		    elif result == []:
			    print('NIL')
		    elif type(result) == list:
			    i = 0
			    print('(', end="")
			    for item in result:
				    print(item, end="")
				    if i != len(result) - 1:
					    print(' ', end="")
				    i += 1
			    print(')')
		    elif result is not None:
			    print(result)
read_eval_print_loop()
 
Nov 23, 2012 at 5:35 PM
The Eternal Darkness
"Wacka-Wacka-Wacka-Wacka-Wacka-Wacka-Wacka-Wacka-BLEIUP"
Join Date: Feb 12, 2012
Location: The Enemy of the Truth
Posts: 314
Age: 106
Noxid said:
Shane said:
Also, there are some things that are missing from the Tilesets guide, should it remain so?
there's stuff missing??
Yeah.
Tile type 1 is for background tiles, Tile type 2 is for underwater background tiles, Tile type 3 (and 4, if I had to guess) are background tiles that cannot be passed through by NPCs (don't know about 4, I have yet to see whether it works for underwater tiles).
Tile type 5 are for blocks that act like solids, but bullets can go through them, and they act like background tiles if entities are on them (i.e. like a Deleet). They were used in the Sacred Grounds.
Tile type 61 is similar to tile type 41, but if the player somehow is on a tile that has that property, then it will act as though it's water-related. As far as I know, Pixel used it only once, in one of the tiles of the Plantation. But that could just be my memory being a bit incorrect.
 
Nov 23, 2012 at 5:38 PM
Not anymore
"Run, rabbit run. Dig that hole, forget the sun."
Join Date: Jan 28, 2010
Location: Internet
Posts: 1369
Age: 32
Hey, pastebin! Don't post 400 lines of code like that!

EDIT: Yeah, all my programming projects are OVER 9000!
Muahahahahaha....

Ok I will stop now.
 
Nov 28, 2012 at 10:00 PM
Junior Member
"Wow! The more I drink of this magical beverage, the more games I can play! Wheee!"
Join Date: Oct 10, 2012
Location: North Dakota
Posts: 24
Noxid I recommend compatibility with tile sets in 2x resolution. I have opened a 2x resolution mod and noticed that the sprites in Booster's Lab do not have the same sprites in game.
 
Nov 28, 2012 at 10:07 PM
In my body, in my head
Forum Moderator
"Life begins and ends with Nu."
Join Date: Aug 28, 2009
Location: The Purple Zone
Posts: 5998
The compatibility is there, you just don't have access to it.
 
Top