Working on WorldMapMaker tool

This commit is contained in:
Abe Pralle 2020-07-06 23:43:22 -07:00
parent 0707e3814f
commit 93178eb6c2
7 changed files with 587 additions and 3 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
*.swp
.DS_Store
Build
Thumbs.db

Binary file not shown.

View File

@ -26,8 +26,25 @@ The pre-compiled FGB ROM for Game Boy Color is [here](ROMS/fgb.gb).
## Tools
- Each of the following tools is provided as a precompiled Windows `exe` along with their original source code.
- These `exe` files were last compiled circa 2000. No attempt has been made to update their project source. The Level Editor and Image Converter `exe`'s are both confirmed to work on Windows 10.
- During its original development, FGB was essentially a single folder containing hundreds of files. Because of this, each tool typically expects its data files to be in the same folder as the tool itself. Data files will need to be moved into each tool folder as inputs and the result moved back to the appropriate location.
each of the following tools was designed to
All of the editors and conversion tools
- During its original development, FGB was essentially a single folder containing hundreds of files. Because of this, each tool typically expects its data files to be in the same folder as the tool itself.
- This project has now been reorganized to cleanly separate original assets, converted data, and source code into separate folders.
- As such data files will generally need to be moved into each tool folder as inputs and the results moved back to the appropriate location if the editors are used.
- Ideally at some point Abe Pralle or a contributer will update the editor projects to be in a modern Visual Studio format and adjust the input and output locations to utilize the current folder structure.
### LevelEditor
![Level Editor](Media/Screenshots/LevelEditor.png)
1. Refer to `Media/DesignDocs/WorldMap.xls` (slightly out of date) to identify levels you want to edit.
2. Copy corresponding `Data/Levels/*.lvl` files to `Tools/LevelEditor`.
3. Run `Tools/LevelEditor`, load, edit, and save the levels.
4. Copy the modified levels back to `Data/Levels/`.
### GBConv2
![GBConv2](Media/Screenshots/GBConv2.png)
1.
## About

View File

@ -0,0 +1,38 @@
FGB Level Data Format
July 6, 2020 by Abe pralle
file_format_version = 3 : Byte
class_count_minus_1 : Byte
first_character_index : Byte # the first index that is a mobile character rather than a BG tile
first_character_id : UInt16LowHigh # the class of the first character
forEach (index in 0..class_count_minus_1)
class_number_low : Byte
class_number_high : Byte
class_number = ((class_number_high :<<: 8) | class_number) - 1
endForEach
tiles_wide : Byte
pitch : Byte # smallest power of 2 where tiles_wide <= pitch
tiles_high : Byte
loop (tiles_high)
loop (tiles_wide)
class_index : Byte
endLoop
endLoop
bg_color : UInt16 # 0_bbbbb_ggggg_rrrrr
# Waypoint List
# TODO (investigate starting at Tools/LevelEditor/Source/Controls.cpp line 393)
# Zones
# TODO
# Exits
# TODO
# Exit Links
# TODO

1
Tools/WorldMapMaker/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
Images

View File

@ -0,0 +1,385 @@
# To run this build file, install Rogue from github.com/AbePralle/Rogue then cd
# to this folder and type "rogo" at the command line.
# description()s are optional - Rogo uses introspection to determine which
# commands are available. 'rogo help default' displays the description for
# "default", etc.
description( "default", "The default action is performed when no other actions are specified. Use 'rogo default' to explicitly perform the default option." )
description( "help", "Displays a list of all actions that can be performed by Rogo." )
#$ LIBRARIES(macOS) = libpng libjpeg zlib
#$ LIBRARIES(Linux-apt) = libpng-dev libjpeg-dev
augment Build
# You can create a Local.mk file with overrides for these values, e.g.:
# LAUNCHER_FOLDER = "~/bin"
PROPERTIES
PROJECT = "WorldMapMaker"
LAUNCHER_NAME = "worldmapmaker"
LAUNCHER_FOLDER = "/usr/local/bin"
endAugment
routine exe_filepath->String
if (System.is_windows) return "Build/$.exe" (Build.LAUNCHER_NAME)
return "Build/$-$" (Build.PROJECT,System.os)
endRoutine
routine launcher_folder->String
local result = Build.LAUNCHER_FOLDER
result = File.expand_filepath( result )
return File.without_trailing_separator( result )
endRoutine
routine rogo_default
rogo_build
rogo_run
#rogo_install_launcher # Uncomment to automatically install a /usr/local/bin/worldmapmaker launcher
endRoutine
routine rogo_build
block exe_filepath
local source_files = Files( "Source", "**/*.rogue" )
local src_filepath = "Source/$.rogue" (Build.PROJECT)
if (source_files.any_newer_than(exe_filepath))
local cpp_filepath = "Build/$-$.cpp" (Build.PROJECT,System.os)
if (source_files.any_newer_than(cpp_filepath))
execute "roguec $ --main --output=$ --target=Console,C++,$" (src_filepath,cpp_filepath,System.os)
endIf
if (System.is_windows)
local libs = ""
local cc = "cl /EHsc /nologo $ /Fe$ $" (cpp_filepath,exe_filepath,libs)
#cc += " /link /LTCG" # uncomment this if there are errors linking libraries
execute cc
else
local libs = which{ System.is_linux:" $LIBRARY_FLAGS(libpng-dev,libjpeg-dev)" || " $LIBRARY_FLAGS(libpng,libjpeg,zlib)" }
local cc = "c++ -Wall -std=gnu++11 -fno-strict-aliasing -Wno-invalid-offsetof $ -o $$" (cpp_filepath,exe_filepath,libs)
execute cc
endIf
endIf
endBlock
endRoutine
routine rogo_run
execute File.conventional_filepath(exe_filepath)
endRoutine
routine rogo_install_launcher
local exe_filepath = File.absolute_filepath( exe_filepath )
if (System.is_windows)
local exe_folder = File.conventional_filepath( File.folder(exe_filepath) )
local path = System.environment//PATH
if (not path.to_lowercase.contains(exe_folder.to_lowercase))
println
println ''Add the following to your system PATH then reopen this command prompt to be able''
println ''to launch $ by typing "$":'' (Build.PROJECT,Build.LAUNCHER_NAME)
println
println " ADD THIS TO YOUR PATH"
println " " + exe_folder
endIf
else
contingent
local result = Process.run( "which " + Build.LAUNCHER_NAME, &env )
necessary (result.success)
local launcher_filepath = result->String.trimmed
necessary launcher_filepath
necessary File.load_as_string( launcher_filepath ).contains( exe_filepath )
unsatisfied
local launcher_filepath = "$/$" (launcher_folder,Build.LAUNCHER_NAME)
println "Creating launcher " + launcher_filepath
local sudo = ""
loop
try
if (not File.exists(launcher_folder)) execute( "$mkdir $"(sudo,launcher_folder) )
File.save( "$.launcher"(Build.LAUNCHER_NAME), ''#!/bin/sh\nexec "$" "$@"\n''(exe_filepath,'$') )
execute( "$mv $.launcher $"(sudo,Build.LAUNCHER_NAME,launcher_filepath) )
execute( "$chmod a+x $"(sudo,launcher_filepath) )
escapeLoop
catch (err:Error)
if (sudo != "") throw err
sudo = "sudo "
endTry
endLoop
endContingent
endIf
endRoutine
routine rogo_clean
verbose_delete( "Build" )
if (not System.is_windows) verbose_delete( "$/$"(launcher_folder,Build.LAUNCHER_NAME) )
endRoutine
routine verbose_delete( filepath:String )
if (File.exists(filepath))
println "> Delete " + filepath
if (not File.delete(filepath))
println "*** Failed to delete - retrying with sudo"
local cmd = ''sudo rm -rf $'' (File.shell_escaped(filepath))
execute cmd
endIf
endIf
endRoutine
routine execute( commands:String, &suppress_error )->Logical
forEach (cmd in LineReader(commands))
print( "> " ).println( cmd )
if (System.run(cmd) != 0)
if (suppress_error) return false
else throw Error( "Build failed." )
endIf
endForEach
return true
endRoutine
#-------------------------------------------------------------------------------
# Introspection-based Launcher Framework
#-------------------------------------------------------------------------------
# Rogo is a "build your own build system" facilitator. At its core Rogo just
# recompiles build files if needed and then runs the build executable while
# forwarding any command line arguments. This file contains a default framework
# which uses introspection to turn command line arguments into parameterized
# routine calls.
# Example: to handle the command "rogo abc xyz 5", define
# "routine rogo_abc_xyz( n:Int32 )".
# "rogo_default" will run in the absence of any other command line argument.
# The following "comment directives" can be used in this file to control how
# RogueC compiles it and to manage automatic dependency installation and
# linking.
# Each of the following should be on a line beginning with the characters #$
# (preceding whitespace is fine). Sample args are given.
# ROGUEC = roguec # Path to roguec to compile this file with
# ROGUEC_ARGS = --whatever # Additional options to pass to RogueC
# CPP = g++ -Wall -std=gnu++11 -fno-strict-aliasing
# -Wno-invalid-offsetof # C++ compiler path and/or invocation
# CPP_ARGS = -a -b -c # Additional C++ args
# LINK = true # Links following LIBRARIES with this Build
# # file (otherwise just installs them)
# LINK = -lalpha -lbeta # Links following LIBRARIES and includes
# # these additional flags
# LINK = false # Linking turned off for following
# # LIBRARIES - info can still be obtained
# # from $LIBRARY_FLAGS()
# LINK(macOS) = ... # Options applying only to
# # System.os=="macOS" (use with any OS and
# # any comment directive)
# LIBRARIES = libalpha
# LIBRARIES = libbeta(library-name)
# LIBRARIES = libfreetype6-dev(freetype2)
# DEPENDENCIES = Library/Rogue/**/*.rogue
#
# LIBRARIES = name(package)
# LIBRARIES = name(package:<package> install:<install-cmd>
# link:<link-flags> which:<which-name>)
#
# The following macro is replaced within this file (Build.rogue) - the libraries
# should normally also be declared in #$ LIBRARIES:
#
# $LIBRARY_FLAGS(lib1,lib2) # sample macro
# ->
# -Ipath/to/lib1/include -Lpath/to/lib1/library -I ... # sample replacement
routine syntax( command:String, text:String )
Build.rogo_syntax[ command ] = text
endRoutine
routine description( command:String, text:String )
Build.rogo_descriptions[ command ] = text
endRoutine
routine help( command:String, description=null:String, syntax=null:String )
if (description) Global.description( command, description )
if (syntax) Global.syntax( command, syntax )
endRoutine
try
Build.launch
catch (err:Error)
Build.rogo_error = err
Build.on_error
endTry
class Build [singleton]
PROPERTIES
rogo_syntax = StringTable<<String>>()
rogo_descriptions = StringTable<<String>>()
rogo_prefix = ?:{ $moduleName.count:$moduleName "::" || "" } + "rogo_" : String
rogo_command = "default"
rogo_args = @[]
rogo_error : Error
LOCAL_DEFS_FILE = "Local.mk"
METHODS
method launch
rogo_args.add( forEach in System.command_line_arguments )
read_defs
on_launch
parse_args
dispatch_command
method dispatch_command
local m = find_command( rogo_command )
require m
local args = @[]
forEach (arg in rogo_args)
which (arg)
case "true": args.add( true )
case "false": args.add( false )
case "null": args.add( NullValue )
others: args.add( arg )
endWhich
endForEach
if (m.parameter_count == 1 and args.count > 1) args = @[ args ] # Wrap args in a ValueList.
m.call( Global, args )
method find_command( name:String )->MethodInfo
return <<Global>>.find_method( rogo_prefix + name )
method on_error
Console.error.println "=" * 79
Console.error.println rogo_error
Console.error.println "=" * 79
on_exit
System.exit 1
method on_command_found
noAction
method on_command_not_found
println "=" * 79
println "ERROR: No such command '$'." (rogo_args.first)
println "=" * 79
println
rogo_command = "help"
rogo_args.clear
on_command_found
method on_launch
noAction
method on_exit
noAction
method parse_args
block
if (rogo_args.count)
local parts = String[]
parts.add( forEach in rogo_args )
rogo_args.clear
while (parts.count)
local cmd = _join( parts )
if (find_command(cmd))
rogo_command = cmd
on_command_found
escapeBlock
endIf
rogo_args.insert( parts.remove_last )
endWhile
on_command_not_found
endIf
# Use default command
on_command_found
endBlock
method read_defs
read_defs( LOCAL_DEFS_FILE )
method read_defs( defs_filepath:String )
# Attempt to read defs from Local.mk
local overrides = String[]
if (File.exists(defs_filepath))
forEach (line in LineReader(File(defs_filepath)))
if (line.contains("="))
local name = line.before_first('=').trimmed
local value = line.after_first('=').trimmed
if (value.begins_with('"') or value.begins_with('\''))
value = value.leftmost(-1).rightmost(-1)
endIf
local p = <<Build>>.find_property( name )
if (p)
overrides.add( "$ = $" (name,value) )
<<Build>>.set_property( this, p, Value(value) )
endIf
endIf
endForEach
endIf
method _join( value:Value )->String
local args = String[]
args.add( forEach in value )
return args.join( "_" )
endClass
routine rogo_help( command="":String )
command = Build._join( Build.rogo_args )
if (command.count)
local syntax = get_syntax( command )
local success = false
if (syntax)
println "SYNTAX"
println " " + syntax
println
success = true
endIf
local description = get_description( command )
if (description)
println "DESCRIPTION"
forEach (line in LineReader(description.word_wrapped(76)))
print( " " ).println( line )
endForEach
println
success = true
endIf
if (success)
return
else
println "=" * 79
println "ERROR: No such command '$'." (command)
println "=" * 79
println
endIf
endIf
println "USAGE"
local lines = String[]
forEach (m in <<Global>>.methods)
if (m.name.begins_with(Build.rogo_prefix))
lines.add( " " + get_syntax(m.name.after_first(Build.rogo_prefix)) )
endIf
endForEach
lines.sort( (a,b)=>(a<b) )
println (forEach in lines)
println
endRoutine
routine get_syntax( m_name:String )->String
if (Build.rogo_syntax.contains(m_name))
return "rogo " + Build.rogo_syntax[ m_name ]
else
local m = <<Global>>.find_method( Build.rogo_prefix + m_name )
if (not m) return null
local line = "rogo $" (m_name.replacing('_',' '))
line += " <$>" (m.parameter_name(forEach in 0..<m.parameter_count))
return line
endIf
endRoutine
routine get_description( m_name:String )->String
if (Build.rogo_descriptions.contains(m_name))
return Build.rogo_descriptions[ m_name ]
else
return null
endIf
endRoutine

View File

@ -0,0 +1,142 @@
#===============================================================================
# WorldMapMaker.rogue
# July 6, 2020
#===============================================================================
$requireRogue "1.7.5"
uses Bitmap
WorldMapMaker()
class WorldMapMaker
METHODS
method init
File.create_folder( "Images" )
#forEach (filepath in File.listing("../../Data/Levels/L*.lvl"))
local filepath = "../../Data/Levels/L0001.lvl"
println filepath
local level = FGBLevel( filepath )
level.render.save_as_png( "Images/$.png"(level.level_number.format("04")) )
#endForEach
endClass
class Graphics [singleton]
PROPERTIES
tiles = Bitmap[]
METHODS
method init
tiles.add( load_with_black_as_transparent("../LevelEditor/background0001-1535.bmp").split(32,64) )
tiles.add( load_with_black_as_transparent("../LevelEditor/objects2048-2303.bmp").split(32,32) )
method load_with_black_as_transparent( filename:String )->Bitmap
local bmp = Bitmap( File(filename) )
forEach (pixel at index in bmp.pixels)
if (pixel.argb == 0xff000000)
bmp.pixels[ index ] = Color(0)
endIf
endForEach
return bmp
endClass
class FGBLevel
PROPERTIES
filename : String
level_number : Int32
version : Int32
classes = Int32[]
first_character_index : Int32
first_character_id : Int32
width : Int32
pitch : Int32
height : Int32
tiles = Int32[]
bg_color : Color
METHODS
method init( filename )
level_number = filename.after_last('L').before_first('_')->Int32
local reader = File.reader( filename )
version = reader.read
local class_count = reader.read
classes.add( 0 ) # index 0 is always empty space
block
first_character_index = reader.read
first_character_id = reader.read
first_character_id = first_character_id | (reader.read :<<: 8)
endBlock
block
loop (class_count)
local low = reader.read : Int32
local high = reader.read : Int32
local cls = ((high:<<:8) | low)
if (cls < first_character_id) --cls
if (cls == 1532) cls = 0
classes.add( cls )
endLoop
endBlock
width = reader.read
pitch = reader.read
height = reader.read
trace width, pitch, height
forEach (j in 0..<height)
forEach (i in 0..<width)
local index = reader.read
trace index, classes.count
require (index >= 0 and index < classes.count)
tiles.add( classes[index] )
endForEach
endForEach
forEach (cls at index in tiles)
which (cls)
case 2102, 2120, 2140, 2168, 2222, 2230, 2238, 2246, 2258, 2268, 2298, 2319, 2335,
2343, 2351, 2362, 2370, 2374, 2390, 2408, 2418, 2426, 2434, 2446
tiles[index+1] = cls+1
tiles[index+width] = cls+2
tiles[index+width+1] = cls+3
endWhich
endForEach
local color_low = reader.read : Int32
local color_high = reader.read : Int32
local color = (color_high :<<: 8) | color_low
bg_color = Color( color_5_to_8(color), color_5_to_8(color:>>:5), color_5_to_8(color:>>:10) )
# Waypoint List
# TODO (investigate starting at Tools/LevelEditor/Source/Controls.cpp line 393)
# Zones
# TODO
# Exits
# TODO
# Exit Links
# TODO
method get( i:Int32, j:Int32 )->Int32
return tiles[ j*width + i ]
method render->Bitmap
local result = Bitmap( (width-2)*16, (height-2)*16, bg_color )
forEach (j in 1..<height-1)
forEach (i in 1..<width-1)
local tile = this.get(i,j)
if (tile > 0)
Graphics.tiles[ this.get(i,j) ].blit( result, (i-1)*16, (j-1)*16, BitmapBlitFn.ALPHA )
endIf
endForEach
endForEach
return result
method color_5_to_8( c:Int32 )->Int32
c &= 0x1F
return ((c / Real64(0x1F)) * 255)->Int32
endClass