Log - I'm trying to figure out how to save the content to a file

Hello,

I have a LOG #TEXT# command in a game, and I just noticed: there is no log file saved.

I can view the log during play, of course, but, after that, it's all gone.


I have:

  • altered the Log function to add the text to game.logString each time it runs
  • created a <div> element with the id divLog
  • created a JS function which appends text into my new element (this is called by my altered Log function)

That's really all that I'm certain is working.

I've tried numerous JS scripts, but I can't get anything to create a text file.

I've also altered the doSave JS function, and it will save just the newly created element (with or without divOutput, if I change it around), but I only want that divLog element in a file, not the rest of the game...


Playing with this also made me realize that we could save a game's transcript with the same method I'm trying to come up with here.

In fact, a simple JS function could probably (maybe?) do something slick, like create a separate HTML file when play begins, then append divOutput to it each time it does the same in the game.


I've got Quest open in VS, too. I added SaveLog in, and it ALMOST worked, but... I realized how little I knew about C# and decided the JS route might be possible.


Anyone have any suggestions?

invisible text to get edited post through


I have a LOG #TEXT# command in a game, and I just noticed: there is no log file saved.

I noticed the Log command does not work myself just a few days ago.

Playing with this also made me realize that we could save a game's transcript with the same method I'm trying to come up with here.

This is what I was hoping to do.

There are security issues with saving a file locally, and JavaScript therefore does not implement it as such as far as I know. There are alternatives though. You could have a button that opens up a new window or dialogue box with the transcript in it, or dump it into a virtual file the player can download (http://html5-demos.appspot.com/static/a.download.html).

There is also WebStorage, and as I read about it, this may be a better way to save games online (something to consider for Quest 6).
https://www.w3schools.com/HTML/html5_webstorage.asp


Thanks, Pix!

I'll check that out.


It doesn't look to me like it's set up to save any file (but I'm reading up on the language right now so I can attempt to alter that):

    Public Sub Log(text As String) Implements IPlayer.Log
        BeginInvoke(Sub()
                        If m_log Is Nothing Then
                            m_log = New Log
                        End If
                        If m_log.txtLog.TextLength > 0 Then
                            m_log.txtLog.AppendText(Environment.NewLine)
                        End If
                        m_log.txtLog.AppendText(DateTime.Now.ToString() + " " + text)
                        m_log.txtLog.Select(m_log.txtLog.Text.Length, 0)
                        m_log.txtLog.ScrollToCaret()
                    End Sub)

    End Sub

The log is just me playing around.


1. The ability to save/print a transcript of the game:
This is a common feature of the Z-machine and Glulx games.

2. The ability to enter RESTART:
This is another common feature of Z-machine and Glulx games. (It's especially nice when you die.)


If we can get these two things going on, the INFOCOM / Inform crowd would only have to adapt to entering USE THE THING, which is not Quest's fault at all. That's the author's prerogative.

Digressions aside, I know we can restart the game, but not by the 'old-school' method: entering a command.

This makes me sad when I get to the last bit of the game. I can't do this:

THE END

Would you like to RESTART, QUIT, UNDO, or VIEW BONUS CONTENT?


Again, this is especially cool when you die (minus the bonus content).

I'm pretty sure RESTART won't be difficult to pull off, but the log is kicking my butt! That code almost resembles JS...


    Private Sub RestartMenuClick()
        Launch(m_currentFile, m_fromEditor, m_editorSimpleMode)
    End Sub

This one may be woith a gander (KV wiggles his eyebrows up and down in a very Groucho-like manner):

$("#cmdRestart").click(function () { window.location.reload(); });

"Restart" as an in-game function doesn't work well, as Quest doesn't keep any record of what the state of objects was to start with. You'd want to start over completely.

On the other hand,

<command name="restart" pattern="restart">
  JS.eval("window.location.reload()")
</command>

just pushes the browser's "refresh" button using javascript, which will restart the game on the web version at least. (Not sure if that'll work in the offline player; it might)


@ThePix

I bet Quest-JS uses WebStorage.
...and Squiffy, too.

They both start where I left off after I've closed the browser. Even when I open a revised game. I have to restart to play the version I just re-compiled. (Am I making sense? I've been trying to understand VB.net for a few hours, and... I may have gone more cuckoo than usual.)


I know how to open a URL without the player having to click anything...

I wonder if JS can replace the text in that first link (http://html5-demos.appspot.com/static/a.download.html) with the content of #divOutput.

I guess piping is what I'm talking about. That's probably not possible between pages...


I'll test that out real quick, mrangel.

Thanks much.


It refreshes the screen, which is pretty cool to know how to do, but it doesn't reset the attributes.

As you pointed out:

Quest doesn't keep any record of what the state of objects was to start with.

So, this doesn't work.

I think I just saw how to do that while perusing an older post, though...

...or was that Pixie's Save/Load setup?


I also didn't know I could assign the command's pattern like that. That is VERY nice to know!


@ThePix

So, could I probably put this (and the rest of the code from that page) in an attribute, then msg (game.scriptText) when issuing the SCRIPT command?

<section>
  <div id="container">
    <div id="saveOutput"></div>
    <input type="text" value="MyFile.txt" placeholder="filename.txt">
    <button onclick="downloadFile()">Create file</button> <output></output>
    <aside id="download-help">
      <ul>
        <li>This link was created using a <code>blob:</code> URL
        ( <a href="http://www.html5rocks.com/en/tutorials/workers/basics/#toc-inlineworkers-bloburis" target="_blank">huh?</a> ).
        It contains the <code>download</code> attribute, which means clicking it will
        force the browser to download the resource rather than navigating to it.</li>
        <li>In Chrome, you can also drag this link out of the browser tab and the file
        will be saved. ( <a href="http://www.thecssninja.com/javascript/gmail-dragout" target="_blank">huh?</a> )</li>
      </ul>
    </aside>
  </div>
</section>

You might have to change the name "log" to something else, IDK.

@The Pixie Would storing that save files on an alternate site just make everything weaker:

  1. You still have JavaScript problems.
  2. The main site will fail. The second site will fail. There will be twice the glitches.
  3. Saving with a third party website relies heavily on the belief that that website can be trusted, which is often false/not the case.

I don't know what web storage is, but I've seen thing or two with web hosting/relying on other websites.


@jmnevil54

I didn't see anyone mention storing stuff on a second site.


With WebStorage, Quest would actually be storing it on your PC. Thinking about it, it does sound like the exact opposite of what it is!


How does it save debug.log?

I've been looking for that in the source code for quite a while now.


Okay...

I've almost got the transcript thing working. In fact, I do have sort of have it working, but I want the stuff that gets erased before the screen is cleared, too.


I've created a div called divCleared and inserted it before divOutput, so it doesn't show up.

I can get it to create clearedOutput1, but I can't get the text from divOutput into that element before emptying divOutput.

Anyone have this easy answer, which is currently eluding me?


I can't get the text from divOutput into that element before emptying divOutput.

You mean something like this?

clearScreen = function() {
    $("#divOutput").css("min-height", 0);
    $("#divOutput").children().appendTo("#divCleared");
    $("#outputData").appendTo($("#divOutput"));
    createNewDiv("left");
    beginningOfCurrentTurnScrollPosition = 0;
    setTimeout(function () {
        $("html,body").scrollTop(0);
    }, 100);
}

Do you want to save files on a remote server or on the computer/mobile device of the player?


mrangel,

I tried $("#divOutput").children().appendTo("#divCleared");, but there's nothing in the div.

I tried a couple of similar things, like childNodes, too. They work in an HTML file, but not Quest. I must be messing up on the syntax or something.


Pertex,

I want to save the contents of the log to a text file on the hard drive.

And I want to put the content of the divOutputAlign1 - divOutputAlign? elements in the #clearedData element I created before emptying the contents during clearScreen, so I can print/save transcripts of the game.


Quest has its own JQuery...

I saw the words 'heavily modified' in the notes in there somewhere, too.

I'm thinking it lacks the functions. I can't get += to replace any text with .innerHTML or anything.


Does the modified clearScreen I just posted still remove the elements from divOutput?
It seems very strange if it removes them from divOutput and doesn't add them anywhere else. Are you sure the clearedData div exists at this point?


Not really sure what the purpose of divCleared is.

As I understand it, you want to create a log of all the game activity, including bits before a 'clearScreen'. To me, rather than moving data into a new div, it would make more sense to modify clearScreen so that it hides the elements rather than removing them; and then when you want to save the log, you just use the contents of divOutput. That way, you don't need to worry about what happens with a save/load either.

clearScreen = function() {
    $("#divOutput").append("<hr />");
    $("#divOutput").children().css("display", "none");
    $("#divOutput").css("min-height", 0);
    createNewDiv("left");
    beginningOfCurrentTurnScrollPosition = 0;
    setTimeout(function () {
        $("html,body").scrollTop(0);
    }, 100);
}

Probably I'm misunderstanding your purpose here.


Aha!

That's what I was looking for!

Sorry! I wasn't clear on my purpose.

Thank you, though!


mrangel,

Now I can easily create an HTML transcript (pulled directly from the save file ):


Code
<html><![CDATA[
<div id="outputData" style="display: none;" data-divcount="19" data-currentdiv="#divOutputAlign19"></div>
            
        <div id="divOutputAlign1" style="text-align: left; " class="section1"></div><div id="divOutputAlign2" style="text-align: left; " class="section1 title"></div><div id="divOutputAlign3" style="text-align: center; " class="section1 title"><span style="font-family:Georgia, serif;color:Black;font-size:12pt;"><span style="font-size:260%">ClearScreen</span></span><br></div><div id="divOutputAlign4" style="text-align: left; " class="section1 title"><span style="font-family:Georgia, serif;color:Black;font-size:12pt;"><div style="margin-top:20px"></div></span><br></div><div id="divOutputAlign5" style="text-align: left; " class="section1"><span style="font-family:Georgia, serif;color:Black;font-size:12pt;">You are in a room.</span><br></div><div id="divOutputAlign6" style="text-align: left; " class=""></div><div id="divOutputAlign7" style="text-align: left; " class="section2"><span style="font-family:Georgia, serif;color:Black;font-size:12pt;"></span><br><span style="font-family:Georgia, serif;color:Black;font-size:12pt;">&gt; jump</span><br><span style="font-family:Georgia, serif;color:Black;font-size:12pt;">You jump, but nothing happens.</span><br></div><div id="divOutputAlign8" style="text-align: left; " class=""></div><div id="divOutputAlign9" style="text-align: left; " class="section3"><span style="font-family:Georgia, serif;color:Black;font-size:12pt;"></span><br><span style="font-family:Georgia, serif;color:Black;font-size:12pt;">&gt; c</span><br></div><hr style=""><div id="divOutputAlign10" style="text-align: left; " class="section3"></div><div id="divOutputAlign11" style="text-align: left; " class=""></div><div id="divOutputAlign12" style="text-align: left; " class="section4"><span style="font-family:Georgia, serif;color:Black;font-size:12pt;"></span><br><span style="font-family:Georgia, serif;color:Black;font-size:12pt;">&gt; l</span><br><span style="font-family:Georgia, serif;color:Black;font-size:12pt;">You are in a room.</span><br></div><div id="divOutputAlign13" style="text-align: left; " class=""></div><div id="divOutputAlign14" style="text-align: left; " class="section5"><span style="font-family:Georgia, serif;color:Black;font-size:12pt;"></span><br><span style="font-family:Georgia, serif;color:Black;font-size:12pt;">&gt; c</span><br></div><hr style=""><div id="divOutputAlign15" style="text-align: left" class="section5"></div><div id="divOutputAlign16" style="text-align: left" class=""></div><div id="divOutputAlign17" style="text-align: left" class="section6"><span style="font-family:Georgia, serif;color:Black;font-size:12pt;"></span><br><span style="font-family:Georgia, serif;color:Black;font-size:12pt;">&gt; l</span><br><span style="font-family:Georgia, serif;color:Black;font-size:12pt;">You are in a room.</span><br></div><div id="divOutputAlign18" style="text-align: left" class=""></div><div id="divOutputAlign19" style="text-align: left" class="section7"><span style="font-family:Georgia, serif;color:Black;font-size:12pt;"></span><br><span style="font-family:Georgia, serif;color:Black;font-size:12pt;">&gt; save</span><br></div>]]></html>

It shows the ]]> at the end of it, but that's easy to fix.


image


Thanks so much!


Getting warmer...

function doTranscript() {
    var element = document.getElementById('divOutput');
    var text = element.innerHTML;
}

Now I've got the outputted text as a string.

Next: I need to learn how to save it to a file.

I couldn't get anything from Pixie's post * to work.


function doTranscript() {
  $('<a />', {
    href: "data:text/html,"+encodeURIComponent($("#divOutput").html()),
    download: "logfile.html",
    id: "log_download"
  }).appendTo("#divOutput");
  document.getElementById("log_download").click();
  $("#log_download").remove();
}

?

Creates a link pointing to a data: URI, uses javascript to click the link (you can't use jQuery's 'click' method here), then deletes the link again.

I think I'd prefer a text version myself…

function doTranscript() {
  log = "";
  $("#divOutput").children().each(function (i) { log += ({HR: "\n* * *\n", BR: "\n\n"}[this.tagName] || $(this).text())+"\n";});
  $('<a />', {
    href: "data:text/plain,"+encodeURIComponent(log),
    download: "logfile.txt",
    id: "log_download"
  }).appendTo("#divOutput");
  document.getElementById("log_download").click();
  $("#log_download").remove();
}

(Noting that the first one of those may end up with hidden elements still hidden in your transcript, but the text version doesn't because it looks at the contents of elements without checking if they're hidden)

Also noting (having removed all the line breaks from the javascript so I can force it to run in the web editor) that the download gets stopped by my popup blocker.


OK ... to get around the popup blocker I now have:

doTranscript = function() {
  log = "";
  $("#divOutput").children().each(function (i) { log += ({HR: "\n* * *\n", BR: "\n\n"}[this.tagName] || $(this).text())+"\n";});
  $('<a />', {
    href: "data:text/plain,"+encodeURIComponent(log),
    download: "logfile.txt",
    id: "log_download"
  }).text("Click here to save log file").appendTo("#divOutput");
}

... but this gets screwed up by other scripts. Harder than it looks.


OK ...
there's an onclick event for the whole document that suppresses the default behaviour of a click.
So I need to do:

doTranscript = function() {
  log = "";
  $("#divOutput").children().each(function (i) { log += ({HR: "\n* * *\n", BR: "\n\n"}[this.tagName] || $(this).text())+"\n";});
  $('<a />', {
    href: "data:text/plain,"+encodeURIComponent(log),
    download: "logfile.txt",
    id: "log_download"
  }).text("Click here to save log file if your popup blocker stopped it!").appendTo("#divOutput");
  downloadlink = document.getElementById("log_download");
  downloadlink.addEventHandler ("click", function (e) {e.stopPropagation();});
  downloadlink.click();
}

To use that with the web editor, you can wrap it in a JS.eval("") but you'll have to remember to:

  • Change all " within the code to '
  • Change \n to \\n
  • Change < to "+Chr(60)+" and > to "+Chr(62)+"
  • Remove all the line breaks in the code

so that Quest doesn't mangle it.


Note that you probably also want to use setTurnTimeout() to call `$("#log_download").remove();" next turn, because this code would behave unreliably if you try to save the transcript again.

Or, you could stick the download link in the sidebar, and have its onfocus, onclick, or onmouseover script update the href using the current contents of divOutput; so moving the mouse over the download link ensures it's always current.


I've tried them all. No saved file.

I think the desktop player is stopping it somehow.

I haven't tried it online, but I'm about to test it out.


Hey K.V., if you are able to get it to work, and on the online editor to, I could use that for my game! Considering how many potential updates I'll have!


I've tried them all. No saved file.

Does the link to click on appear?
I could only test it in the online version, and even there I think it could vary between browsers.


Does the link to click on appear?

Yep, but it does nothing on click.

I don't know what it should show as the source in the href, but here's what I've got:

<a href="data:text/plain,%0A%0A%0Atranscript%0A%0AYou%20are%20in%20a%20room.%0A%0A%3E%20script%0A" download="logfile.txt" id="log_download">Click here to save log file if your popup blocker stopped it!</a>


Note: K.V.'s save function, I mean. But I thought this was that.....


jmne,

I think we'll call it mrangel's, but I'll definitely post the entire code, once it works online and offline.

I think it works online now, but I haven't tried yet. I'm still wrestling the desktop version.


Well, I'm in Linux-land with mrangel until I find my Windows 10 DVD...

(A Linux Mint update decided I didn't need Arch Linux or Windows 10 anymore. That's what I get for having Ubuntu on my hard drive. Should have stuck with Arch and Windows.)


Anyway, I've also forgotten my password for the K.V. account, so I'm being a Dick for a while. (Apologies in advance.)


I just realized: I've never checked out Pixie's Save/Load system. Does that really save? Or do I recall something about copying text into a text editor?


That's the results I got online before adding the stopPropagation event handler. I'm guessing the offline version uses a different method to block clicking on links.


HA!

I got back into Arch Linux!

(Linux Mint will not be getting updated again anytime soon.)


It didn't work for me online, either, but I'm using the online editor this time, too.


The first one* shows the link, I click it, it opens a tab with a blank page.

The rest don't do anything, but I'm probably changing something I shouldn't for the online editor. Here's what I've got stuck in there:

JS.eval ("doTranscript = function() { log = ''; $('#divOutput').children().each(function (i) { log += ({HR: '\\n* * *\\n', BR: '\\n\\n'}[this.tagName] || $(this).text())+'\\n';}); $('"+Chr(60)+"a /"+Chr(62)+"', { href: 'data:text/plain,'+encodeURIComponent(log), download: 'logfile.txt', id: 'log_download' }).text('Click here to save log file if your popup blocker stopped it!').appendTo('#divOutput'); downloadlink = document.getElementById('log_download'); downloadlink.addEventHandler ('click', function (e) {e.stopPropagation();}); downloadlink.click();}")


I'm only supposed to put this one code in there, correct?

The doTranscript = function () { instead of function doTranscript() { is new to me.

Is it to append an existing function? Or is it just a different way to write the same thing?


The odd method of declaring a function is because
function doTranscript() {
would be equivalent to
var doTranscript = function() {
which makes it local to the 'eval' block.

That looks right to me. I'll test it again.
Hmm... copy/paste your script into my game and it gives the blank tab again.

This one certainly works for me:

script = "doTranscript = function() {log = '';$('#divOutput').children().each(function (i) { log += ({HR: '\\n* * *\\n', BR: '\\n\\n'}[this.tagName] || $(this).text())+'\\n';});$('" +Chr(60) + "a /"+Chr(62)+"', {download: 'logfile.txt',id: 'log_download'}).attr('href', 'data:text/plain,'+encodeURIComponent(log)).text('Click here!').appendTo('#divOutput');db = document.getElementById('log_download'); db.addEventListener('click', function (e) {e.stopPropagation();}); db.click();};"
JS.eval (script)
JS.doTranscript()

I can't see any difference between them that should make a difference.


I've been playing with it for a while.

It works in Firefox on Arch Linux, but not Chromium (with default settings).

http://textadventures.co.uk/games/view/lwpgtxt_902eqjky5geq-q/transcript2


doTranscript = function() {
  log = "";
  $("#divOutput").children().each(function (i) { log += ({HR: "\n* * *\n", BR: "\n\n"}[this.tagName] || $(this).text())+"\n";});
  addText("<a target='_blank' href='data:text/plain, " + encodeURIComponent(log) + "' download='logfile.txt' id='log_download'>Click here to save log file if your popup blocker stopped it!</a>");
  downloadlink = document.getElementById("log_download");
  downloadlink.addEventListener ("click", function (e) {e.stopPropagation();});
  downloadlink.click();
}

Ugh ... how've I managed that?

Somehow I've typed 'EventHandler' instead of 'EventListener', gone back right away and fixed it, and then copied the wrong version into the forum.

I must have fixed it without thinking as I was removing the line-breaks.


I get this when I click it in Firefox:

Transcript2

You are in a room.Enter SCRIPT

> SCRIPT


This is the correct output.

...but I have an image there. How do I switch that back to HTML?

(Could offer a choice in the command script (txt or HTML), once it's working. I don't see why there couldn't be something that ONLY works in the online player for a change!)


I changed it to EventListener, but I get the same results.


UPDATE

That change to EventListener DID fix it in Chromium!!!

I just didn't see it automatically download. (I was watching for a popup, like in Firefox. (I'm silly.))


Whoo-hoo!

Go, mrangel, go!!!

So the last script I posted WORKS ONLINE ONLY!!!


  • I'd like it to save the images, or maybe save it as an HTML with a link to them. (Give me an inch, I take a mile!)

  • I already know how to incorporate this into my save log command, so the original question has just been answered, as far as the online player is concerned.

  • Now, I must make it work in the desktop player.

  • After this, if we can get a RESTART command to make the desktop player think I hit CTRL+R, Quest will lack no Inform 7 / INFOCOM features.

  • After that, I'll be toying with the notion of porting the desktop editor to ...something that works on *nix.


Saving HTML should be easy enough; just change the download filename, the content type to text/html, and the first two lines of the function (which do the html→text conversion).

Images… I'm not sure if just saving the links will work. Do the images have the same URL each time a game is run? If not, you probably want to convert them to more data: URI's in the same way.


I think this is what's stopping it in the desktop player:

    Private Sub GoURL(data As String)
        If data.StartsWith("http://") Or data.StartsWith("https://") Or data.StartsWith("mailto:") Then
            System.Diagnostics.Process.Start(data)
        End If
    End Sub

I changed it to this:

    Private Sub GoURL(data As String)
        If data.StartsWith("http://") Or data.StartsWith("https://") Or data.StartsWith("mailto:") Or data.StartsWith("data:text") Then
            System.Diagnostics.Process.Start(data)
        End If
    End Sub

Quest tries, but I just get this:

image

image


I can add another If that opens the default browser, but I have no clue what the code for that would be in ASP.vb.


Making progress (I think).

I have edited the goURL function in PlayerHTML.vb, but I'm missing something simple somewhere.

Here's what I've done:

Original:

    Private Sub GoURL(data As String)
        If data.StartsWith("http://") Or data.StartsWith("https://") Or data.StartsWith("mailto:") Then
            System.Diagnostics.Process.Start(data)
        End If
    End Sub

I added the If with data:text:

    Private Sub GoURL(data As String)
        If data.StartsWith("http://") Or data.StartsWith("https://") Or data.StartsWith("mailto:") Then
            System.Diagnostics.Process.Start(data)
        End If
        If data.StartsWith("data:text") Then
            Process.Start("chrome.exe", data)
        End If
    End Sub

This is what the href should be: data:text/plain, %0A%0A%0Atranscript%0A%0AYou%20are%20in%20a%20room.%0A%0A>%20script%0A

...but it's only putting this through to Chrome: data:text/plain,

I wonder if the comma is the culprit... Nah, I don't think the comma would show up if that was the case. (I was thinking it was ending the parameter or something.)


More on this as it comes in...


NOTE: I used Chrome because Internet Explorer will not open the file, even with the correct link. If I can get this to work with Chrome and Firefox when I call each directly, I think I can add a function to find the player's default browser first.

ALSO NOTE: I'm 'learning' all of this as I go, so please excuse my ignorance (which, in this case, is NOT bliss).


ONE MORE NOTE:

I like it displaying the text in the browser better than it downloading the file automatically. This way, the player could print to paper or PDF (or whatever) OR save it to a file.

Firefox asks you what you'd like to do, but Chrome just downloads the .txt file. This may upset (and/or confuse) some players.


Since I'm all over the place (sorry), this is what I'm after:

  • Save the information you can view in the Log during play to a .txt file which can be viewed, in its entirety, after closing the game

  • The ability to enter SCRIPT or TRANSCRIPT during play to print or save everything Quest has printed to the screen during play.

    • When using FROTZ or Glulxe, this is default for every game. If you quit the game, your file is saved.
      • It even works in Parchment (the online version).

I asked for a way to save the Log (as well as the transcript) directly to the hard drive, but one of the first codes mrangel posted actually opened the browser, displaying the text exactly as it should be...

This is nice. I believe it to be better than what I originally wanted.

The player could print that or save it or whatever they like.


Do you have a space after the comma? There shouldn't be, but you seem to have one in an earlier post. Could explain that behaviour.
Also, I'm not sure if this method will fail for large transcripts.


I'm missing something simple somewhere.


Do you have a space after the comma?

Yes.

Hot dog! It works! It works!


EDIT:

If Windows knew what to do with the data:text, it would be smooth sailing.

Hrmm...

Maybe I need to use Try.

This is what I've added to the Visual Studio code:

        If data.StartsWith("data:text") Then
            Process.Start("chrome.exe", data)
        End If

I'm going to learn to use Try and Catch.

Thanks, mrangel!!!


If you want to view the file rather than saving it, it shouldn't be that hard to open a new tab and copy data across to it. Whether you'll then be able to save it from there would be browser dependant (IE used to be a pain, saving the original HTML document without any dynamically generated content. Don't know if this is better now).

If you're allowing the user's browser of choice, be aware that different versions of IE have different arbitrary limits for the maximum size of a data: URI.

I remember thinking that it might be better to base64 encode the URI, but I can't remember why I thought it might help. If you find a circumstance where it's useful, the URI would then be "data:text/plain;base64,"+btoa(log).


Enhanced compatibility version.

(Does desktop Quest implement its own browser? The existence of the GoURL sub seems redundant if it was just embedding a normal browser in its own window, which is what I would have expected it to do for rendering HTML/CSS/JS content. But if it's using IE, which I understand was quite easy in VS, some version of this might help)

doTranscript = function() {
  log = "";
  $("#divOutput").children().each(function (i) { log += ({HR: "\n* * *\n", BR: "\n\n"}[this.tagName] || $(this).text())+"\n";});
  if (window.navigator.msSaveOrOpenBlob) {
    // Because if you want it to work in Internet Explorer, you have to do it differently
    window.navigator.msSaveOrOpenBlob (new Blob([log], {type: "text/plain"}), "logfile.txt");
  } else {
    $('<a />', {
      download: "logfile.txt",
      id: "log_download"
    }).text("Click here to save log!").appendTo("#divOutput");
    downloadlink = document.getElementById("log_download");
    if (window.Blob && URL.createObjectURL) {
      downloadlink.href = URL.createObjectURL(new Blob([log], {type: "text/plain"}));
    } else {
      downloadlink.href = "data:text/plain,"+encodeURIComponent(log);
    }
    downloadlink.addEventListener ("click", function (e) {e.stopPropagation();});
    downloadlink.click();
  }
}

(use msSaveBlob instead of msSaveOrOpenBlob if you don't want to give the player the option of opening it. To do the same in chrome, remove the "download" property of the href)


IE just tells me "The webpage cannot be displayed" when I paste the data:text... bit that I copy from Firefox into it.


NOTE: I didn't intentionally start trying to open the text to view it with an option to print or save. I just wanted it to save it.

Quest isn't behaving at all like a browser in this case, though. Even when I do get it to pass your code through, it's just opening the browser I specify with the game's text displayed in it.

Come to find out, this is what I'd like it to do (or the same thing in Notepad -- I can get Quest to open Notepad, but I can't get it to paste the text into it).


Wait!

I need to find how Quest saves a JS file! (It just saves whatever you type in the field, as you type it in, as far as I can tell. And it saves it into the game's directory.)

I bet that's easier to mess with than this, and it would work.

I've already found the function that checks for the online player, so we could have it do your browser download thing if online, otherwise save it like it does a JS file (or open the default browser with the text displayed in the body).


Does desktop Quest implement its own browser? The existence of the GoURL sub seems redundant if it was just embedding a normal browser in its own window, which is what I would have expected it to do for rendering HTML/CSS/JS content. But if it's using IE, which I understand was quite easy in VS, some version of this might help)

mrangel,

We're good at typing each other posts and missing each other's recent posts!


The desktop version has Chromium built into it.

I've made it load that data:text... string, just as Firefox does. It displays it like it's a new page, but you can't save, you can't print, and you can't back up. That's why I went the goURL route. It just opens whatever I put as the parameter in the browser as soon as the script runs.


I was just reading up on blobs, too.

I'm trying your code right now.


PS

If you're not just enjoying doing all this work on this, I don't wish to impose. (I'm appreciating every bit of it, and I think I'm learning, but I feel like I may be ... you know... imposing at this point.)

I'm a forum problem-solving junkie, you see, and I sometimes assume everyone else here is, as well.


The problem (I believe) is that Windows doesn't know what app to open data:text/plain...text with.

If the href was 'filename.txt', Windows would open that up with Notepad. If it was 'filename.htm', it would go to the default browser, which is Chrome in this case.

The goUrl function opens any actual webpage in Chrome with no problem. (It also works for the mailto: links.)


This may or may not be related:

Quest's browser will not run things in iframes that Chrome and Firefox will, and vice-versa.

I.e., I can put the Quest forum in an iframe. It works in Quest, but not in a real browser.


Audio and video links and embeds work differently in the Quest browser, too.


'try', 'throw', and 'catch' deal with handling errors and displaying error messages:

when you put scripting into 'try', if it fails for whatever reason, there's various built-in error messages that you can have displayed in/via the 'catch', or you can create your own scripting for error messages

for pseudocode example (it's been awhile since I've done 'try' and 'catch' with the main programming languages ... and am too lazy to re-look them up, lol)

try {
  msg ("Age?")
  get input {
    player.age = result
  }
}
catch (CatchInvalidInput (e)) {
  e = "ERROR: you need to input an integer: a non-decimal number"
  msg (e)
}

the 'throw' lets you create your own custom scripting for the errors, and also acts as like a jump/branch, so you don't need duplicate 'catches' everywhere, you have 'throws' within your game code, that go to the various/proper 'catch' blocks for them.

here's some links of various languages' examples:

https://www.w3schools.com/js/js_errors.asp (JS: JavaScript)

https://docs.oracle.com/javase/tutorial/essential/exceptions/index.html (Java full-bore programming language, this is NOT the same as JS:JavaScript)

http://www.cplusplus.com/doc/tutorial/exceptions/ (C++ full-bore programming language)

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/exception-handling-statements (C#)


(edited the previous post; for wider support in different browsers)

If it has embedded Chromium, I have no idea why the previous script doesn't work.


The problem (I believe) is that Windows doesn't know what app to open data:text/plain...text with.

When it first came out, a lot of malware started using data: URI's rather than actual files, because as soon as it's a file on a disk somewhere, a virus checker could scan it. MS's response was just to block them everywhere except inside the browser.


ya... lots of malware/adware uses the 'data:script' in url's for the apple computers, to open up their webpages/downloads... I still don't understand this type of stuff that well though, sighs.


Ah... I figured it was a security measure of some sort.


I am assuming it has embedded Chromium. I see "Chromium" in the code now and then.


I fed the parameter to this when it displayed the TRANSCRIPT text inside Quest's browser:

ctlWebView.Load("res://local/ui")

I used ctlWebView.Load(href). It cleared the game content and displayed the transcript content. Quest's browser has no tabs or Back button though, so I had to restart.


I see you edited the code. I'll try with that revision.

Thanks much!


HK,

Try only seems to check if its script sent out the command, not whether it succeeded. (This, I assume, is because nothing is being returned from the other function.)

Argh!

I should have taken computers instead of HVAC/R!!!


mrangel,

This is my href with the new code:

blob:res%3A///35526a11-8950-4f19-9f41-64f1a2da569c

Windows doesn't find a default app to run that, either.

Can you end a blob with .txt? That might solve it...


If you can edit Quest itself, I think this should be relatively easy to do. I'll step out, though; really don't like VS (it's worse than javascript, which I never really got the hang of).


You can't pass a blob: URI to another app. It refers to a location in the browser's memory; you have to rely on the browser to save or open it.
Does it open the blob at all?

If it opens within the existing window, then try changing the content type to application/octet-stream


really don't like VS (it's worse than javascript

I've been thinking it looks like a dumb version of JS...

If you can edit Quest itself, I think this should be relatively easy to do.

I've got it open in Visual Studio right now. I thought it should be easy, too...

VB.NET has public Subs and private Subs (whatever that really means), and...

I'm just in over my head, and I'm pulling you under with me, mrangel.


I just found logfile.txt in my Downloads folder!

I was watching the game's directory (for some reason).

EDIT: I was being sillly. This was where I downloaded it while playing online.


I got it working in the desktop version.

PlayerHTML.vb

    Private Sub GoURL(data As String)
        If data.StartsWith("http://") Or data.StartsWith("https://") Or data.StartsWith("mailto:") Then
            System.Diagnostics.Process.Start(data)
        End If
        'The following section was added by KV to open the game transcript in-browser to print or save.
        If data.StartsWith("data:text") Then
            If File.Exists("C:\Program Files\Google\Chrome\Application\chrome.exe") = True Then
                Process.Start("chrome.exe", data)
            ElseIf File.Exists("C:\Program Files (x86)\Google\Chrome\Application\chrome.exe") = True Then
                Process.Start("chrome.exe", data)
            ElseIf File.Exists("C:\Program Files\Mozilla Firefox\firefox.exe") = True Then
                Process.Start("firefox.exe", data)
            End If
        End If
    End Sub

The JS functions added to the game:

clearScreen = function() {
    $("#divOutput").append("<hr />");
    $("#divOutput").children().css("display", "none");
    $("#divOutput").css("min-height", 0);
    createNewDiv("left");
    beginningOfCurrentTurnScrollPosition = 0;
    setTimeout(function () {
        $("html,body").scrollTop(0);
    }, 100);
}

doTranscript = function() {
  log = "";
  $("#divOutput").children().each(function (i) { log += ({HR: "\n* * *\n", BR: "\n\n"}[this.tagName] || $(this).text())+"\n";});
  addText("<a target='_blank' href='data:text/plain," + encodeURIComponent(log) + "' download='transcript.txt' id='log_download'>Click here to save log file if your popup blocker stopped it!</a>");
  var href = "data:text/plain," + encodeURIComponent(log) + "";
  downloadlink = document.getElementById("log_download");
  downloadlink.addEventListener ("click", function (e) {e.stopPropagation();});
  downloadlink.click();
  ASLEvent("goScriptUrl", href);
}


goScriptUrl

  <function name="goScriptUrl" parameters="href">
    JS.goUrl (href)
  </function>

I can't find the default location of Microsoft Edge, or I would throw it in there for the Edge users.

Internet Explorer won't open anything, so I've discounted it completely.

I will add and Else that displays an Error message, too.


mrangel,

You rock!

I wasn't trying to put you off of this last night (or this morning).

I always appreciate your involvement. I just didn't want you to pull all of your hair out on my account, you know what I mean?


Is this a security risk?

If so, I won't be disheartened.


Now I'm really going to learn how to automatically save the log to text file.

Invisible text inserted to edit post


This line of code is blowing my mind:

$("#divOutput").children().each(function (i) { log += ({HR: "\n* * *\n", BR: "\n\n"}[this.tagName] || $(this).text())+"\n";});

Attempt:

$("#divOutput").children().each(function (i)

For each child of the element named divOutput, do :


{ log += ({HR: "\n* * *\n", BR: "\n\n"}[this.tagName] || $(this).text())+"\n";}

NOTE: We set the variable log as an empty string in the line above this one.

So log += is the same thing as saying log = log + in Quest.


So this is what we're adding to the string named log:

({HR: "\n* * *\n", BR: "\n\n"}[this.tagName] || $(this).text())+"\n";})

HR: "\n* * *\n", BR: "\n\n"}[this.tagName]

This is the one that really gets me.


{HR: "\n* * *\n", BR: "\n\n"}

I'm thinking this does something with line breaks. (I know. I'm so very astute, right?)

It seems to be replacing <hr/> and <br/> with newlines.


[this.tagName]

Uh... Is this finding what type of element divOutput is?

Does HR: "\n* * *\n", BR: "\n\n"}[this.tagName] find those tagNames (element types) in the string and replace them?


||

Is this piping those functions or an Or operand???


$(this).text())+"\n"

Add the text from this child to the string variable named log with a line break following it.


In JS, the {} is a literal object. But we're actually using it like a dictionary in Quest; {HR: "\n* * *\n", BR: "\n\n"} is a dictionary with the keys "HR" and "BR", and string values.

dictionary[somekey] extracts the value for a specific key; so {HR: "\n* * *\n", BR: "\n\n"}[this.tagName] is "\n* * *\n" if the current tag is an <hr>, a double newline if the tag is a <br>, and undef otherwise.

Within an each() function, this is the current element in the iteration, and this.tagName gets an uppercase version of the HTML tag name. So "BR", "HR", "P", "DIV" etc.

So for an <hr> or <br> element, it puts a couple of newlines or a line of asterisks in the text version.

|| is logical or. But JS and Perl support nice short-circuiting; || returns its first true argument if there is one. So you can use a || b || c || d to get the first defined variable from the list. So {HR: "\n* * *\n", BR: "\n\n"}[this.tagName] || $(this).text() gives you a couple of newlines or a line of stars; or the contents of $(this).text() if the thing to the left of the || is undefined.

$(this) is like this, but allows jQuery methods rather than just the plain JS/DOM ones. So I can use .text() to get a plain text version of the contents of each <p> element.

That's the first thing that sprung to mind for converting a chunk of HTML into text.


mrangel,

Thank you very much for that! I was close, but those asterisks were making me think of wild-cards. Ha-ha!


It doesn't pick up on the single line breaks, but I'm studying up on how you're doing this at the moment. Between that and the explanation you just dropped, I bet that goes well quickly.

Then, I only need to grab the images, too. That probably entails adding IMG along with HR and BR, but using btoa() to convert the image to Base64 somehow first.

NOTE: I've actually been using data:text/html;base64 in my script in an attempt to display the outputted text, in its original/intended font and format, with the image links intact.

The text file is a great way to do it, and it's exactly how Z-machine and Glulx games saves the transcript. It's also easier to deal with than an HTML.

...but Quest is so awesome...

I know I can get it to one-up Inform 7.


Digressions aside, I've switched back to text/plain for now.


This is the script I'm using:

doTranscript = function() {
  log = "";
  $("#divOutput").children().each(function (i) { log += ({HR: "\n* * *\n", BR: "\n\n\n"}[this.tagName] || $(this).text())+"\n";});
  addText("<a target='_blank' href='data:text/plain;base64,"+btoa(log) + "' download='transcript.html' id='log_download'>Click here to view transcript file if your popup blocker stopped it!</a>");
  var href = "data:text/plain;base64,"+btoa(log) + "";
  downloadlink = document.getElementById("log_download");
  downloadlink.addEventListener ("click", function (e) {e.stopPropagation();});
  downloadlink.click();
  ASLEvent("goScriptUrl", href);
}

I went with Base64 because I think Mozilla is saying the other way has a lower maximum character limit.


This is the output (which is identical, besides the font, no matter which method I use):

transcript

A roomYou can go north.

> go north

A room2You can go south.

> go southA roomYou can go north.

> script


I even turned on the new line after each description in Quest's settings, but I get that same output.

Here's an actual screenshot:

image


PS

Want to see what Quest looks like when running under Gtk?

image


Check it out:

http://textadventures.co.uk/games/view/nft9fbscdkw43hddd0yvra/transcript


You can't do it in the desktop editor. It only works online.

(Pay no mind to the exit 'list' in the location bar. I forgot to align that bit on the right.)


OK, it looks like it works neatly with my game, but that's because I already messed about with the HTML a bit.

At this point it's seeing the <div>s and taking the text content out of them, not examining the different elements within them. Not sure the best way to deal with that. Maybe change $("#divOutput") to $("#divOutput > div") in that line, or children() to find("span,hr,br") to explicitly search for those elements.


Cool.

I'll try that. Thanks!


Check out the game I linked to above, if you get bored. (Tell me if yours looks better.)


Or if you want HTML, you could use log = $("#divOutput").html(); :p


Sweet!

I just need to change the CSS to display:block (or just delete that bit). I bet I can figure that out!


This is what I've got going on at the moment:

clearScreen = function() {
    $("#divOutput").append("<hr />");
    $("#divOutput").children().css("display", "none");
    $("#divOutput").css("min-height", 0);
    createNewDiv("left");
    beginningOfCurrentTurnScrollPosition = 0;
    setTimeout(function () {
        $("html,body").scrollTop(0);
    }, 100);
}

doTranscript = function() {
  var log = $("#divOutput").html();
  var href = "data:text/html;base64,"+btoa(log) + "";
  if (webPlayer){
    addText("<a target='_blank' href='data:text/html;base64,"+btoa(log) + "' download='transcript.html' id='log_download'>Click here to view transcript file if your popup blocker stopped it!</a>");
    downloadlink = document.getElementById("log_download");
    downloadlink.addEventListener ("click", function (e) {e.stopPropagation();});
    downloadlink.click();
    ASLEvent("goScriptUrl", href);
  } else {
    addText("You are playing offline.  Copy and paste this url in your browser: <br/>"+href);
  }
}

All I need to do is undo that second line in the clearScreen function before I passlog to...

Hey... I may have just figured it out...


$("#divOutput").children().css("display", "").html();
  var log = $("#divOutput").html()

image


Hehehe...

Uh... I can bring 'em back...

...but I must now find a way to replace display:none with displayReplaced then run the script, then change them back again. I think that would happen so fast, the player wouldn't see it.

NOTE: I put the <hr/> there every time the screen is cleared because it seemed like a nice thing to have there while perusing a transcript...

I'm thinking the answer is in this post: http://textadventures.co.uk/forum/quest/topic/5fd2-liccuidcbbf_eiefa/log-im-trying-to-figure-out-how-to-save-the-content-to-a-file#6eb52dc5-0e93-4db8-b617-3b79b977c83b

(I'm studying now.)


I changed the first line of the clearScreen function, giving the <hr/> and id.

clearScreen = function() {
    $("#divOutput").append("<hr id=\"clearedAbove\" />");

I have a gut feeling that may come in handy. (Either that or it's breakfast time.)


off the top of my head...

$('<div id="logCopy" />').html($("#divOutput").html()).appendTo("#outputData");
$("#logCopy > div").css("display", "block");
log = $("#logCopy").html();
$("#logCopy").remove();

Oh, and regarding:

$("#divOutput").append("<hr id=\"clearedAbove\" />");

You shouldn't have multiple elements with the same ID. jQuery behaves fairly well with it, but how the browser handles it isn't exactly reliable.
In this case, you'd be better using <hr class="clearedAbove" />; which you can find all instances of with $(".clearedAbove").

If you were going down that path, you could actually change $("#divOutput").children().css("display", "none"); in clearScreen to $("#divOutput").children().addClass("clearedScreen"); and then put .clearedScreen { display: none; } in the document CSS to hide all elements with that class. Then don't include that line in the CSS from the saved transcript; so they all appear again.

(I assume you can add CSS to the document? I haven't tried it)


I just got back to my PC.

You shouldn't have multiple elements with the same ID .... you'd be better using <hr class="clearedAbove" />

That makes sense.

change $("#divOutput").children().css("display", "none"); in clearScreen to $("#divOutput").children().addClass("clearedScreen"); and then put .clearedScreen { display: none; } in the document CSS to hide all elements with that class. Then don't include that line in the CSS from the saved transcript; so they all appear again.

mrangel, you're one clever son-of-a-gun!

I assume you can add CSS to the document?

Yes, sir. The fastest and easiest way I've found is JS.addText("<style>.clearedScreen { display: none; }</style>").


mrangel,

You should definitely:

  • Load a game with this feature added in the web player.
  • Save the game.
  • Quit .
  • Load the save.
  • Note that #divOutput is empty (but divOutputAlign is not messed up).

So, you fixed the issue with ShowMenu, along with numerous other things (and we all thank you very, very, very much!), but the web player seems to empty out #divOutput when loading a save no matter what. (EDIT: Found a fix for this now.)

This may not be such a bad thing, overall. Pertex recently pointed out that the hidden elements may negatively effect the browser during the automatic scrolling, and I had wondered the same thing.


NOTE: I'm still using this in my personal versions of the games I play, so this wasn't all for naught.


...and, I'm back to trying to figure out how to tweak the source code.

I'm certain I can eventually get the log to create a .txt file and append each entry to it.

The same can be done with a transcript command, I'm sure.


the web player seems to empty out #divOutput when loading a save no matter what

I still haven't seen this behaviour. Is it just being weird for me?


Really?

When you load a saved game, it doesn't look like this:

image


The screen never clears during play or anything, either.


Every game I save and load online does this to me. In Chrome and Firefox. Even old games.


Here's my example game, which I created online, just to make sure it isn't something with my desktop editor:

http://textadventures.co.uk/games/view/-7zus1ucqegvotxqgtgzqa/loaded-save-tester

<!--Saved by Quest 5.7.6404.16853-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="Loaded Save Tester">
    <gameid>68e8daeb-f2c6-4560-a6c2-b886785f742e</gameid>
    <version>1.0</version>
    <firstpublished>2017</firstpublished>
    <menufont>Georgia, serif</menufont>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <description><![CDATA[When I load a saved game, I can't see this!<br/>]]></description>
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
  </object>
</asl>

More on the subject:
http://textadventures.co.uk/forum/quest/topic/qdwn7teux0im1uaryij1jw/while-a-game-is-being-tested#560319ca-55da-4ca0-9e50-23cefab41409


OK ... that game gives me a blank screen.

On my "disposable game" I see the screen contents that were there before saving (including the "game saved" messages). When playing other people's games (most notable on the one that brought me here in the first place) divOutput seems to restore its contents too.


Ah, nope. It's just the way I'm using it.

Typing "SAVE" doesn't save the contents of divOutput; clicking the "save" button in the corner of the screen does.


Typing "SAVE" doesn't save the contents of divOutput; clicking the "save" button in the corner of the screen does.

So I see...


playercore.js

    $("#cmdSave").click(function () {
        saveGame();
        afterSave();
    });

So I need to add this to my SAVE command:

if (game.online) {
  JS.saveGame()
  JS.afterSave()
}

Try it now, mrangel. I updated it. Now you can enter SAVE when online, and it calls the same thing as when you click.

http://textadventures.co.uk/games/view/-7zus1ucqegvotxqgtgzqa/loaded-save-tester

<!--Saved by Quest 5.7.6404.16853-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="Loaded Save Tester">
    <gameid>68e8daeb-f2c6-4560-a6c2-b886785f742e</gameid>
    <version>1.0</version>
    <firstpublished>2017</firstpublished>
    <menufont>Georgia, serif</menufont>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <description><![CDATA[When I load a saved game, I can't see this!<br/>]]></description>
    <enter type="script">
      webCheck
    </enter>
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
  </object>
  <command name="kv_webcheckCmd">
    <pattern>online;webcheck</pattern>
    <script>
      webCheck
    </script>
  </command>
  <command name="kv_saveCmd">
    <pattern>save</pattern>
    <script><![CDATA[
      JS.eval ("addText (Date()+\"&nbsp;\") ")
      if (not game.online) {
        request (RequestSave, "")
      }
      else {
        JS.saveGame ()
        JS.afterSave ()
      }
    ]]></script>
  </command>
  <function name="webCheck"><![CDATA[
    JS.eval ("addText(\"<br/>webCheck initialized<br/>\");var bool = false; if(webPlayer) {bool = true;ASLEvent('setOnline', bool);}else { bool = false; ASLEvent('setOnline', bool);}addText(\"<br/>webCheck complete<br/>\");")
  ]]></function>
  <function name="setOnline" parameters="bool">
    switch (bool) {
      case ("true") {
        msg ("You are playing online!")
        game.online = true
      }
      case ("false") {
        msg ("You are playing in the desktop version.")
        game.online = false
      }
      default {
        error ("Error checking for online player.")
      }
    }
  </function>
</asl>

Seems to work as intended :)

I'm wondering why RequestSave doesn't do that.


I had JS.doSave() in there for the desktop version at first, but I edited that. Now it's request (RequestSave, "") again.

Pixie is phasing out request everywhere possible, and the doSave JS function is in the latest source code, but not the current release. So, I thought it only courteous to everyone else but us to change it to something that works universally.

saveGame and afterSave are doing some HTML posting stuff to the server (which I haven't learned about yet).


Hrmm...

Could I nix the functions, and just do:

JS.saveGame ()
JS.afterSave ()

request (RequestSave, "")

...no, it should be:

//Offline first:
request (RequestSave, "")

//Online cleanup
JS.saveGame ()
JS.afterSave ()

What could happen if I try to post the online save from the desktop editor?

I'm scared to test it!

UPDATE: It didn't seem to do anything abnormal when I saved in the desktop with this command alone.

Loaded Save Tester

You are in a room.
When I load a saved game, I can't see this!

webCheck initialized

webCheck complete
You are playing in the desktop version.

> save

Saved: C:\Users\kMFNv\Documents\tester.quest-save


It worked online, too, but it printed my save command twice:

Loaded Save Tester

You are in a room.
When I load a saved game, I can't see this!

webCheck initialized

webCheck complete
You are playing online!

> save

> save


When playing other people's games (most notable on the one that brought me here in the first place)

Which game might that be?


It's a fetish game; I happened to load an old save that had a picture in the log, and quickly looked to make sure nobody else can see my screen. Then realised that demonstrates that saving online does work.


Ha!

That's funny stuff!


You wouldn't happen to know a way to set game.something to something using JS.eval() without using an ASLEvent and setting up a function for it, would you?


I suspect you'd have to make a function for it.


the hidden elements may negatively effect the browser during the automatic scrolling

Have you noticed this, mrangel?

I haven't tested it on a big game yet.


I've not seen any issues; but the automatic scrolling code seems to be pretty weird anyway. (As far as I can tell from skim-reading the code, the scrolling might mess up if you hide an element that's in the middle of the output, but should be fine with hiding stuff at the top or bottom)

(If I'm playing a game and a command produces more than a screenful of output, I want to read what it said. I don't want it to scroll up to the top of the output and then back down to the command bar. I want to read last turn before I decide what to do next. Sometimes I might want to enter a command before reading, like if I'm reentering a room with a lot of stuff in it and I already know where I'm going. But when I'm playing a game, I find automatically scrolling to the bottom really, really annoying.)


I fancy the MORE prompt, myself.


I said I'd never had by screen bounce up and down on my Android in some other thread. That was true then. Not so much now...

That's pretty crazy! My screen bounced about 20 times, just like you said yours does sometimes. (I believed you. I just thought maybe my device was impervious to that. Nope!)


Can't we just add this to kill it?

scrollToEnd = function {
}

Back to the transcript:

I've got this going on at the moment, and it works offline.

NOTE: This prints the transcript. You can print, save to PDF, or whatever your machine's print options are.


The game:

CLICK HERE TO VIEW THE GAME'S CODE
<!--Saved by Quest 5.7.6404.15496-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="Printing a Transcript">
    <gameid>8f137d1a-50d8-43bf-b630-299ab3dcd0b2</gameid>
    <version>0.1</version>
    <firstpublished>2017</firstpublished>
    <showscore type="boolean">false</showscore>
    <feature_advancedscripts />
    <commandpane />
    <inituserinterface type="script">
      JS.setCommands ("Print_Transcript;Clear_Screen", "black")
    </inituserinterface>
    <subtitle>version{game.version}</subtitle>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <enter type="script">
    </enter>
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
  </object>
  <command name="JScmd">
    <pattern>JS.#text#</pattern>
    <script>
      JS.eval (text)
    </script>
  </command>
  <command name="print">
    <pattern>print</pattern>
    <script>
      JS.print ()
    </script>
  </command>
  <command name="transcript">
    <pattern>script;transcript;print transcript;print_transcript</pattern>
    <script>
      JS.printTranscript ()
    </script>
  </command>
  <command name="clearthescreencmd">
    <pattern>clr;clear;clear_screen</pattern>
    <script>
      JS.clearScreen ()
    </script>
  </command>
  <javascript src="javascript.js" />
</asl>

The JS:

printTranscript = function() {
  var iframe = document.createElement("iframe"); // create the element
  document.body.appendChild(iframe);  // insert the element to the DOM 
  addText("<style>.clearedScreen { display: block; }</style>"); //display the cleared text
  iframe.contentWindow.document.write($("#divOutput").html()); // write the HTML to be printed
  iframe.contentWindow.print();  // print it
  addText("<style>.clearedScreen { display: none; }</style>");//hide the cleared text again
  document.body.removeChild(iframe); // remove the iframe from the DOM
}


clearScreen = function() {
    $("#divOutput").append("<hr class=\"clearedAbove\" />");
    addText("<style>.clearedScreen { display: none; }</style>");
    $("#divOutput").children().addClass("clearedScreen");
    $("#divOutput").css("min-height", 0);
    createNewDiv("left");
    beginningOfCurrentTurnScrollPosition = 0;
    setTimeout(function () {
        $("html,body").scrollTop(0);
    }, 100);
}

http://textadventures.co.uk/games/view/dbtxp7q5euw7ygj6vodavw/printing-a-transcript


That looked funny online, so I changed the JS to this:

printTranscript = function() {
  $('<div id="logCopy" />').html($("#divOutput").html()).appendTo("#outputData");
  $("#logCopy > div").css("display", "block");
  var iframe = document.createElement("iframe"); // create the element
  document.body.appendChild(iframe);  // insert the element to the DOM 
  //addText("<style>.clearedScreen { display: block; }</style>"); //display the cleared text
  iframe.contentWindow.document.write($("#logCopy").html()); // write the HTML to be printed
  iframe.contentWindow.print();  // print it
  //addText("<style>.clearedScreen { display: none; }</style>");//hide cleared text again
  document.body.removeChild(iframe); // remove the iframe from the DOM
  $("#logCopy").remove();
}


clearScreen = function() {
    addText("<hr/>");
    $("#divOutput").append("<hr class=\"clearedAbove\" />");
    addText("<style>.clearedScreen { display: none; }</style>");
    $("#divOutput").children().addClass("clearedScreen");
    $("#divOutput").css("min-height", 0);
    createNewDiv("left");
    beginningOfCurrentTurnScrollPosition = 0;
    setTimeout(function () {
        $("html,body").scrollTop(0);
    }, 100);
}

I have turned this thread into a tangled braid.

I shall break it up into new threads (if worthwhile).


Printing/Saving a transcript

Here is the new thread for printing a transcript:
http://textadventures.co.uk/forum/samples/topic/arefgacf4eotexvb1yjtka/printing-a-transcript


Saving the log as a file as it is written

Visual Studio just laughs at me every time I try modifying anything. (The war isn't over though!)

I will start a new, clean thread if I make any progress.


Creating a RESTART command

Starting a new thread concerning the desktop player soon. Note that mrangel's method works when playing online.


K.V. How many people do you have on your account? You post all the time! I have been having problems with insomnia lately, (just googled it, one of the side effects of melatonin is insomnia if you take too much), but this is ridiculous!


addText("<style>.clearedScreen { display: none; }</style>");

I think there maybe you would want

addText("<style>#divOutput .clearedScreen { display: none; }</style>");

If .clearedScreen is only hidden when it's inside #divOutput, you don't need to change the styles to make it visible before printing.
Also, that line doesn't need to be inside a function, as you only need to print the <style> block once; probably on initialisation.

So that would be something like…

printTranscript = function() {
  var iframe = document.createElement("iframe"); // create the element
  document.body.appendChild(iframe);  // insert the element to the DOM
  iframe.contentWindow.document.write($("#divOutput").html()); // write the HTML to be printed
  iframe.contentWindow.print();  // print it
  document.body.removeChild(iframe); // remove the iframe from the DOM
};

addText("<style>#divOutput > .clearedScreen { display: none; }</style>");

clearScreen = function() {
    addText("<hr/>");
    $("#divOutput").append("<hr class=\"clearedAbove\" />");
    $("#divOutput").children().addClass("clearedScreen");
    $("#divOutput").css("min-height", 0);
    createNewDiv("left");
    beginningOfCurrentTurnScrollPosition = 0;
    setTimeout(function () {
        $("html,body").scrollTop(0);
    }, 100);
};

mrangel,

That modified printTranscript function is very nice! Thanks!

...but the screen doesn't clear when I remove the line from the function.

addText("<style>#divOutput > .clearedScreen { display: none; }</style>");

Is it hurting anything being inside the function?

Or should I make it only run the first time clearScreen is called? (This is how I have it now.)


Here's what works for me:

printTranscript = function() {
  var iframe = document.createElement("iframe"); // create the element
  document.body.appendChild(iframe);  // insert the element to the DOM
  iframe.contentWindow.document.write($("#divOutput").html()); // write the HTML to be printed
  iframe.contentWindow.print();  // print it
  document.body.removeChild(iframe); // remove the iframe from the DOM
}


var clearedOnce = false;

clearScreen = function() {
    addText("<hr/>SCREEN CLEARED");
    $("#divOutput").append("<hr class=\"clearedAbove\" />");
    if (!clearedOnce){
      addText("<style>#divOutput > .clearedScreen { display: none; }</style>");
    }
    clearedOnce = true;
    $("#divOutput").children().addClass("clearedScreen");
    $("#divOutput").css("min-height", 0);
    createNewDiv("left");
    beginningOfCurrentTurnScrollPosition = 0;
    setTimeout(function () {
        $("html,body").scrollTop(0);
    }, 100);
}


jmne,

I have a sleeping problem, but it's that I can't sleep longer than 3 hours at a time. (My back is stupid.)

...and I can usually sleep twice or thrice every 24 hours. So it isn't too bad.

I've kept myself awake for 48 hours before, too -- just see how long I could sleep. Answer: 3 hours.

I thought melatonin was a sleep aid? (Or does too much just have a reverse effect?)

The doctor told me to try it one time. It just made me have Strange Dreams during my 3 hour rests. So, I decided to stop taking it.


I'm not sure when user JS is executed in relation to the standard stuff. Maybe it's being called before addText is defined or something.

Running it only one is probably more efficient. I remember reading that at least one browser had memory issues with duplicate CSS rules; but no idea if that's still the case.

On second thoughts, putting <style> in the document body is quite clunky in any case. It would probably be more elegant to do something like instead of using addText…
$("head").append("<style>#divOutput .clearedScreen { display: none; }</style>");
or even
$("style").last().append("#divOutput .clearedScreen { display: none; }");

Or if you want to get as much code in one line as possible:
$("#clearStyle").length || ($('<style>', {id: "clearStyle"}).text("#divOutput .clearedScreen { display: none; }").appendTo("head"));
(which creates a new <style> element if it doesn't already exist)


mrangel,

Look at you go!

...and yes, please, and thank you for the one-liner! (One-liners are the best.)


Do you prefer the first method you came up with (downloading as text) over this?

It wouldn't be hard to add a prompt, asking whether the player would like to download a .txt file or to print from the HTML with the images and formatting intact.


I'm not sure when user JS is executed in relation to the standard stuff. Maybe it's being called before addText is defined or something.

It seems to run before it even prints the game's title to the screen, but the class doesn't exist until clearScreen runs the first time. (I think this is right.)


Running it only one is probably more efficient. I remember reading that at least one browser had memory issues with duplicate CSS rules; but no idea if that's still the case.

This seems to be true, because there's no mysterious error showing up in the developer console now that I've added that if....


If it's just that the code isn't being executed, it might be worth using ready.

$(function () { some code here }); will cause the function to be executed once, immediately after the document has finished loading. You might not be able to use addText here (depending whether playercore.js is loaded with the page or later), but <head> will certainly exist, so my last one-line version would work.

So maybe you could do:
$(function () { $("style").last().append("#divOutput .clearedScreen { display: none; }"); });
to wait until the document has loaded (including it's built-in style elements), and then add a line to the last <style> that exists.


I added an alert("Setting up clearedScreen class..."); before that line, outside of any function, and the alert fired. I think it's that the class doesn't exist yet, but, if I edit in your revisions (appending everything to the main style element), I think it will be smooth sailing.


jmne,

I have a sleeping problem, but it's that I can't sleep longer than 3 hours at a time. (My back is stupid.)

...and I can usually sleep twice or thrice every 24 hours. So it isn't too bad.

I've kept myself awake for 48 hours before, too -- just see how long I could sleep. Answer: 3 hours.

I thought melatonin was a sleep aid? (Or does too much just have a reverse effect?)

The doctor told me to try it one time. It just made me have Strange Dreams during my 3 hour rests. So, I decided to stop taking it.
blah

Melatonin is a natural chemical. Your body produces it.

It only causes insomnia if you take too much. Normal people are told to take 2-3 mg. I was told to take 10 mg (the largest amount the pill came in), drug therapist. I used to, before, took 15 mg when I needed it. That night however, I took 25 mg. I got 4 hours of sleep after falling asleep when it wore off, at 4:00 a.m. But I had to get up at 8:30, for my grandma. She means well, but she fights over everything, and I can't say no to her. (Stupid grandma)
This talks about insomnia https://vanwinkles.com/the-dark-side-downsides-side-effects-of-melatonin

"When used occasionally and at the correct time, melatonin is a fine means of encouraging sleep. But, ironically, with prolonged use, it can actually amplify insomnia. Having too much melatonin in the system, the theory goes, overwhelms the receptors, changing how a patient reacts to the hormone — whether it’s endogenous or exogenous.

According to Dr. Wurtman, melatonin supplements may work at first, but soon “you’ll stop responding because you desensitize the brain. And as a consequence, not only won’t you respond to the stuff you take…you won’t respond to the stuff you make, so it can actually promote insomnia after a period of time.”

Mauricio Farez, an Argentinian sleep researcher, has similar reservations. “I have some issues, in terms of the pharmacology, and…it’s really hard to have stable levels of the drug in our blood.”"

I might just ask for another sleeping medicine.

Having strange dreams is normal. I can understand not getting sleep to get away from them, but I absolutely need sleep or I get headaches.

What kind of insomnia do you have?


I just can't sleep because of back pain.

I took the melatonin (5 mg) for two or three nights. My dreams were WEIRD. I've been able to take control of my dreams since I was little, so no nightmares, but I was still dreaming up some outlandish stuff.


Another problem I have is thinking about things I haven't finished yet.

...and things I've yet to understand.

...and sometimes the solution to a problem hits me as soon as I'm about to doze off...

...you know, normal stuff, besides the back pain.


I'm about to find out the effect Scotch whisky has.

More on this as it comes in...


PS

The morePrompt and #txtCommand are still kicking my butt, but the problem is not insurmountable. This, I definitely believe.


What kind of back problems? Are you seeing a doctor about it, or just the sleeping issues? I would just buy a temper pedic, the adjustable softness kind (you are an adult... with a job...).


K.V.!


Huh?

What?

I'm awake...

Hello, jmne!

Sorry... I'm currently at war with a JavaScript function...

I carried air-conditioners and heaters around for 15 years and 'wore down' three discs in my lower back.

Bad posture was also a big factor.

Doctor said he can't help me, lest I want pills to mask the pain, which I don't.

I do the exercise thing and all that jazz.

I got an inversion table. I can sleep upside down for quite a while, come to find out. You're not supposed to stay upside down that long though.

Chiropractors get back there, and, every time, they go, "man... Your back is messed up!"

They're scared to crack my back.

I need a backiotomy.

I'm trying to find a way to work online from home right now.


Besides all that, I'm froody.

I'm used to my back, anyway. It's been like that for around a decade.

Also, I sleep in spurts, and it's not like I'm starting a fight club or anything. So that's cool, too.

(I had houseguests keeping me awake for a few months, but that's no longer a thing.)


How are you, jmne?


Good I guess. Sleeping better. Getting ready for Thanksgiving!


jmne,

That's good.

Don't eat too much on Thursday!


The Log:

I still haven't figured out how to make Quest save the log to a .txt file, but I've got this going on just for kicks:

Add to start script:

JS.eval("var logArr = [];logFunc = function(stuff){alert('Logged:\\n'+stuff);logArr.push(Date()+' '+stuff);};viewLog = function(){ addText('<b><h3>Log:</h3></b><hr/>');for (i = 0;i<logArr.length;i++){addText(logArr[i]+'<hr/>');}};")

Modified Log function:

  <function name="Log" parameters="text">
    JS.logFunc (text)
    request (Log, text)
  </function>

Command

  <command name="view_log_cmd">
    <pattern>view log;log</pattern>
    <script>
      JS.viewLog ()
    </script>
  </command>

ain't that the truth... so much food on thanksgiving/etc-holidays... attempted forced gluttony... don't people realize that no one wants to eat that much food and that you always make/have too much food for everyone (don't ever worry about not having enough food, lol)... sighs... resist the iron-cage of society/social-interactions and don't eat too much! lol

messing up the back is the worse, as any movement involves the back, and so with messed up back, you feel pain from any/every movement you do, and it's not really fixable either...


(this is just a general statement of the human population)

unfortunately, lots of people don't know proper techniques and thus hurt their backs. Due to having done personal (proud of the weight I got to for my size, as I'm not that big, sighs) power lifting and knowing physics, I'm an expert on proper technique (99% of people do NOT know how to squat properly: if you've been shown how to squat, you've been shown incorrectly / if you see someone doing a squat or being taught how to do a squat, you're seeing the wrong way/teaching of doing a squat!), but most people are clueless and hurt their backs. With the proper technique, you'll never hurt your back, regardless of the weight (obviously we're talking about weight, aka practical weight, that can be lifted by the person, lol) of the object you're trying to lift. Your 'back' is mainly held up by your spine (along with your muscles to a degree), which is a very thin bone (technically multiple rings of bone on top of each other), which means that it can be easily snapped (like a twig can be), and thus you never want to lift with it, obviously. You lift by using your butt/legs, which are the strongest part of your body. Without getting into the details of the proper technique, there's a simple trick to lift correctly (which will cause you to do all of the many details of the proper technique), which is: simply look up at the ceiling/sky during the entire lifting: never look straight forward and especially never look down. This will cause you to do the proper technique without knowing all of the details of it. It'll keep your back straight and force you to use your butt/legs to lift the object, thus not using your back, and thus not injuring it, as remember, despite how muscular/strong your back seems to be or looks, it's really just that thin spine, which snaps easily like a twig. NEVER EVER EVER bend over/down to lift something up! Remember, LOOK UP during the entire lifting (before you lift the object and during your lifting of the object).

for non-injured people (this may hurt injured people --- I'm not a doctor/physiologist), a really good way to stretch the back (move it into a proper posture), is to do this:

roll your hips forward (holding them there/forward) and slump your upper body and (bend) knees, and hold it like that for a bit

to "roll your hips forward", you move/rotate your lower butt forward, while not moving your waist/belt-line forward, and hold it there for awhile as you slump. Think of it as the opposite of sticking your butt out (especially for girls --- this is really bad for your spine), you want to stick out your front side, lol. (see below)

to 'slump your upper body', you lean forward slightly and loosely / "droop/slump", head looking down (chin against your chest) and shoulders/arms moved forward a bit in front of your body, hanging loosely. As you slump, you remain holding your hips rolled forward. VERY IMPORTANT: bend your knees slightly and relax/be-loose with them (you want to be kinda able to bounce a bit / bend knees and be loose, don't be rigid/locked with straight legs and non-bent knees, you want to be able to bounce a bit up and down on your legs/bent-knees), to produce the full 'slumping' posture/effect.

normal posture of the spine is an 'S', which is bad, you want it to be straight: '|', and the above causes the 'S' of your spine to be straight. Doing this technique should feel really good for a non-injured person, as it's the proper straight-spine posture, unlike a person's normal/natural and bad posture of an 'S' spine.


Yeah.

All that stuff HK said about your back sounds about right...

...but my message to you is this:

No job is more important than your back!

If you mess your back up for the company store, the company store don't want you no more.

They don't want you to hurt your back anyway.

...but they do expect you to get the job done...

So, ask for help. If you don't get help, ask two more times.

If it comes down to you having to choose between quitting your job or messing your back up and losing your job, just quit. The job will be gone either way. You can get a new job.


EDITED OUT RANT


I was dumb. Now my back sucks, and I don't sleep like most folks do.

Besides that, I'm quite content.

Still, don't be like me, kids! Stay in school!


Anyway...

Visual Studio is kicking my butt.

I still can't get it to save the content of the log to a text file.


See next post


See next post


See next post


Success!

I got it to save the log's contents to a .txt file in my Documents directory each time a log entry is created!

Player.vb
Starts at line 1064

    Public Sub Log(text As String) Implements IPlayer.Log
        BeginInvoke(Sub()
                        If m_log Is Nothing Then
                            m_log = New Log
                        End If
                        If m_log.txtLog.TextLength > 0 Then
                            m_log.txtLog.AppendText(Environment.NewLine)
                        End If
                        m_log.txtLog.AppendText(DateTime.Now.ToString() + " " + text)
                        m_log.txtLog.Select(m_log.txtLog.Text.Length, 0)
                        m_log.txtLog.ScrollToCaret()
                        Dim filePath As String
                        Dim logNamePre As String
                        logNamePre = "quest_log_" + Date.Now.ToString("yyyy-MM-dd_HH.mm.ss") + ".txt"
                        filePath = System.IO.Path.Combine(
                        My.Computer.FileSystem.SpecialDirectories.MyDocuments, logNamePre)
                        My.Computer.FileSystem.WriteAllText(filePath, m_log.txtLog.Text, True)
                    End Sub)

    End Sub

The way it stands, it creates a new .txt file in Documents, with all the text you could view when clicking 'Log'.

It creates a new file each time.

I have a LOG #text# command in my test game.

Here's my first file, which was created immediately upon entering the command:log Testing 949am

filename: quest_log2017-11-30_0949_42.txt

contents:

11/30/2017 9:49:42 AM Testing 949am


Then, 25 seconds later, I entered: log testing 950am

That created this file in my Documents directory:

filename: quest_log2017-11-30_0950_07.txt

contents:

11/30/2017 9:49:42 AM Testing 949am
11/30/2017 9:50:07 AM testing 950am


...and it passed all 87 tests.

Whoo-hoo!


Now, if I could figure out how to create a directory named Quest Logs in the user's Documents directory, I think that would suffice.

What say you, Pix?


Now it creates a directory named Quest_logs in Documents (if it doesn't already exist).

It creates a new .txt file each time the Log function is called, which contains the entire contents of the in-game log.

At first, I thought it would be better if it appended the existing file, but what if... anything messed up. It's the log file. I want it, and I may want the old log files.

The files are very small.

Then, I thought I wanted them saved to the game folder, but that wouldn't work when I was playing an actual .quest game, especially if I wasn't the person who created the game.


QUESTION: Do we want our logs saved on the player's hard drive?

Some of us might sometimes, but it would need to be ANNOUNCED LOUDLY:

"IN THIS VERSION OF QUEST, YOUR LOG WILL NOW BE SAVED TO THE PLAYER'S COMPUTER! DON'T WRITE CRAZY STUFF, UNLESS YOU WANT THE WORLD TO SEE IT!"


It passes all 87 tests in the VS solution, too.


image


image


Player.vb
(starting at line 1064)

    Public Sub Log(text As String) Implements IPlayer.Log
        BeginInvoke(Sub()
                        If m_log Is Nothing Then
                            m_log = New Log
                        End If
                        If m_log.txtLog.TextLength > 0 Then
                            m_log.txtLog.AppendText(Environment.NewLine)
                        End If
                        m_log.txtLog.AppendText(DateTime.Now.ToString() + " " + text)
                        m_log.txtLog.Select(m_log.txtLog.Text.Length, 0)
                        m_log.txtLog.ScrollToCaret()
                        Dim filePath As String
                        Dim logNamePre As String
                        Dim logDir As String
                        logDir = My.Computer.FileSystem.SpecialDirectories.MyDocuments + "\Quest_logs"
                        My.Computer.FileSystem.CreateDirectory(logDir)
                        logNamePre = "quest_log_" + Date.Now.ToString("yyyy-MM-dd_HH.mm.ss") + ".txt"
                        filePath = System.IO.Path.Combine(
                        logDir, logNamePre)
                        My.Computer.FileSystem.WriteAllText(filePath, m_log.txtLog.Text, True)
                    End Sub)

    End Sub

Okay, guys,

What am I not testing?

What could this hurt? (Seriously. Not sarcastically.)

It wouldn't effect the web player at all, right?

The web player does its own thing with Log and walkthroughs (which is ignoring them), right?


Now... we need a file read function...


We have that already. (It's in Pixie's Quest.)

It's GetFileData(), I think.


The thing (or one of the things) is, the compiled game is running from inside of a zipped file (I think). So (I think) it acts differently when you're testing in the editor than when you're playing an actual, built game.

Like...

Well I don't know. I'll go play with a few functions and be right back.


DL

Lookie here:

http://textadventures.co.uk/forum/quest/topic/dsm3qslrtkooijastlxgga/reading-from-a-file


Saw that, thanks...


Log in to post a reply.

Support

Forums