Announcement

Collapse
No announcement yet.

Running Multiple Engines Against 1 ID1 (or mod) Folder

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • #31
    In just one day I have severely upgraded my bat skills. I rewrote this entire thing from the ground up and it is awesome.

    Changes:

    1 everything works (last bat had 2 unlikely mistakes - unlikely you would find them)
    2 install script is separate and only meant to be run if you add an engine or game
    3 better folder structure and dynamic menu system
    4 30% more fiber

    I don't intend to release it yet. I released the gist of it yesterday. Imma let some time go by and come back with a much meaner version.
    http://www.nextgenquake.com

    Comment


    • #32
      Bat tut

      I don't want this thread to die just yet. The "theme" (to some degree) was batch scripting. That being said and due to the fact that I have polished my bat skills, I decided to post a bit of a tutorial here.

      The point of this tutorial is to illustrate a menu system. The system works by creating text files with no extension, in the same directory as the bat. The file names double as menu titles. The contents of the files become menu options. The system maps all possible options and retrieves the option name due to the number you chose. This is illustrated by a sophisticated case system that can determine which option you chose and from which menu it came. Included is also a check that the user choice is within the range of possible choices and the menu is reset upon invalid choices being made. This all sounds very complicated but, I'm posting it and went to the trouble to create/comment this because it is actually very simple. If you remove all comments this script is like 62 lines, very tight.

      This is the results of the bat script


      Below are the 4 files needed for this example. All the menu files/options are arbitrary and serve as placeholders for an actual menu. The bat script is fully ::commented and should serve as the body of this tutorial.

      example\menu.bat
      Code:
      @echo off
      
      :main
        ::create a list of filenames to loop through
        set menus="menuOne;menuTwo;menuThree"
        
        ::localize enviro vars and expand vars at execution (vs parse)
        setlocal EnableDelayedExpansion
        
        ::send the menu list through the switch loop
        call :menu_from_file_switch %menus%
        
        ::program is complete
        goto end
        
      :menu_from_file_switch
        ::get list of cases
        set case=%1
      
        ::rape a for loop for it's delim parsing capabilities
        for /f "tokens=1* delims=;" %%a in (%case%) do (
          ::menu creation and choice subroutine
          call :menu_from_file %%a
          ::if there is more to the list (ex: %%b="menuTwo;menuThree") send it back through
          if not "%%b"=="" call :menu_from_file_switch "%%b"
          ::kill this loop iteration
          if not "%%b"=="" exit /b
        )
      
        ::%%b is empty (="")
        endlocal
        exit /b
      
      :menu_from_file
        ::get path for list content
        ::all files are in the same directory in this example so, only filenames are needed
        set list_path=%1
        
        ::reset choice success
        set pass=0
        ::reset map of choices
        set "map="
        ::reset current max list number
        set /a max=0
        
        ::echo the filepath (filename only in this example), in this case the file has no extension
        ::this means it can be used as a menu name
        echo.
        echo.%list_path%
        
        ::loop through file contents
        for /f "delims=" %%i in (%list_path%) do (
          ::increment current max choice number
          set /a max=max+1
      	::concat num-option to map
      	if not "!map!"=="" (set "map=!map!;!max!-%%i") else set "map=!max!-%%i"
      	::ex 3. someOption
      	echo.!max!. %%i
        )
        
        ::start user input
        set /p choice=Choose an option: 
        
        ::if the user choice is !> than the max possible choice and choice!=0 set choice success
        if not !choice! gtr !max! if not !choice!==0 set pass=1
        
        ::if the choice was not valid give a message and start the choice over
        if !pass! egu 0 echo.!choice! is not a valid option
        if !pass! equ 0 goto menu_from_file %list_path%
        
        ::ex: assume choice = 2
        ::map="1-val1;2-val2;3-val3" call set choice= "val2;3-val3"
        if !pass! equ 1 call set choice=%%map:*%choice%-=%%
        ::remove from the first ; clean to the end of the string
        if !pass! equ 1 set value=%choice:;=&rem.%
        
        ::switch through menu possibilities
        if !pass! equ 1 if "%list_path%"=="menuOne" call :one %value%
        if !pass! equ 1 if "%list_path%"=="menuTwo" call :two %value%
        if !pass! equ 1 if "%list_path%"=="menuThree" call :three %value%
        
        ::same as exit /b ... exits this subroutine but not the entire script
        goto :eof
      
      ::a bunch of generic subroutines to illustrate cases
      :one
         set opt=%1
         echo.menuOne: You chose %opt%
         echo.
         exit /b
      :two
         set opt=%1
         echo.menuTwo: You chose %opt%
         echo.
         exit /b
      :three
         set opt=%1
         echo.menuThree: You chose %opt%
         echo.
         exit /b
      
      :end
        ::this is a done deal but stay open until the user presses a key
        pause
        ::upon keypress
        exit
      Note that each menu ends with one line break. This is necessary to be properly parsed.

      example\menuOne
      Code:
      A
      B
      C
      D
      example\menuTwo
      Code:
      aa
      bb
      cc
      dd
      example\menuThree
      Code:
      AaA
      BaB
      CaC
      DaD
      EaE
      FaF
      GaG
      My enginator menu (last posted) utilizes some of these concepts but, where it differs greatly is: This menu uses recursion for a repeatable menu system. My other bat used "spaghetti programming" - where things are repeated instead of recursed.

      ie - do this 3 times VS do this, then do this again but a lil different, then do this again but different still (but not different enough to be 3 separate things)

      Notes:

      My "cases" (ne,:two,:three) may seem repetitive but, this is because this is not a real world example. Those cases could do very different things, completely different. That's the whole power behind the menu. The case will only be hit by the menu that corresponds and the case knows the exact value of the chosen option (you could make more cases). consider:

      menuThree:
      set opt=%1
      if "%opt"=="AaA" do Something
      if "%opt"=="BaB" do Something
      if "%opt"=="CaC" do Something
      etc...

      You could build an entire text option game with this one menu concept.

      Bonus @echo , echo. , echo

      @echo on/off (turns on/off constant echo)
      echo. -> echoes regardless of @echo status
      echo -> echoes only if @echo on, otherwise prints "echo is off"

      I also wanted to post a no-comment version. I don't know about all programmers but personally, I like uncommented code versions. I can read the commented one once or twice and then run the uncommented one in my head. With the exception of QC. QC is usually commented to the side, which is not as distracting.

      Code:
      @echo off
      
      :main
        set menus="menuOne;menuTwo;menuThree"
        setlocal EnableDelayedExpansion
        call :menu_from_file_switch %menus%
        goto end
        
      :menu_from_file_switch
        set case=%1
        for /f "tokens=1* delims=;" %%a in (%case%) do (
          call :menu_from_file %%a
          if not "%%b"=="" call :menu_from_file_switch "%%b"
          if not "%%b"=="" exit /b
        )
        endlocal
        exit /b
      
      :menu_from_file
        set list_path=%1
        set pass=0
        set "map="
        set /a max=0
        
        echo.
        echo.%list_path%
        
        for /f "delims=" %%i in (%list_path%) do (
          set /a max=max+1
      	if not "!map!"=="" (set "map=!map!;!max!-%%i") else set "map=!max!-%%i"
      	echo.!max!. %%i
        )
        
        set /p choice=Choose an option: 
        if not !choice! gtr !max! if not !choice!==0 set pass=1
        if !pass! equ 0 echo.!choice! is not a valid option
        if !pass! equ 0 goto menu_from_file %list_path%
        if !pass! equ 1 call set choice=%%map:*%choice%-=%%
        if !pass! equ 1 set value=%choice:;=&rem.%
        if !pass! equ 1 if "%list_path%"=="menuOne" call :one %value%
        if !pass! equ 1 if "%list_path%"=="menuTwo" call :two %value%
        if !pass! equ 1 if "%list_path%"=="menuThree" call :three %value%
        goto :eof
      
      :one
         set opt=%1
         echo.menuOne: You chose %opt%
         echo.
         exit /b
      :two
         set opt=%1
         echo.menuTwo: You chose %opt%
         echo.
         exit /b
      :three
         set opt=%1
         echo.menuThree: You chose %opt%
         echo.
         exit /b
      
      :end
        pause
        exit
      Last edited by MadGypsy; 04-02-2015, 06:28 PM.
      http://www.nextgenquake.com

      Comment


      • #33


        hah, look at the results of my little experiment. The main.bat file creates a var, passes it by reference to a remote bat, and then echoes successful results. This happens without the called bat (funcs.bat) ever opening visually.

        My funcs.bat has a work-around for having to create cases for every possible function that may be included in the document. It relies on you providing the proper subroutine and then allows up to 7 arguments. Placeholder vars are used as reference and then the original references are reassigned the new values accordingly. This is probably not the best way to do this but, it works, and it's reusable. It also allows me to keep my script cleaner in the long run.

        this:

        set "ref1="
        call remote.bat ":label" ref1

        is as close as I can get to:

        public var ref1:SomeType = src.class.ExternalClass::labelAsFunc() as SomeType

        You may also notice that I have upgraded my batch scripting even more. I'm using a different method of delayed expansion, returning input, using remote scripts... I'm gonna have this language licked in another couple of days.
        Last edited by MadGypsy; 04-06-2015, 11:50 AM.
        http://www.nextgenquake.com

        Comment


        • #34
          Whoops, I realized the huge flaw in my last post. It's a write-only system. You can't actually read any of those arguments... lol. Oh well, back to the drawing board.
          http://www.nextgenquake.com

          Comment


          • #35
            Lol, I know nobody cares about this thread but, I am keeping it alive because I have not stopped working on "enginator". I have built more and more advanced versions of it about 4 or 5 times now. They all work and they all do almost the same thing. The difference between them is the sophistication of the code. My current level is a bit like treating remote batch scripts like classes (as much as possible). I think this is the last rewrite cause, the code is finally resembling how I would program in a more OOP friendly language. I've got abstraction, encap, inheritance... probably more stuff but, that's what I can think of off the top.

            Some of you may wonder why I have put so much energy into this. Well, it hasn't really been that much energy, an hour or two every night since my first post and two (maybe 3) entire days. That's time learning and implementing.

            My focus on this is because this language is very different from the grand majority of languages that I know. It's taken me 2 weeks to figure out how to apply advanced programming concepts to this language. Batch script is not your friend. It is a bitch. Look at the most bare bones structure for a function

            Code:
            set "reference_var="
            
            call :template reference_var
            
            :template
              set_local
                set "return_value="
                //do work that ultimately sets return_value
              (endlocal
                set "%~1=%return_value%"
                goto :eof
              )
            Just this simple process has so much going on that it's suffocating.

            A var is passed by reference to a subroutine with no argument interface. A value within a local block (contained vars expire) is assigned. Delayed Expansion is raped from the endlocal to maintain the dynamically created value and assign it to the reference that was initially passed.

            Psssh... all that just for a returnable function. This is not the work around. This is how you do it.

            note: The (endlocal stuff) above is the fancy way of putting all the code on one line without it actually being on one line
            Code:
              these are the same thing
            
              (endlocal
                set "%~1=%return_value%"
                goto :eof
              )
            
              endlocal&set "%~1=%return_value%"&goto :eof
            Anyway, I have given this language time and energy because it is so different. I program in a bunch of languages because this is what I do, learn them. VBScript has me interested on the same level batch scripting does. It's syntax needs therapy.

            EDIT: Earlier I said "They all work and they all do almost the same thing." I meant how they internally work. My latest version of enginator is beast far more ellaborate than any version before it. It does EVERY fucking thing. You put engines in the engines folder and games in the games folder, start enginator. That's it. No renaming anything. Nothing confusing. The program holds your hand almost the entire way. About the only way you could fuck up is to type a mapname that doesn't exist or include an invalid switch in one of the command line files. The map thing is semi fixed cause, if your maps folders are un-paked (at least upon first run/install), my app will make a list of all available maps (without including bsp models). That list becomes a number choice menu. The engines and games choices already work this way. This means only cmd line switches allow unverified user input and there is no way around that really. I'm not writing cmd line switch profiles for every possible engine.
            Last edited by MadGypsy; 04-07-2015, 10:24 AM.
            http://www.nextgenquake.com

            Comment


            • #36


              Doesn't look like much, does it? Psssh, this menu is elite as fuck for batch script.

              Features:

              Create a menu from directories, specific filetypes and/or file contents
              All input (number or "manual") is checked within context for legitimacy (existence)
              All invalid input is discarded and the menu refreshes, allowing you to try again.
              Choose items by number or use 0 option and input them manually
              Automatic pagination of long lists

              That's the basic menu class description but, implementation is as simple as one line of code. You can even chain menus to create some pretty powerful menu systems. Here is the line of code that implemented the menu in the image.

              Code:
              call path\menus.cmd "contents" "Choose_A_Map:" "path\id1.lst"
              That's it. That's all it takes to build completed menus with all the bells and whistles, utilizing my menu script.


              For anybody that actually reads the code, here is the source to my menu class.

              menus.cmd
              Code:
              @echo off
              
              REM # Create a list...
              REM - of specific files in a path 
              REM - from the contents of a file
              REM - from the folders in a directory
              REM - from a range of map values
              
              REM # Automatically paginate long lists
              REM - pagination options only appear if max list options exceed page length
              REM - "-" & "+" keys paginate list
              REM - pagination options only display currently possible paging directions
              
              REM # Allow 0 option
              REM - option to input the value of your number choice manually
              REM - input is compared in context for existance
              
              :constructor
                SetLocal
                  ::establish that everything is in order
                  if not "%~1"=="" (set "case=%~1")    else goto :case_error "constructor"
                  if not "%~2"=="" (set "title=%~2")   else set "title=Menu:"
                  if not "%~3"=="" (set "pointer=%~3") else goto pointer_error "constructor"
                  if not "%~4"=="" (set "arg1=%~4")    else set "arg1=*"
                  if not "%~5"=="" set "max=%~5"
                  
                  call :menu_header %title
                  
                  ::set valid menu selection switches
                  set "access=0"
                  set "pagination=0"
                  set "page_prev=0"
                  set "page_next=0"
                  
                  ::set page length
                  set "page=9"
                  
                  ::set the current maximum viewed list number to 0
                  if "%case%"=="range_map" (set "cmax=%arg1%") else set "cmax=%page%"
                  
                  ::include manual input in the list
                  echo. 0. manual input
                  
                  ::prints an ordered list, returns %map% of the list, returns %max% of the maximum list number
                  if "%case%"=="contents"  call :content_list %pointer%
                  if "%case%"=="directory" call :directory_list %pointer%
                  if "%case%"=="files"     call :file_list %pointer% %arg1%
                  
                  ::returns %cmax% instead of max and doesn't return a map
                  if "%case%"=="range_map" call :range_map_list %pointer% %arg1% 
                  
                  ::pagination options
                  if %max% gtr %page% set "pagination=1"
                  if %pagination% equ 1 if %cmax% gtr %page% set "page_prev=1"
                  if %pagination% equ 1 if %cmax% lss %max% set "page_next=1"
                  if %pagination% equ 1 echo.
                  
                  if %pagination% equ 1 echo. page options:
                  if %page_prev% equ 1 echo. - previous
                  if %page_next% equ 1 echo. + next
                  if %pagination% equ 1 echo.
                  
                  ::allow the user to select a number
                  set /p "choice=  >" 
                  
                  ::catch empty input
                  if "%choice%"=="" set "choice=0"
                  
                  set /a "diff=max%%page"
                  set /a "prev=(cmax-(page*2))+1"
                  if not %cmax% lss %max% set /a "prev=(max-(page+diff))+1"
                  if %prev% lss 1 set "prev=1"
                  
                  if %page_prev% equ 1 if "%choice%"=="-" set /a "arg1=prev"&set "access=3"
                  if %page_next% equ 1 if "%choice%"=="+" set /a "arg1=cmax+1"&set "access=3"
                  
                  if not %access% equ 3 if not %choice% gtr %max% set "access=1"
                  if %choice% equ 0 if %access% equ 1 set "access=2"
                  
                  ::if the choice was 
                  if %access% equ 3 call :constructor "range_map" %title% %map% %arg1% %max%
                  if %access% equ 3 set "parse_map=%value%"
                  
                  ::if the choice was 
                  if %access% equ 2 call :user_input %map% %choice%
                  if %access% equ 2 set "parse_map=%user_input%"
                  
                  ::returns %parse_map% as the value of the chosen number 
                  if %access% equ 1 call :parse_map %map% "%choice%"
                  
                  ::if the choice was not valid, call this constructor again and pass it's return value to this instances %parse_map%
                  if %access% equ 0 echo.
                  if %access% equ 0 echo.%choice% is not a valid selection
                  if %access% equ 0 pause&call :constructor %case% %title% %pointer% %arg1% %max%
                  if %access% equ 0 set "parse_map=%value%"
                  
                EndLocal&(
                  set "value=%parse_map%"
                )&goto :eof
                
              REM ---Various methods of looping remote data to create lists
              
              :content_list
                ::expansion is enabled here in order for i to increment
                SetLocal EnableDelayedExpansion
                  set /a i=0
              	
                  ::get every line from the file
                  for /f "delims=" %%d in (%~1) do (
                    set /a i=i+1
                    ::build a map of number value pairs, where the value is the current line of the file
                    if not "!return_map!"=="" (set "return_map=!return_map!+!i!:%%d") else set "return_map=!i!:%%d"
                    ::align and print list
                    if not !i! gtr %page% echo. !i!. %%d
                  )
                  echo.
                EndLocal&(
                  set "map=%return_map%"
                  set "max=%i%"
                )&goto :eof
                
              :file_list
                ::expansion is enabled here in order for i to increment
                SetLocal EnableDelayedExpansion
                  set /a i=0
              	
                  ::get every file of a specific type within a directory
                  for %%d in (%~1\*.%~2) do (
                    set /a i=i+1
                    ::build a map of number value pairs, where the value is the current filename.ext
                    if not "!return_map!"=="" (set "return_map=!return_map!+!i!:%%~nxd") else set "return_map=!i!:%%~nxd"
                    ::align and print list
                    if not !i! gtr %page% echo. !i!. %%~nd
                  )
                  echo.
                EndLocal&(
                  set "map=%return_map%"
                  set "max=%i%"
                )&goto :eof
               
              :directory_list
                SetLocal EnableDelayedExpansion
                  set /a i=0
              	
                  ::get every directory within a directory
                  for /d %%d in (%~1\*.*) do (
                    set /a i=i+1
                    ::build a map of number value pairs, where the value is the current filename.ext
                    if not "!return_map!"=="" (set "return_map=!return_map!+!i!:%%~nd") else set "return_map=!i!:%%~nd"
                    ::align and print list
                    if not !i! gtr %page% echo. !i!. %%~nd
                  )
                  echo.
                EndLocal&(
                  set "map=%return_map%"
                  set "max=%i%"
                )&goto :eof
                  
              :range_map_list
                SetLocal EnableDelayedExpansion
                  set /a "i=0"
                  set /a "e=%~2+(page-1)"
                  ::get a range of options from a map
                  for /l %%d in (%~2,1,%e%) do (
                    if not %%d gtr %max% call :parse_map "%~1" %%d
                    ::if not %%d gtr %max% set "parse_map=%parse_map:.=&rem.%"
                    if not %%d gtr %max% echo. %%d. !parse_map!
                    if not %%d gtr %max% set "i=%%d"
                  )
                  echo.
                EndLocal&(
                  set "map=%~1"
                  set "cmax=%i%"
                )&goto :eof
                
              REM ---Parse the value of a selected number from a map of choices
              
              :parse_map
                SetLocal
                  ::establish that everything is in order
                  if not "%~1"=="" (set "m=%~1") else goto :map_error %0
                  if not "%~2"=="" (set "i=%~2") else goto :case_error %0
              	
                  ::parse the value of the selected number from the map
                  call set return=%%m:*%i%:=%%
                  set return=%return:+=&rem.%
              	
                EndLocal&(
                  set "parse_map=%return%"
                )&goto :eof
                
              :parse_input
                SetLocal
                  set "return=0"
              	
                  ::establish that everything is in order
                  if not "%~1"=="" (set "m=%~1") else goto map_error %0
                  if not "%~2"=="" (set "c=%~2") else goto :case_error %0
              	
                  ::parse the value of the selected number from the map
                  call set temp=%%m:*:%c%+=%%
                  if not "%temp%"=="%m%" set "return=1"
              	
                EndLocal&(
                  set "parse_input=%return%
                )&goto :eof
                
              REM ---Switch to user input mode
              
              :user_input
                SetLocal
                  echo.
                  echo. Type the name of your selection:
                  set /p "return=>"
              
                  call :parse_input "%~1" "%return%"
                  if %parse_input% equ 0 set "return="	
                EndLocal&(
                  set "user_input=%return%
                )&goto :eof
                
              REM ---Simple menu header
                
              :menu_header
                set t=%title:_= %
                cls
                echo.
                echo. %t%
                echo.
                goto :eof
              
              REM ---Various errors
              
              :case_error
                echo.menu.cmd::%~1 
                echo.A case has not been supplied
                pause&goto :eof
               
              :map_error
                echo.menu.cmd::%~1
                echo.A map has not been supplied
                pause&goto :eof
              
              :pointer_error
                echo.menu.cmd::%~1
                echo.You have not supplied a pointer to the file or folder that you want to access
                pause&goto :eof
              Last edited by MadGypsy; 04-09-2015, 05:49 PM.
              http://www.nextgenquake.com

              Comment


              • #37
                I finished this project a while back. It has the following features:

                1) Load any map in any engine (except engineX)
                2) Load any demo in any engine (except engineX)
                3) Run "no-map" mods (like smc) from a game folder of it's own. For instance you do not need to put RogueSMC in your Rogue folder. It can be put in some other folder and run using Rogue as a "basegame"
                4) Paks are searched and archived for maps and demos, meaning if you choose a game to run, you are automatically presented with all the maps and/or demos for that game as a list of choices. In the case of "no-map" mods (like smc) you are presented with a list based on the "basegame" for that mod
                5) all configs (regardless of what they are named) get juggled in the background according to the engine you choose.
                6) you are presented with the final commandline that is created based on all of your choices and you can further modify this manually before running the game
                7) an entire profile system which allows you to save/load profiles based on choices you have made regarding a specific game. So let's say you chose darkplaces, rogue, R1m1, some other commandline options, etc... you can save that profile and bring it all back just as you selected it, with one click
                a simple .cfg editor, really this is not even as robust as notepad BUT it brings up the config for the engine you selected. So, instead of looking around for configs, you simply select the engine you want to use and the .cfg that appears will be the one for that engine.

                There are more and more options but these are the most powerful. All engines get stored in an engines directory and all games get stored in a game directory. Unlike how the normal system works, where you would need a game directory in every engine directory, my method separates all of the coupling and repetition of the standard method. All engines and games get recorded and presented to you in a selectable list format. Basically you can load any game/demo in any engine with little more than a few clicks. My system does not work with engineX for reasons that must be engine related. I have loaded games with every engine you can imagine, with no issues, except engineX. Even old engines work.

                My app uses NO batch files. It is windows only but the entire app was built using vbscript, jquery/javascript, html, css & a couple of 3rd party .exe tools. The third party tools just do 2 things. One of them searches all paks for maps, demos and records their filenames, the other makes the xml database that is created pretty (humanly readable). The app is wrapped in an .HTA and runs as if it was an .exe (ie... not in your browser).

                The original app was named AQuake, then it was changed to Dark_Roast (based on the color theme of the app and Dark & Roast being words that can apply to Quake). The final App name is Dark Arts, has an entirely new theme and all the code has been rewritten from the ground up to be clean, optimized and (hopefully) glitch free.

                I would share this app, but I am not posting one more piece of my work here til I get my account back. If I never get it back,... I will never share this. I am not giving away more of my efforts while I am locked out of 4 years worth of them.
                Last edited by TheRealMadGypsy; 06-23-2015, 07:42 PM.

                Comment

                Working...
                X