Discussion .net community userscript jam #1

Edlit

Hello coders, users and innocent bystanders!

The time has come for our first community event where we together implement one or two ideas from the ideas forum as a userscript, for this first event I have chosen 2 separate ideas that have to do with the daily quests.

The ideas to implement this time are:
Showing #Products in quests & New quest-giver in the saloon for daily quests

What I would like to happen now is that we figure out a way to achieve this together and show as much of the process here on the forums. I will summarize the progress from time to time in code-snapshots for people to try out and give feedback on. (I don't think we need a code-rep atm, but if the need comes up I'll get us one).

Let the coding begin! (ofc we all know to start with design before actually writing any code...)

/Edlit
 

Edlit

Well, if you are not a coder you can help with testing, documentation, cheering and running to the shops to get jolt or other energy drinks!

Atm there isn't much to test or document, so stick with the cheering, etc (Don't forget that forum rules still apply - spam is spam, etc /Mod-Edlit)

/Edlit
 

Slygoxx

The West Team
Marshal
For the number of products, we need inventory data. That should not be that hard, Bag.items.yield[1808].count to show your potatoes :) We do however need to retrieve the item needed for the quest somehow, and then show that data.
 

Edlit

Ok, by inspection it seems the quest requirements are not sent by id:s but by text so this means we have to keep track of every quest and their requirements unless we can search the bag...

So either we have to restrict ourselves to the repeatable quests or implement a text-search in our script.

Either way we must look for when this call is made:
Code:
http://en15.the-west.net/game.php?window=quest&quest_id=10130
(where quest_id is ofc variable depending on the quest at hand)

And the relevant resulting html is:
Code:
<div id="questHead">
      <table class="questPortrait" style="margin: 4px; float: left;display:inline"
      cellspacing="0" cellpadding="0">
        <tr>
          <td style=
          "height:6px; background: no-repeat url(images/border/edge_tl_small.png);"></td>

          <td style="background: repeat-x url(images/border/border_t.png);"></td>

          <td style=
          "background: no-repeat url(images/border/edge_tr_small.png); width:6px; height:6px;">
          </td>
        </tr>

        <tr>
          <td style="background: repeat-y url(images/border/border_l.png); width:6px;">
          </td>

          <td class="shadow_content"><img id="questImage" src=
          "images/quest/employer/sheriff.png" name="questImage" /></td>

          <td style=
          "background: repeat-y url(images/border/border_r.png) right; width:6px;"></td>
        </tr>

        <tr>
          <td style=
          "background: no-repeat url(images/border/edge_bl.png);height:6px; width:6px;">
          </td>

          <td style="background: repeat-x url(images/border/border_b.png)"></td>

          <td style=
          "background: no-repeat url(images/border/edge_br.png); height:6px; width:6px;">
          </td>
        </tr>
      </table>

      <div class='questBar'>
        <div class='questBarOn'></div>

        <div class='questBarOff'></div>

        <div class='questBarOff'></div>

        <div class='questBarOff'></div>

        <div class='questBarOff questBarSpace'></div>

        <div class='questBarOff'></div>

        <div class='questBarOff'></div>

        <div class='questBarOff'></div>

        <div class='questBarOn questBarSpace'></div>

        <div class='questBarOff'></div>

        <div class='questBarOff'></div>

        <div class='questBarOn'></div>

        <div class='questBarOn questBarSpace'></div>

        <div class='questBarOff'></div>

        <div class='questBarOff'></div>

        <div class='questBarOff'></div>

        <div class='questBarOff questBarSpace'></div>
      </div>

      <table class="questPortrait" id="questBox" style="margin: 4px; float: left;"
      cellspacing="0" cellpadding="0">
        <tr>
          <td style=
          "height:6px; background: no-repeat url(images/border/edge_tl_small.png);"></td>

          <td style="background: repeat-x url(images/border/border_t.png);"></td>

          <td style=
          "background: no-repeat url(images/border/edge_tr_small.png); width:6px; height:6px;">
          </td>
        </tr>

        <tr>
          <td style="background: repeat-y url(images/border/border_l.png); width:6px;">
          </td>

          <td class="shadow_content">
            <div id="questDescription">
              <h3 id="questTitle" style="margin: 0 0 1em 6px;">Repeatable quests
              (Chained)</h3>

              <p><strong>Sheriff John Fitzburn:</strong> Good morning, Edlit. News of the
              prison break got out already? No problem, the guy is just a small fish.
              What bothers me more is that he also stole his chains!</p>
            </div>
          </td>

          <td style=
          "background: repeat-y url(images/border/border_r.png) right; width:6px;"></td>
        </tr>

        <tr>
          <td style=
          "background: no-repeat url(images/border/edge_bl.png);height:6px; width:6px;">
          </td>

          <td style="background: repeat-x url(images/border/border_b.png)"></td>

          <td style=
          "background: no-repeat url(images/border/edge_br.png); height:6px; width:6px;">
          </td>
        </tr>
      </table>

      <div style="clear: left;"></div>
    </div>

    <div class="questtargets">
      <p>&nbsp;</p>
    </div>

    <div id="questFoot">
      <strong style="width: 310px; float: left;">Requires:</strong> <strong style=
      "width: 310px; float: left; margin-left: 12px;">Reward:</strong>

      <div style="clear: left; height: 100px; overflow-y: auto;">
        <div id="questRequirements">
          <div class="grey">
            After accepting the quest Repeatable quests (Chained) you must wait 24 hours.
            (Done)
          </div>

          <div>
            Shackle 0/1
          </div>
        </div>

        <div id="questRewards">
          <div id="staticRewards"></div>
        </div>

        <div style="clear: left;"></div>
      </div>
    </div>

    <div class="questButtons">
      <span id="button_quest_10130_accept"><a class="button_wrap button" href=
      "#"><span class="button_middle">Accept quest</span></a></span> <span id=
      "button_quest_10130_finish"><a class="button_wrap button" href="#"><span class=
      "button_middle">Complete quest</span></a></span> <span id=
      "button_quest_10130_cancel"><a class="button_wrap button" href="#"><span class=
      "button_middle">Cancel quest</span></a></span>
    </div>
  </div>
This is the same template for all repeatable quests (except the crafting ones): so the html that needs to be manipulated is:
Code:
document.getElementById('questRequirements').getElementsByTagName('div')[1].innerHTML
Now, as i said either do some regex magic and perform a search or make a table (or something - I'm no js wiz) with quest_id's and yield_id's in it...

/Edlit
 

Slygoxx

The West Team
Marshal
We could make an object with the quest id's and the product it uses, and then retrieve that product based on the quest id, edit the html to include the amount of products you have, and that'd be it I guess.

Not the way I would like it to happen, but it seems like there is no other way as there is no item id in the html we can use.
 

Edlit

Well... we have:
Code:
for (var product_id in Bag.items.yield) {
    if (Bag.items.yield[product_id].obj.name == "Whiskey") {
        alert (Bag.items.yield[product_id].count);
    }
};
And...
Code:
document.getElementById('questRequirements').getElementsByTagName('div')[1].innerHTML
That for the repeatable quests returns something like (with an optional '(Done)') - or really it doesn't return anything but an error, but the overall idea still stands ;)
Code:
    Shackle 0/1
or
Code:
    Transport Ammunition (15 minutes)

So... something like...
Code:
var requirement = document.getElementById('questRequirements').getElementsByTagName('div')[1].innerHTML;

if (requirement.indexOf('/') > -1) {
    product_name = requirement.trim().slice(0, requirement.trim().lastIndexOf(' '));
}

for (var product_id in Bag.items.yield) {
    if (Bag.items.yield[product_id].obj.name == product_name) {
        // alert (Bag.items.yield[product_id].count);
        // do something useful here...
    }
};
Although the first part needs someone with better understanding of js, etc to give it some love...

/Edlit
 

Slygoxx

The West Team
Marshal
So basically loop through all the items you have, and check if that item is in there. Should work. I'll probably be able to create something this evening :p
 

rice farmer

All this programming stuff is over my head, but I assume you're getting the html code from the console?
 

Edlit

All this programming stuff is over my head, but I assume you're getting the html code from the console?
Well, personally I'm using FireBug (a mozilla add-on) that I'm used to check out code with - but you can probably use the console too...

/Edlit
 

Slygoxx

The West Team
Marshal
I use both Firebug for Chrome, and Chrome's built-in console. Some things are better viewable with firebug, others with the console.

Also, there can be multiple requirements, so we need to loop through the requirements as well :) Should be easy enough though.

Currently programming this btw. If you want to track the progress, http://userscripts.org/scripts/show/127422
The javascript file is located here: http://www.westgadgets.net/qreq.js
 
Last edited:

Edlit

Btw, there can be multiple requirements, so we need to loop through the requirements as well :) Should be easy enough though.
Yup, we have to figure out distinct characteristics of every kind of requirement. I think the '/' for the items is distinct enough but the problem is that we can only be sure that they are products for the repeatable quests - other quests have all kind of equipments as requirements...

/Edlit
 

Edlit

Well, if I am correct, and the way I plan to do it works, there should be no issue with multiple requirements.

Edit: I'm done. Please test, script can be installed here: http://userscripts.org/scripts/show/127422
Ok, tested and I can confirm Elmyr's assessment - it works beautifully and by looking at the code I don't see anything that could go wrong.

Nicely done Sly!

Now, on to the other part - moving all repeatable saloon quests to the clock or cranium...

/Edlit
 

Slygoxx

The West Team
Marshal
For those wondering how (a certain aspect of) the script works, or for those who want to learn some more about scripting, I'll break down the code :)

First, the entire script.

Code:
function qReq()
{
if (window.document.getElementById('questFoot') && Bag.loaded)
{
var divcount = window.document.getElementById('questRequirements').getElementsByTagName('div').length;
for (var loopy = 0;loopy < divcount;loopy++)
{
var questreq = window.document.getElementById('questRequirements').getElementsByTagName('div')[loopy].innerHTML;
var prodamount = 0;
var reqsplit = questreq.split(' ');
var itemreq = "";
var loopyz=0;
var slashcount = 0;
var slashfound = false;

for (var r=0;r<reqsplit.length;r++)
{
	if (reqsplit[r].indexOf('/') < 0 && !slashfound)
	{
		if(r == 0)
		itemreq += reqsplit[r];
		else
		itemreq += ' ' + reqsplit[r];
	}
	else if (!slashfound)
	{
	slashcount = r;
	slashfound = true;
	}
}

for (var product_id in Bag.items.yield) {
    if (Bag.items.yield[product_id].obj.name == itemreq) {
        prodamount = Bag.items.yield[product_id].count;
    }
};

var otherreq = reqsplit[slashcount];
if(reqsplit[slashcount+1])
otherreq += ' ' + reqsplit[slashcount+1];

//alert (itemreq + ', ' + prodamount + ', ' + slashcount);

if (prodamount != 0)
window.document.getElementById('questRequirements').getElementsByTagName('div')[loopy].innerHTML = itemreq + ' (' + prodamount + ') ' + otherreq;


}
}
setTimeout(function(){qReq()},2000);
}

setTimeout(function(){qReq()},3000);

function loadBag()
{
if(!Bag.loaded)
Bag.loadItems();
}

setTimeout(function(){loadBag()},8000);
Now, breaking it down into sensible parts :)

Code:
function qReq()
{
if (window.document.getElementById('questFoot') && Bag.loaded)
{
function qReq() creates the function, easy enough. The second part checks if something with the id 'questFoot' exists, and if the bag has been loaded. The script does not work when the bag is not loaded, so it doesn't make sense to continue if it has not yet been loaded. 'questFoot' is a specific element that only appears in quests, so it basically checks if a quest window has been opened.

Code:
var divcount = window.document.getElementById('questRequirements').getElementsByTagName('div').length;
for (var loopy = 0;loopy < divcount;loopy++)
{
This gets the amount of quest requirements, as there can be multiple requirements. It then creates a loop that will loop through all of the requirements, and execute the code below on each of them.

Code:
var questreq = window.document.getElementById('questRequirements').getElementsByTagName('div')[loopy].innerHTML;
var prodamount = 0;
var reqsplit = questreq.split(' ');
var itemreq = "";
var loopyz=0;
var slashcount = 0;
var slashfound = false;
The first line makes 'questreq' contain the quest requirement we're working on. var reqsplit = questreq.split(' '); splits that requirement whenever a space occurs. For example, "Warm meal 0/1" will become a collection that contains "Warm, meal, 0/1". You can then select a part of that, reqsplit[0] is "Warm", reqsplit[1] is "meal", etc. Notice it starts with 0.
The rest of the code just sets some variables we are going to use later.

Code:
for (var r=0;r<reqsplit.length;r++)
{
	if (reqsplit[r].indexOf('/') < 0 && !slashfound)
	{
		if(r == 0)
		itemreq += reqsplit[r];
		else
		itemreq += ' ' + reqsplit[r];
	}
	else if (!slashfound)
	{
	slashcount = r;
	slashfound = true;
	}
}
A loop again (loopception!), this one checks each of the elements in reqsplit. If that element does not contain a '/', the result of reqsplit[r].indexOf('/') will be -1, and as such, smaller than 0. If it does not contain a '/' and it has not yet been found, we will continue.
If it's the first word, we do not want to add a space, which is what the if(r == 0) does. If it is any other word, we do want to add a space.
If the word does contain a '/', and it has not yet been found, we make the variable 'slashcount' equal to the word we were working on, so we can add it back later. We also set the variable 'slashfound' to true, so it will not check any more words.

Code:
for (var product_id in Bag.items.yield) {
    if (Bag.items.yield[product_id].obj.name == itemreq) {
        prodamount = Bag.items.yield[product_id].count;
    }
};
This part loops through all products in your inventory, and checks if the name of that product equals the name we are looking for. If any match is found, 'prodamount' is set to the amount you have of that product. If no match is found, 'prodamount' is not set, and remains 0, which is what we declared in the beginning of the code.

Code:
var otherreq = reqsplit[slashcount];
if(reqsplit[slashcount+1])
otherreq += ' ' + reqsplit[slashcount+1];

//alert (itemreq + ', ' + prodamount + ', ' + slashcount);

if (prodamount != 0)
window.document.getElementById('questRequirements').getElementsByTagName('div')[loopy].innerHTML = itemreq + ' (' + prodamount + ') ' + otherreq;
This adds the part of 'reqsplit' wherever the '/' occured to a variable, so we can add it back later. If there is also some text behind that, particularly the '(Done)' part when you've already completed an objective, it adds that to the variable as well.
The alert is just a debugging alert I used to test the code, not much use now though.
If the amount of products is not 0, in other words, if the product was found, we change the quest requirement's text. If the product was not found, we do not change anything. This works, because when the text is not a product, the product could not be found, and the amount is thus still 0. itemreq is the item, prodamount is the amount you have of that item, and otherreq is the part described above.

Code:
}
}
setTimeout(function(){qReq()},2000);
}

setTimeout(function(){qReq()},3000);

function loadBag()
{
if(!Bag.loaded)
Bag.loadItems();
}

setTimeout(function(){loadBag()},8000);
We then have a couple of closing tags, closing the loop and the if statement (in that order).
setTimeout(function(){qReq()},2000); calls the function every 2 seconds. Basically, the function calls itself every 2 seconds, so we loop the entire function. Then we have the final closing tag, closing this function.
Next up is the code that starts the script, 3 seconds after loading the page. The script calls itself, but it needs to be executed first.

At last have the function that loads the bag if it has not already been loaded otherwise. This function is called 8 seconds after the page has been loaded.

That's it. If you still have questions, just ask :)
 

Slygoxx

The West Team
Marshal
Now, on to the other part - moving all repeatable saloon quests to the clock or cranium...
How will we do that though? We could make something clickable in the saloon. We could then open our own quest window, but then what? We need to fill it with the repeatable quests somehow. I have looked, and I could not find any data about available quests. We could generate our own data based on the player's level and the day, but then there is no way we could see if the quest has already been completed.
 

Edlit

setTimeout(function(){qReq()},2000); calls the function every 2 seconds. Basically, the function calls itself every 2 seconds, so we loop the entire function. Then we have the final closing tag, closing this function.
Next up is the code that starts the script, 3 seconds after loading the page. The script calls itself, but it needs to be executed first.

At last have the function that loads the bag if it has not already been loaded otherwise. This function is called 8 seconds after the page has been loaded.

That's it. If you still have questions, just ask :)
Ok, I actually have a question about this timing stuff... would it be possible to attach the execution of qReq() to an event instead? and would that be of any real benefit?

/Edlit
 
Last edited by a moderator:

Slygoxx

The West Team
Marshal
It could be possible. However, the benefits are small as the script doesn't really make anything slower.
 

Edlit

slygoxx said:
How will we do that though? We could make something clickable in the saloon. We could then open our own quest window, but then what? We need to fill it with the repeatable quests somehow. I have looked, and I could not find any data about available quests. We could generate our own data based on the player's level and the day, but then there is no way we could see if the quest has already been completed.
Well, we can find out if a quest is completed or not by looking at the image used in it's detail window or at a quest-givers list. We can even see if a certain questgiver in the saloon has any available quests by looking at the images for ! & ? and correlating their absolute position with pre-made list.

Using the info about the players level and what day it is we could pre-generate a list of quests that could be available each day and then by adding even listeners to the 'complete quest' determine if they are completed this day - storing info in a cookie or such.

We could also remove the repeatable quests from the saloon questgivers after you have clocked on them, but it will be more problematic to change the ! & ? in the saloon view.

Or am I hoping for too much?

/Edlit