From 93178eb6c28ea759004aec050bddf25793b4545b Mon Sep 17 00:00:00 2001 From: Abe Pralle Date: Mon, 6 Jul 2020 23:43:22 -0700 Subject: [PATCH] Working on WorldMapMaker tool --- .gitignore | 1 + Media/DesignDocs/WorldMap.xls | Bin 19456 -> 19456 bytes README.md | 23 +- Tools/LevelEditor/LevelDataFormat.txt | 38 ++ Tools/WorldMapMaker/.gitignore | 1 + Tools/WorldMapMaker/Build.rogue | 385 ++++++++++++++++++ .../WorldMapMaker/Source/WorldMapMaker.rogue | 142 +++++++ 7 files changed, 587 insertions(+), 3 deletions(-) create mode 100644 Tools/LevelEditor/LevelDataFormat.txt create mode 100644 Tools/WorldMapMaker/.gitignore create mode 100644 Tools/WorldMapMaker/Build.rogue create mode 100644 Tools/WorldMapMaker/Source/WorldMapMaker.rogue diff --git a/.gitignore b/.gitignore index 9ffc6eb..bf099f8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.swp .DS_Store Build +Thumbs.db diff --git a/Media/DesignDocs/WorldMap.xls b/Media/DesignDocs/WorldMap.xls index f7d6b7115e03d9a6007a9559777fc66911b6d55d..d04f9060d461afd3928b462626bf5233cacbbd33 100644 GIT binary patch delta 57 zcmZpe!Pqc^af1evgeU`pZ)S2)esO+UiGqJxT4r*pLTGVn(PT%a@XgbiuDWwVwrtrxDOgx)sFkNxyc)^y>z{ViUu(`ym GhYString + 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: install: +# link: which:) +# +# 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<>() + rogo_descriptions = StringTable<>() + 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 <>.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 = <>.find_property( name ) + if (p) + overrides.add( "$ = $" (name,value) ) + <>.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 <>.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)=>(aString + if (Build.rogo_syntax.contains(m_name)) + return "rogo " + Build.rogo_syntax[ m_name ] + else + local m = <>.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..String + if (Build.rogo_descriptions.contains(m_name)) + return Build.rogo_descriptions[ m_name ] + else + return null + endIf +endRoutine diff --git a/Tools/WorldMapMaker/Source/WorldMapMaker.rogue b/Tools/WorldMapMaker/Source/WorldMapMaker.rogue new file mode 100644 index 0000000..9e8d581 --- /dev/null +++ b/Tools/WorldMapMaker/Source/WorldMapMaker.rogue @@ -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..= 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.. 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