Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

Text-Adventure Engine

Discussion in 'The Lab' started by Zwiebelchen, Oct 28, 2015.

  1. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    7,014
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    Here's a new thing I'm working on...

    Basicly, it's a game engine based on a node system with multiple-choice dialogue intended for rogue-like or text-adventure games.

    The idea behind this was to use .TGA as highres image backdrops which can be assigned on a per-scene base. The backdrop will remain active until a new backdrop information is called by a scene.

    Then I have actors which will serve as objects that can be placed dynamically over the backdrop. Those will also be images, however, also have a dummy unit associated with them that can be accessed to display a portrait.


    Code (vJASS):
    library TextAdventure initializer init uses ArrowKeyEvent, ImageTools

    /*
      requires ArrowKeyEvent by Bribe, which can be found here:
      [url]http://www.hiveworkshop.com/forums/jass-resources-412/system-arrowkeyevent-205650/[/url]
      requires ImageTools, which can be found here:
      [url]http://www.hiveworkshop.com/forums/submissions-414/imagetools-271099/[/url]
      */


    globals
          private constant real BACKGROUND_RESOLUTION = 1024
          private constant real BACKGROUND_X = 0
          private constant real BACKGROUND_Y = 0
          private constant real CAM_DISTANCE = 1000
          private constant real CAM_ANGLE = 270

          private constant string PRESELECT_SOUND = "Sound\\Interface\\PickUpItem.wav"
          private constant string SELECT_SOUND = "Sound\\Interface\\GamePause.wav"
         
          private boolean IsEngineEnabled = false
          private constant integer DUMMY_RAW = 'hfoo'
          private real DUMMY_X = 0
          private real DUMMY_Y = 0
          private unit DUMMY
    endglobals


    private function EnableCinematicMode takes nothing returns nothing
         local real x = BACKGROUND_X+BACKGROUND_RESOLUTION/2
         local real y = BACKGROUND_Y+BACKGROUND_RESOLUTION/2
         if not IsEngineEnabled then
             //create a dummy unit to fix an annoying bug in WC3 in which the first image created is bugged
             set DUMMY = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_RAW, DUMMY_X, DUMMY_Y, 0)
             call PauseUnit(DUMMY, true)
             call UnitAddAbility(DUMMY, 'Aloc')
             call ShowUnit(DUMMY, false)
             set IsEngineEnabled = true
             call CinematicModeExBJ(true, GetForceOfPlayer(GetLocalPlayer()), 0)
             call EnableUserControl(true)
             call SetCameraPosition(x, y)
             call SetCameraBounds(x, y, x, y, x, y, x, y)
             call SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK, CAM_ANGLE, 0)
             call SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, CAM_DISTANCE, 0)
         endif
    endfunction

    private function DisableCinematicMode takes nothing returns nothing
         local real x1
         local real x2
         local real y1
         local real y2
         if IsEngineEnabled then
             call RemoveUnit(DUMMY)
             set x1 = GetRectMinX(bj_mapInitialPlayableArea)
             set x2 = GetRectMaxX(bj_mapInitialPlayableArea)
             set y1 = GetRectMinY(bj_mapInitialPlayableArea)
             set y2 = GetRectMaxY(bj_mapInitialPlayableArea)
             call CinematicModeExBJ(false, GetForceOfPlayer(GetLocalPlayer()), 0)
             call SetCameraBounds(x1, y1, x1, y2, x2, y2, x2, y1)
             call ResetToGameCamera(0)
             set IsEngineEnabled = false
         endif
    endfunction


    struct Actor
          integer portrait
          image picture
          string picturepath
          boolean show
          string title
         
          private real x
          private real y
          private real z
          private real sizeX
          private real sizeY

          static method create takes string name, string imagefile, integer portraitraw, real X, real Y, real Z, real SizeX, real SizeY returns thistype
              local thistype this = thistype.allocate()
              set this.portrait = portraitraw
              set this.picturepath = imagefile
              set this.picture = null
              set this.title = name
              set this.show = false
              set this.x = X
              set this.y = Y
              set this.z = Z
              set this.sizeX = SizeX
              set this.sizeY = SizeY
              return this
          endmethod

          method draw takes boolean display returns nothing
              if display then
                  if this.picture != null then
                     call ReleaseImage(this.picture)
                     if this.picturepath != "" then
                         set this.picture = NewImage(this.picturepath, this.sizeX, this.sizeY, BACKGROUND_X+this.x, BACKGROUND_Y+this.y, this.z, 2)
                         call SetImageRenderAlways(this.picture, true)
                     else
                         set this.picture = null
                     endif
                  else
                     set this.picture = NewImage(this.picturepath, this.sizeX, this.sizeY, BACKGROUND_X+this.x, BACKGROUND_Y+this.y, this.z, 2)
                     call SetImageRenderAlways(this.picture, true)
                  endif
              else
                  if this.picture != null then
                     call ReleaseImage(this.picture)
                     set this.picture = null
                  endif
              endif
          endmethod

          method setPosition takes real X, real Y, real Z returns nothing
              set this.x = X
              set this.y = Y
              set this.z = Z
          endmethod
         
          method onDestroy takes nothing returns nothing
              if this.picture != null then
                 call ReleaseImage(this.picture)
              endif
              set this.picture = null
              set this.picturepath = ""
              set this.portrait = 0
              set this.title = ""
          endmethod
    endstruct


    private struct Node
          static thistype current = 0
          static integer selected = 0

          stub method draw takes nothing returns nothing
          endmethod
    endstruct


    struct Scene extends Node
          static thistype lastCreated = 0
          string txt
          Node nxt
          Actor act
          static image lastBackground = null
         
          private static thistype previous = 0
          private Actor array acts[4]
          private boolean clear
          private sound audio
          private boolean audiocutoff
          private image background
          private string backgroundpath
          private boolean clearbackground
          private trigger ondraw
          private triggeraction ondrawact
          private static Actor array lastAct[4]
         
          static method add takes string text, Actor talker returns thistype
              local thistype t = thistype.create()
              set thistype.lastCreated = t
              set t.txt = text
              set t.nxt = 0
              set t.act = talker
              set t.audio = null
              set t.audiocutoff = false
              set t.background = null
              set t.backgroundpath = ""
              set t.clearbackground = false
              set t.acts[0] = 0
              set t.acts[1] = 0
              set t.acts[2] = 0
              set t.acts[3] = 0
              set t.clear = false
              set t.ondraw = null
              set t.ondrawact = null
              return t
          endmethod
         
          method link takes Node next returns thistype
              set this.nxt = next
              return this
          endmethod

          method setActors takes Actor first, Actor second, Actor third, Actor fourth returns nothing
              set this.acts[0] = first
              set this.acts[1] = second
              set this.acts[2] = third
              set this.acts[3] = fourth
              set this.clear = true
          endmethod
         
          method registerOnDraw takes code func returns nothing
              if this.ondraw != null then
                 call TriggerRemoveAction(this.ondraw, this.ondrawact)
              else
                 set this.ondraw = CreateTrigger()
              endif
              set this.ondrawact = TriggerAddAction(this.ondraw, func)
          endmethod

          method setBackground takes string path returns nothing
              set this.backgroundpath = path
              set this.clearbackground = true
          endmethod

          method setAudio takes string path, boolean stopWhenSkipped, integer volume returns nothing
              set this.audio = CreateSound(path, false, false, false, 0, 0, "DefaultEAXON")
              call SetSoundVolume(this.audio, volume)
              set this.audiocutoff = stopWhenSkipped
          endmethod

          method draw takes nothing returns nothing
              local integer i = 0

              call EnableCinematicMode()

              set Node.current = this

              if this.act != 0 then
                  call SetCinematicScene(this.act.portrait, ConvertPlayerColor(GetPlayerId(GetLocalPlayer())), this.act.title, this.txt, 1000, 0)
              else
                  call SetCinematicScene(0, ConvertPlayerColor(GetPlayerId(GetLocalPlayer())), "", this.txt, 1000, 0)
              endif

              if this.clear then
                  loop
                      exitwhen i >= 4
                      if thistype.lastAct[i] != 0 then
                          call thistype.lastAct[i].draw(false)
                      endif
                      if this.acts[i] != 0 then
                          call this.acts[i].draw(true)
                          set thistype.lastAct[i] = this.acts[i]
                      endif
                      set i = i + 1
                  endloop
              endif
             
              if this.clearbackground and thistype.lastBackground != null and thistype.lastBackground != this.background then
                 call ReleaseImage(thistype.lastBackground)
                 set thistype.lastBackground = null
              endif
              if this.backgroundpath != "" then
                  if this.background != null then
                     call ReleaseImage(this.background)
                  endif
                  set this.background = NewImage(this.backgroundpath, BACKGROUND_RESOLUTION, BACKGROUND_RESOLUTION, BACKGROUND_X, BACKGROUND_Y, 0, 2)
                  call SetImageRenderAlways(this.background, true)
                  set thistype.lastBackground = this.background
              endif

              if this.previous.audio != null and this.previous.audiocutoff then
                  if GetSoundIsPlaying(this.previous.audio) or GetSoundIsLoading(this.previous.audio) then
                      call StopSound(this.previous.audio, false, true)
                  endif
              endif
              if this.audio != null then
                  call StartSound(this.audio)
              endif
              if this.ondraw != null then
                  call TriggerExecute(this.ondraw)
              endif
          endmethod
         
          method onDestroy takes nothing returns nothing
             set this.txt = ""
             set this.nxt = 0
             set this.act = 0
             set this.acts[0] = 0
             set this.acts[1] = 0
             set this.acts[2] = 0
             set this.acts[3] = 0
             set this.clear = false
             if this.audio != null then
                 call KillSoundWhenDone(this.audio)
             endif
             set this.audio = null
             set this.background = null
             if this.ondraw != null then
                 call TriggerRemoveAction(this.ondraw, this.ondrawact)
                 call DestroyTrigger(this.ondraw)
                 set this.ondraw = null
                 set this.ondrawact = null
             endif
          endmethod
    endstruct

    struct Choice extends Node
          Scene array choices[4]
         
          static Scene parent = 0
          static constant sound SELECT = CreateSound(SELECT_SOUND, false, false, true, 12700, 12700, "")
          static constant sound PRESELECT = CreateSound(PRESELECT_SOUND, false, false, true, 12700, 12700, "")
         
          method link takes Scene first, Scene second, Scene third, Scene fourth returns thistype
              set this.choices[0] = first
              set this.choices[1] = second
              set this.choices[2] = third
              set this.choices[3] = fourth
              return this
          endmethod

          method draw takes nothing returns nothing
              local integer i = 0
              local string new = ""
              set Node.current = this
              set new = thistype.parent.txt + "\n"
              loop
                  exitwhen i >= 4
                  if this.choices[i] != 0 then
                      if this.selected == i then
                          set new = new + "\n|cffffcc00> " + this.choices[i].txt + "|r"
                      else
                          set new = new + "\n   " + this.choices[i].txt
                      endif
                  endif
                  set i = i + 1
              endloop

              if this.parent.act != 0 then
                  call SetCinematicScene(this.parent.act.portrait, ConvertPlayerColor(GetPlayerId(GetLocalPlayer())), this.parent.act.title, new, 1000, 0)
              else
                  call SetCinematicScene(0, ConvertPlayerColor(GetPlayerId(GetLocalPlayer())), "", new, 1000, 0)
              endif
             
              set new = ""
          endmethod
         
          method onDestroy takes nothing returns nothing
              set this.choices[0] = 0
              set this.choices[1] = 0
              set this.choices[2] = 0
              set this.choices[3] = 0
          endmethod
    endstruct


    //=================================================

    private function enter takes nothing returns nothing
          local Scene s
          local Choice c
          if IsEventArrowKeyPressed() then
              if GetEventArrowKey() == ARROW_KEY_RIGHT then
                  if Node.current != 0 then
                      if Node.current.getType() == Scene.typeid then
                          set s = Node.current
                          if s.nxt != 0 then
                              if s.nxt.getType() == Scene.typeid then
                                 call s.nxt.draw()
                              elseif s.nxt.getType() == Choice.typeid then
                                  set c = s.nxt
                                  set Choice.selected = 0
                                  set Choice.parent = s
                                  call c.draw()
                              endif
                          else
                              set Choice.selected = 0
                              set Choice.parent = 0
                              set Node.current = 0
                              call DisableCinematicMode()
                          endif
                      elseif Node.current.getType() == Choice.typeid then
                          set c = Node.current
                          set s = c.choices[Choice.selected]
                          if s != 0 then
                              call s.nxt.draw()
                              call StartSound(Choice.SELECT)
                          else
                              set Choice.selected = 0
                              set Choice.parent = 0
                              set Node.current = 0
                              call DisableCinematicMode()
                          endif
                      endif
                  endif
              elseif GetEventArrowKey() == ARROW_KEY_DOWN then
                  if Node.current.getType() == Choice.typeid then
                      set c = Node.current
                      if Choice.selected < 3 and c.choices[Choice.selected+1] != 0 then
                          set Choice.selected = Choice.selected + 1
                          call StartSound(Choice.PRESELECT)
                      endif
                      call c.draw()
                  endif
              elseif GetEventArrowKey() == ARROW_KEY_UP then
                  if Node.current.getType() == Choice.typeid then
                      set c = Node.current
                      if Choice.selected > 0 then
                          set Choice.selected = Choice.selected - 1
                          call StartSound(Choice.PRESELECT)
                      endif
                      call c.draw()
                  endif
              endif
         endif
    endfunction

    //=================================================

    private function init takes nothing returns nothing
         call RegisterArrowKeyEvent(function enter)
    endfunction

    endlibrary


    It is not MPI by design (who would want to play multiplayer adventures anyway?).
     
    Last edited: Nov 4, 2015
  2. BPower

    BPower

    Joined:
    Mar 18, 2012
    Messages:
    1,709
    Resources:
    21
    Spells:
    15
    Tutorials:
    1
    JASS:
    5
    Resources:
    21
    Use my ImageTools library to create/destroy image handles. It's very lightweight.
    Image handles can easily crash your game, if something is not set correct.
     
  3. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    7,014
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    Thanks, nice suggestion. I never experienced any issues with images, but I can see how it's useful to avoid crashes if a user enters an incorrect path. I'll add it.

    The interest in this is pretty limited so far. :p
    I see if I can finish it on weekend, I'm curious to code a small text adventure.
     
  4. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,429
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    I suppose a test map is in order to show how awesome this can be! These sorts of systems get hype through demos. I can definitely see potential (maybe even as a 2D engine if expanded, something like this). I think it is a pretty cool idea regardless, and it could be great for Zelda-like dialogue. It is a pretty nice way to get story across without making the user feel like they're reading a wall of text, it even received some success in WoW with this popular addon.
     
  5. Spellbound

    Spellbound

    Joined:
    Jan 9, 2005
    Messages:
    2,028
    Resources:
    18
    Models:
    1
    Icons:
    1
    Skins:
    5
    Maps:
    1
    Spells:
    9
    JASS:
    1
    Resources:
    18
    Lol inspired by the 'Let's make a map' thread? xD I'm curious to see where this can take the project.
     
  6. Chaosy

    Chaosy

    Tutorial Reviewer

    Joined:
    Jun 9, 2011
    Messages:
    11,067
    Resources:
    18
    Icons:
    1
    Maps:
    1
    Spells:
    10
    Tutorials:
    6
    Resources:
    18
    Can you upload a test map? I have problems understanding what exactly this is without either that or screenshots.
     
  7. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    7,014
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    I will upload a testmap once I finished it and made the required model dummy in magos.
     
  8. Nestharus

    Nestharus

    Joined:
    Jul 10, 2007
    Messages:
    6,146
    Resources:
    8
    Spells:
    3
    Tutorials:
    4
    JASS:
    1
    Resources:
    8
    This looks awesome except for the fact that it is Singleton : |.

    I don't know that gifs work, so it'd be pretty awesome if you could support like an animated background scene and transitions ^_^. Also, layering on top of scenes. For example, if a character is speaking, you can show them on top of the scene with a dialog box.
     
  9. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    7,014
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    You can set the z height of images, so yes, proper layering of actors is possible.

    However, as I wanted that portrait thing (and also use the bottom of the screen in letterbox mode) I planned using transmissions instead of texttags... so no speech bubbles (for now).

    But even with the current tools, it is very possible to create a rogue-like dungeoncrawler with a randomly generated dungeon or a run-of-the-mill dating sim. :p


    No gif support in WC3, though. But I could add a "sequencer" feature that allows cycling through a set amount of images for actors to effectively build your own gif out of an array of tga files.


    EDIT:
    Hmm, I just noticed that scenes should probably also fire an "onDraw" function, so that users can set variables when a certain scene is triggered.
     
  10. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,429
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    pls make a dating sim for the test map it would be glorious
     
  11. Chaosy

    Chaosy

    Tutorial Reviewer

    Joined:
    Jun 9, 2011
    Messages:
    11,067
    Resources:
    18
    Icons:
    1
    Maps:
    1
    Spells:
    10
    Tutorials:
    6
    Resources:
    18
    Don't give him ideas, lol.
     
  12. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    7,014
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    Glorious would probably not be the right word. I'd say it would be "Weird" with a capital W.
     
  13. Kino

    Kino

    Joined:
    Oct 12, 2008
    Messages:
    1,714
    Resources:
    19
    Models:
    5
    Maps:
    14
    Resources:
    19
    Madlibs: Warcraft Edition.
     
  14. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    7,014
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    EDIT:

    Alright guys... bugtested this and the demo map is finished aswell.

    Have a look! The API looks a bit clunky at first, but it's actually super convenient once you get the gist of it with all the cool method nesting.
    Maybe I'll write a tutorial on this for the GUI users.

    Damn convenient API

    Code (vJASS):

    globals
         Scene array scenes
         Choice array choices
    endglobals

    function OnDrawKitchen takes nothing returns nothing
         set scenes[1].txt = "The shack still looks wooden. You find that remarkable. What will you do?"
         set scenes[2].txt = "Enter the kitchen"
    endfunction

    function OnDrawBedroom takes nothing returns nothing
         set scenes[1].txt = "The shack still looks wooden. You find that remarkable. What will you do?"
         set scenes[3].txt = "Enter the bedroom"
    endfunction

    function Trig_Test_Actions takes nothing returns nothing
         local Actor testchar = Actor.create("Kitchen Girl", "soyoudontlikecake.tga", 'h000', 300, 200, 10, 512, 512)
         set choices[0] = Choice.create()
         set choices[1] = Choice.create()
         set choices[2] = Choice.create()
         set choices[3] = Choice.create()
         set choices[4] = Choice.create()
         set choices[5] = Choice.create()
         
         //main hall
         set scenes[0] = Scene.add("Welcome, generic adventure protagonist!", 0)
         set scenes[1] = Scene.add("You just woke up in a shack hallway for absolutely no reason at all. What do?", 0)
         call scenes[1].setActors(0, 0, 0, 0) //many scenes link back to this scene, so make sure we destroy all actors here so they don't carry over
         call scenes[1].setBackground("palace.tga") //display the first background
         set scenes[2] = Scene.add("Enter the room on the right", 0)
         set scenes[3] = Scene.add("Enter the room on the left", 0)
         
         
         //kitchen
         set scenes[4] = Scene.add("It seems to be a kitchen.\nAnd for some reason the artstyle changed. But that is just because Zwiebelchen was too lazy to look for consistent assets.", 0)
         call scenes[4].registerOnDraw(function OnDrawKitchen) //register a function that changes some text nodes in the hallway
         call scenes[4].setBackground("kitchen.tga")
         set scenes[5] = Scene.add("You look around and see nothing of value, except maybe for a delicious looking cake on the table.", 0)
         set scenes[6] = Scene.add("So what now, generic adventure protagonist?", 0)
         set scenes[7] = Scene.add("Eat the cake to find out if it was a lie", 0)
         set scenes[8] = Scene.add("Back out and be a sissy", 0)
         set scenes[9] = Scene.add("OM NOM NOM NOM.", 0)
         set scenes[10] = Scene.add("(You hear the sound of footsteps behind your sexy back)", 0)
         set scenes[11] = Scene.add("Hey, meatbag, did you just eat my cake?", testchar) //adding the actor as argument will display the portrait
         call scenes[11].setActors(testchar, 0, 0, 0) //display alice on screen aswell
         set scenes[12] = Scene.add("Err... no?", 0)
         set scenes[13] = Scene.add("Nice apron...!", 0)
         set scenes[14] = Scene.add("Hey there, lady with a knife. Let me just hit on you because I totally disregard life!", 0)
         set scenes[15] = Scene.add("Wrong. Fucking. Answer.", testchar)
         set scenes[16] = Scene.add("|cffff0000Congratulations! You got brutaly murdered.|r\nGood thing that videogame protagonists respawn, right?", 0)
         call scenes[16].setActors(0, 0, 0, 0)
         call scenes[16].setBackground("")
         call scenes[16].setAudio("Units\\Human\\Priest\\PriestDeath.wav", false, 127)
         
         //bedroom
         set scenes[17] = Scene.add("It seems to be a generic bedroom.\nWith a chest. Gotta love dem chests!", 0)
         call scenes[17].registerOnDraw(function OnDrawBedroom) //register a function that changes some text nodes in the hallway
         call scenes[17].setBackground("bedroom.tga")
         set scenes[18] = Scene.add("So what to do with this beautiful chest?", 0)
         set scenes[19] = Scene.add("Stare at it", 0)
         set scenes[20] = Scene.add("Open it", 0)
         set scenes[21] = Scene.add("Moonwalk back into the hallway", 0)
         set scenes[22] = Scene.add("A girl materialized out of nowhere. What is happening?", 0)
         call scenes[22].setActors(testchar, 0, 0, 0)
         set scenes[23] = Scene.add("Did you just stare at my chest?", testchar)
         set scenes[24] = Scene.add("I still do!", 0)
         set scenes[25] = Scene.add("Hey there, random psychopath chick!", 0)
         
         set scenes[26] = Scene.add("A girl materialized out of nowhere. What is happening?", 0)
         call scenes[26].setActors(testchar, 0, 0, 0)
         set scenes[27] = Scene.add("Did you just touch my chest?", testchar)
         set scenes[28] = Scene.add("Can I?", 0)
         set scenes[29] = Scene.add("Double entendre! Engarde!", 0)
         
         
         //===================================================
         
         //connect nodes
         
         //main hall
         call scenes[0].link(scenes[1].link(choices[0])) //just a linear link towards the first choice
         call choices[0].link(scenes[2], scenes[3], 0, 0) //link the answer scenes to the choice
         call scenes[2].link(scenes[4]) //progress to kitchen
         call scenes[3].link(scenes[17]) //progress to bedroom
         
         //kitchen
         call scenes[4].link(scenes[5].link(scenes[6].link(choices[1])))
         call scenes[6].link(choices[1])
         call choices[1].link(scenes[7], scenes[8], 0, 0)
         call scenes[7].link(scenes[9].link(scenes[10].link(scenes[11].link(choices[2]))))
         call scenes[8].link(scenes[1])
         call choices[2].link(scenes[12], scenes[13], scenes[14], 0)
         call scenes[12].link(scenes[15].link(scenes[16]))
         call scenes[13].link(scenes[15]) //We already linked scene 15 with scene 16, so we don't have to do that again
         call scenes[14].link(scenes[15])
         call scenes[16].link(scenes[0]) //We died, so reset the game
         
         //bedroom
         call scenes[17].link(scenes[18].link(choices[3].link(scenes[19], scenes[20], scenes[21].link(scenes[1]), 0))) //let's step up our nesting game, shall we? Notice how you can link even inside choice brackets?
         call scenes[19].link(scenes[22].link(scenes[23].link(choices[4].link(scenes[24], scenes[25], 0, 0))))
         call scenes[24].link(scenes[15]) //see this? We can link back to the kill scene here without further changes, as the scene has no background directly associated with it
         call scenes[25].link(scenes[15])
         call scenes[20].link(scenes[26].link(scenes[27].link(choices[5].link(scenes[28].link(scenes[15]), scenes[29].link(scenes[15]), 0, 0)))) //amazing nesting!
         
         
         //use the draw method to start the first scene and start the sequence
         call scenes[0].draw()
       
       
         call DisableTrigger(gg_trg_Test)
    endfunction
     

    Attached Files:

    Last edited: Nov 4, 2015
  15. oger-lord

    oger-lord

    Joined:
    Jul 24, 2006
    Messages:
    156
    Resources:
    2
    Maps:
    1
    Tutorials:
    1
    Resources:
    2
    Is there any disadvantage of using meaningful variable names?
    It is difficult to understand the context with names scenes[0-29] and choices[0-6].

    Example

    Code (vJASS):

         //main hall
         set mainHall = Scene.add("Welcome, generic adventure protagonist!", 0)
         set mainHall_2 = Scene.add("You just woke up in a shack hallway for absolutely no reason at all. What do?", 0)
         call mainHall_2.setActors(0, 0, 0, 0) //many scenes link back to this scene, so make sure we destroy all actors here so they don't carry over
         call mainHall_2.setBackground("palace.tga") //display the first background
         
         set mainHall_choice = Choice.create()
         set mainHall_enterRight = Scene.add("Enter the room on the right", 0)
         set mainHall_enterLeft = Scene.add("Enter the room on the left", 0)

         //main hall
         call mainHall.link(mainHall_2.link(mainHall_choice)) //just a linear link towards the first choice
         call mainHall_choice.link(mainHall_enterRight, mainHall_enterLeft, 0, 0) //link the answer scenes to the choice
         call mainHall_enterLeft.link(kitchen) //progress to kitchen
         call mainHall_enterRight.link(bedroom) //progress to bedroom
     

     
  16. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    7,014
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    Yes. My lazyness. ;D

    On a more serious note, remember that Scene.add is also the constructor of scenes.
    In order to link scenes properly, it is required that you create choices and scenes before linking them.

    That is why linking and creating is separated in my example. Because at some points, you will eventually try to link a scene that hasn't been constructed yet (as the system allows you to create cyclic designs).
    It actually happened once when I was coding the demo map... so I made the convention that you create all scenes and choices before applying the first link to avoid possible coding errors due to uninitialized variables.
     
  17. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    564
    Resources:
    6
    Tools:
    1
    Maps:
    1
    Spells:
    1
    JASS:
    3
    Resources:
    6
    From the demo map:
    Code (vJASS):

         //connect nodes
         
         //main hall
         call scenes[0].link(scenes[1].link(choices[0])) //just a linear link towards the first choice
         call choices[0].link(scenes[2], scenes[3], 0, 0) //link the answer scenes to the choice
         call scenes[2].link(scenes[4]) //progress to kitchen
         call scenes[3].link(scenes[17]) //progress to bedroom
         
         //kitchen
         call scenes[4].link(scenes[5].link(scenes[6].link(choices[1])))
         call scenes[6].link(choices[1])
         call choices[1].link(scenes[7], scenes[8], 0, 0)
         call scenes[7].link(scenes[9].link(scenes[10].link(scenes[11].link(choices[2]))))
         call scenes[8].link(scenes[1])
         call choices[2].link(scenes[12], scenes[13], scenes[14], 0)
         call scenes[12].link(scenes[15].link(scenes[16]))
         call scenes[13].link(scenes[15]) //We already linked scene 15 with scene 16, so we don't have to do that again
         call scenes[14].link(scenes[15])
         call scenes[16].link(scenes[0]) //We died, so reset the game
         
         //bedroom
         call scenes[17].link(scenes[18].link(choices[3].link(scenes[19], scenes[20], scenes[21].link(scenes[1]), 0))) //let's step up our nesting game, shall we? Notice how you can link even inside choice brackets?
         call scenes[19].link(scenes[22].link(scenes[23].link(choices[4].link(scenes[24], scenes[25], 0, 0))))
         call scenes[24].link(scenes[15]) //see this? We can link back to the kill scene here without further changes, as the scene has no background directly associated with it
         call scenes[25].link(scenes[15])
         call scenes[20].link(scenes[26].link(scenes[27].link(choices[5].link(scenes[28].link(scenes[15]), scenes[29].link(scenes[15]), 0, 0)))) //amazing nesting!
         
     


    Oh man... is this going to scale for more than a few rooms, i.e something like a castle?
    It looks like something a "compiler" would generate.

    It might be a good idea to make a gui tool outisde of WC3 (inside of it [using {v}Jass] would be pretty hard) that gives the users a nice
    interface for making the scenes and the links between them and make this tool generate the equivalent script that would use the Text Adventure Engine... or not, I don't know, I'm not really a text-adventure kind of a guy (I don't like reading) :p
     
  18. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    7,014
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    The problem is more or less the non-descriptive names in my example. ;)

    The thing is, it is actually very easy to create patterns even without a good GUI tool for it.

    For example, to create a straight "line" of 4 dialogue texts (A,B,C,D) without any choices, all you do is nest as many links as you can into each other:

    set A = Scene.add(...)

    set B = Scene.add(...)

    set C = Scene.add(...)

    set D = Scene.add(...)


    call A.link(B.link(C.link(D)))


    Or you can use the verbose solution:

    call A.link(B)

    call B.link(C)

    call C.link(D)


    As you can see; the verbose variant is much more readable and looks more logical. But the nested one has it's appeals if you want to keep your code short. It's completely optional, fortunately. Nobody forces you to nest your links. :)


    But yes, a 3rd party code generator for this would be awesome. Maybe one where you can graphically connect nodes?
    And then you can just click a node to enter it's pre-defined properties, like text, background, audio file, etc.?
    I'd say writing something like this in visual basic or Java would take 2 hours at most. It's pretty straightforward.

    The reverse is actually harder: parsing an existing code to present it in a human readable layout.

    Creating custom onDraw functions would be a bit more difficult, though.
     

    Attached Files:

  19. btdonald

    btdonald

    Joined:
    Dec 3, 2011
    Messages:
    346
    Resources:
    8
    Maps:
    1
    Spells:
    7
    Resources:
    8
    Pure awesomeness!!!

    I've tested it, It's really cool.
     
  20. Dr Super Good

    Dr Super Good

    Spell Reviewer

    Joined:
    Jan 18, 2005
    Messages:
    26,134
    Resources:
    3
    Maps:
    1
    Spells:
    2
    Resources:
    3
    How does this hold out in multiplayer?

    Also might want to add some automatic timing support. Eg that a scene changes from one to the other automatically after X seconds without user input.

    You forgot to update the main post of the thread with the latest version. People should not need to scroll down to find an update.