• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Text-Adventure Engine

Status
Not open for further replies.
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.


JASS:
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:
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.
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.
 
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.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
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.
 
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.
 
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.


JASS:
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
 

Attachments

  • AdventureEngine.w3x
    8.2 MB · Views: 107
Last edited:
Level 8
Joined
Jul 24, 2006
Messages
157
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].


JASS:
     //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
 
Is there any disadvantage of using meaningful variable names?
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.
 
Level 13
Joined
Nov 7, 2014
Messages
571
From the demo map:
JASS:
     //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
 
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
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.
 

Attachments

  • adventureeditor.jpg
    adventureeditor.jpg
    14.6 KB · Views: 85

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
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.
 
How does this hold out in multiplayer?
This is intended for single-player use only.
You just don't use arrow-key events in multiplayer, so I saw no reason at all to make this multiplayer compatible.

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.
I hate automatic text progression with a passion and will go out of my way to keep this system clean from such crap.

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.
True dat.
 
Why wouldn't someone use an arrow-key movement system in multiplayer?
The user-experience is horrible with up to 1 second delay between button-press and registering of the event.

The delay in singleplayer mode is bad enough on it's own.


Besides, what's the point when you have only 8MB in Multiplayer anyway? You really want to create an image-heavy game with an 8MB size limit? Good luck with that! The demo map is 8MB in size alone and it has only 3 background images imported at 1024x1024 quality.
 
I really like this system.

Thought it seems like a pain to use. The API is confusing to me.
It's not, actually. All you need to do is create the scenes, then link them with each other.
Am I missing something here?

A.link(B) is as intuitive as it gets. If you have a suggestion how that could be improved, go ahead!
 
JASS:
     Scene.add("Welcome, generic adventure protagonist!", 0)
     Actor.create("Kitchen Girl", "soyoudontlikecake.tga", 'h000', 300, 200, 10, 512, 512)
     call scenes[11].setActors(testchar, 0, 0, 0)

Once I figure out what the numbers are for I might be able to understand how to use it.
'h000' is the rawcode of the unit that is used to display the portrait.
300 is the X-offset (relative to the bottom left corner of the background image) where the actor is meant to be displayed.
200 is the Y-offset of the actor.
10 is the z-offset (you can use that to layer your actors on top of each other).
512 and 512 are the X and Y scale of the actor image.

Scene.add takes an actor after the displayed text as input parameter. This actor is the actor whose portrait is displayed when the text is shown.
Scene.setActors takes up to 4 actors to be displayed simultanously on this scene.

JASS:
Actor.create takes string name, string imagefile, integer portraitraw, real X, real Y, real Z, real SizeX, real SizeY returns thistype

Scene.add takes string text, Actor talker returns thistype
Scene.setActors takes Actor first, Actor second, Actor third, Actor fourth returns nothing
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
This is intended for single-player use only.
You just don't use arrow-key events in multiplayer, so I saw no reason at all to make this multiplayer compatible.
Many maps like Genesis of Empires 2 used arrow keys no problem in multiplayer so that is not really an excuse. I would say a reasonable multiplayer behaviour would be that everyone shares the same view state and can contribute.

I hate automatic text progression with a passion and will go out of my way to keep this system clean from such crap.
Who said it is just for text? It could be for some simple or forced animations such as opening a chest or changing backdrop in response to a choice.

The user-experience is horrible with up to 1 second delay between button-press and registering of the event.
If you are using wireless not intended for gamming I guess the user experience is horrible with 1 second delay. If you use a wired connection the Round Trip Time from UK to US is only 150 ms at most which is 0.15 seconds.

Besides, what's the point when you have only 8MB in Multiplayer anyway? You really want to create an image-heavy game with an 8MB size limit? Good luck with that! The demo map is 8MB in size alone and it has only 3 background images imported at 1024x1024 quality.
Use 256*256 quality or with compression. You can also create some scenes with real time graphics, if your system supported it.

You must remember that some early text adventure games were only 1-2 MB at most, so 8 MB is more than enough.
 
Many maps like Genesis of Empires 2 used arrow keys no problem in multiplayer
Never heard of that map.

I would say a reasonable multiplayer behaviour would be that everyone shares the same view state and can contribute.
Err... how is that supposed to work? Like Twitch plays Pokemon?

Who said it is just for text? It could be for some simple or forced animations such as opening a chest or changing backdrop in response to a choice.
Yeah sprite-based animations are already planned. I just didn't have time to implement it yet.

If you are using wireless not intended for gamming I guess the user experience is horrible with 1 second delay. If you use a wired connection the Round Trip Time from UK to US is only 150 ms at most which is 0.15 seconds.
Hahahaha... you think that arrow key events are just delayed by the normal game latency? That's cute! :D
Here's my suggestion for you: try out the demomap in singleplayer. Yes, singleplayer. The arrow key latency is already higher than 0.15 seconds in SINGLEPLAYER.

Use 256*256 quality or with compression. You can also create some scenes with real time graphics, if your system supported it.
Errr.. what? 1024x1024 backdrops already look horrible enough on modern machines.

You must remember that some early text adventure games were only 1-2 MB at most, so 8 MB is more than enough.
Obviously, you can make as small or big as you want, but the idea was to use high quality images. Because it is 2015. ;)

Seriously, I have no idea why you would want that multiplayer. It's an adventure engine. It involves reading a lot. Not exactly a fun multiplayer experience if you ask me.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
Never heard of that map.
What sort of WC3 enthusiast are you? That map is a technical marvel.

Err... how is that supposed to work? Like Twitch plays Pokemon?
Yes. Might not be perfect but at least is a feature you can boast. Good for local coop.

Hahahaha... you think that arrow key events are just delayed by the normal game latency? That's cute! :D
Here's my suggestion for you: try out the demomap in singleplayer. Yes, singleplayer. The arrow key latency is already higher than 0.15 seconds in SINGLEPLAYER.
I do not notice the delay in singleplayer. Maybe its the cinematic mode causing the delay in displaying the messages and not the actual keyboard input.

In multiplayer it is higher because its based on the synchronization interval of the server. Host robots and LAN fix that.

Errr.. what? 1024x1024 backdrops already look horrible enough on modern machines.
Pah, kids these days. If it is not "smart" this or "UHD" that they do not give it a second thought. Back in my day we would jaw drop with 320x480 pixels and only 512 colours.

Obviously, you can make as small or big as you want, but the idea was to use high quality images. Because it is 2015. ;)
If that was the case you would be using SC2 which has a >>100MB map limit.

Instead you are using WC3 so this is 2005 and you are limited to only 4 MB maps. Oh wait they raised that around 2009, you are limited to 8 MB maps.

Seriously, I have no idea why you would want that multiplayer. It's an adventure engine. It involves reading a lot. Not exactly a fun multiplayer experience if you ask me.
Everything is better as a group.
 
What sort of WC3 enthusiast are you? That map is a technical marvel.
Maybe. Hard to find out with nobody hosting it on bnet.

Yes. Might not be perfect but at least is a feature you can boast. Good for local coop.
I won't implement nigh-useless features just to "tick the box".
I'm not into featuritis.

I do not notice the delay in singleplayer. Maybe its the cinematic mode causing the delay in displaying the messages and not the actual keyboard input.
There is a delay. And no it's not the cinematic mode, displaying messages is instant.

In multiplayer it is higher because its based on the synchronization interval of the server. Host robots and LAN fix that.
Not my experience. I use arrow key events in Gaias aswell (to control the optional 3rd person cam) and in any bnet session with a number of players > 1 I have at least a half-second delay.

Pah, kids these days. If it is not "smart" this or "UHD" that they do not give it a second thought. Back in my day we would jaw drop with 320x480 pixels and only 512 colours.
Visual consistency is a thing. Unless you make the UI equally pixelated, it will not look visually consistent to have low-res backgrounds.

If that was the case you would be using SC2 which has a >>100MB map limit.
Where have I seen that before?

Instead you are using WC3 so this is 2005 and you are limited to only 4 MB maps. Oh wait they raised that around 2009, you are limited to 8 MB maps.
Not in Singleplayer. Which was kind of the point.

Okay, look; I designed this to be used in Singleplayer. Making it Multiplayer-compatible would require a total redesign and I don't really see the point in it. So if anyone wants to make a multiplayer variant, all power to you.

Everything is better as a group.
There are plenty of games that are better in singleplayer. Mostly Adventures.
Name one multiplayer adventure game in the last decade.
 
Status
Not open for further replies.
Top