Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

[Help] Setting character bio issues.

So I'm trying to feed something back in to set the bio field of a character (A shop) And it seems to work perfectly fine, I can get everything to log properly and ultimately end up with a string that I want. But, after everything is said and done when I do shop.set("bio", "This string"); I get an error; TypeError: Cannot read property 'length' of null at evalmachine.<anonymous>:2207:39 at eval ( This is how I set up the bio field. Item Shop Inventory --item|+3 Longsword --value|3300 --received|1500101 --material|0 --enchant|3 --sold|No --item|+2 Armor --value|2150 --received|1500101 --material|0 --enchant|2 --sold|No But for everything I use .length on, has a value. It will only throw the error if I try and set the bio field. (Doesn't matter what the string is) If I comment the line out, everything goes back to working fine. I'm so close to getting a working shop for the party up and running. Here's the code. var Shop = Shop || {}; on("chat:message", function (msg) { if (msg.type != "api") return; var command = msg.content.split(" ", 1); if (command == "!runShop") { var shop = findObjs({ _type: "character", name: "Shop" })[0]; shopAttr = function(shopAttrName) { return findObjs({ _type: "attribute", _characterid: shop.get("_id"), name: shopAttrName })[0]; }; shop.get("bio", function(bio) { var bioCopy = bio.replace(/ --/g, "--"); log(bioCopy); var itemIndex = bioCopy.match(/--item.*?(?=<br>|$)/g); log(itemIndex); //Create a list of all the items in the shop var shopItems = []; for ( var i=0; i<itemIndex.length; i++) { var n = itemIndex[i].split("--"); var a = 1; var chatObj = {}; var mTags = ""; var mContent = ""; while(n[a]) { mTag = n[a].substring(0,n[a].indexOf("|")); mContent = n[a].substring(n[a].indexOf("|") + 1); chatObj[mTag] = mContent; a++; }; shopItems.push(chatObj); }; log(shopItems); //Load calander and get date var calander = findObjs ({_type: "character", name: "*Calander"})[0].get("_id"); var calanderAttrName = ["Year", "Month", "Weekday", "Day"]; var calanderValue = []; var dateLine = ""; for (var i=0;i<calanderAttrName.length;i++){ var calanderAttrValue = findObjs({_type:"attribute", _characterid:calander, name:calanderAttrName[i]})[0].get("current") + ""; if (calanderAttrName[i] != "Weekday") { if (parseInt(calanderAttrValue) < 10) {var calanderAttrValue = "0" + calanderAttrValue;}; dateLine = dateLine + calanderAttrValue; }; calanderValue.push(parseInt(calanderAttrValue)); }; dateLine = parseInt(dateLine); log(calanderValue); log(dateLine); var soldItemsValue = 0; var shopSold = shopAttr("Sold"); var shopSold_Cur = parseInt(shopSold.get("current")); var shopLevel = shopAttr("Level"); var shopLevel_Cur = parseInt(shopLevel.get("current")); var updatedBio = "Item Shop Inventory<br>"; log(shopItems.length); for (var i=0; i<shopItems.length; i++){ //Is item sold? if (shopItems[i].sold != "No") continue; //Otherwise, find sale date var itemMaterial = parseInt(shopItems[i].material); var itemEnchant = parseInt(shopItems[i].enchant); var forsaleDays = (((5 + itemMaterial ) * itemEnchant ) / ((shopLevel_Cur) / 5 )); log(forsaleDays); var forsaleMonths = 0; while(forsaleDays > 30) { forsaleDays -= 30; forsaleMonths += 100; }; var forsaleTime = forsaleMonths + forsaleDays; log(forsaleTime); var received = parseInt(shopItems[i].received); var bestBefore = received + forsaleTime; log(bestBefore); if (dateLine >= bestBefore) { shopItems[i].sold = calanderValue[3] + "." + calanderValue[1] + "." + calanderValue[0]; soldItemsValue = parseInt(shopItems[i].value); log(soldItemsValue); shopSold_Cur += soldItemsValue; }; log(shopItems[i]); updatedBio += "--item|" + shopItems[i].item + " --value|" + shopItems[i].value + " --received|" + shopItems[i].received + " --material|" + shopItems[i].material + " --enchant|" + shopItems[i].enchant + " --sold|" + shopItems[i].sold + "<br>"; }; log(shopItems); log(updatedBio); log(shopSold_Cur); //My problem child. Even if I set the string to "This is a string" it still gives the error. shop.set("bio", updatedBio); }); }; });
From a cursory view, it might have to do with trying to update the bio within the callback of the bio. Try doing shop.set('gmnotes',updatedBio) instead and see if that works just to confirm. If it still fails then it's something with your logic.
Just checked it with gmnotes. Works perfectly, so the logic works out in the end. Also tried to pull the line outside of the bio field. Said updatedBio was undefined. So I defined it outside of the field, but the stuff within the bio field aren't getting added to the string. (hope that makes sense)
1426603196
Lithl
Pro
Sheet Author
API Scripter
Kerberos said: Just checked it with gmnotes. Works perfectly, so the logic works out in the end. Also tried to pull the line outside of the bio field. Said updatedBio was undefined. So I defined it outside of the field, but the stuff within the bio field aren't getting added to the string. (hope that makes sense) That's because getting the content of the bio field is an asynchronous operation. It hasn't finished yet by the time you try to set the value if you just move the set call outside the callback. All solutions to the asynchronous problem are basically what you've already got, though: calling set at the end of the callback.
Alright, I think I understand. Basically if I want to change anything in the bio I have to put the set call inside of the callback for the bio field. So any idea why it works perfectly with gmnotes, but if I try it with bio it gives me the length - null error?
Kerberos said: So any idea why it works perfectly with gmnotes, but if I try it with bio it gives me the length - null error? Ken L. said: From a cursory view, it might have to do with trying to update the bio within the callback of the bio. From my point of view without understanding the exact back-end, There's something wonky happening where the 'bio' part of the data-structure is nulled during the callback (i'm not even sure if this is the actual behavior given I haven't checked this myself). If you want to achieve the same effect, you can store this configuration information in 'gmnotes' and have the script write to 'bio' and bypass this quandrum.
So, tried like you said and did this with my code. shop.get("bio", function(bio) { /*Stuff and things */ shop.set("gmnotes", updatedBio); }); shop.get("gmnotes", function(gmnotes) { log(gmnotes); shop.set("bio", gmnotes); shop.set("gmnotes", ""); }); Which, as far as I can tell should write everything to the gmnotes field (which we confirmed before that it works). Then it should stop playing with the bio, look up gmnotes, copy gmnotes text into bio field, then wipe out gmnotes (because there's no point in having multiple copies of the same text, we're just using it as a placeholder.) Unfortunately I got the same error as before. TypeError: Cannot read property 'length' of null at evalmachine. :2207:39 at eval ( If there is any more info you need that'd help you diagnose this I'd be more than willing to supply.
Actually, I meant rather than parsing the bio section, just put that store config information in the gmnotes section, use the callback on reading gmnotes, and set the bio field.
I'm not entirely sure what you mean anymore. Isn't that basically the little bit I just added or...?
1426618663

Edited 1426619016
No, just change the datafield and write to the bio. var Shop = Shop || {}; on("chat:message", function (msg) { . . . if (command == "!runShop") { . . . shop.get("gmnotes", function(notes) { . . . shop.set("bio", updatedBio); }); }; });
Unfortunately, one of the reasons I was doing it the way I was is that I need to write over the value of the "sold" tag if the item has been sold.That way when the shop next iterates through, it can tell if an item has been sold or not and skip it. While I could probably punch up something to get it working, it would probably be a very obtuse workaround and if this problem starts persisting for every overcomplicated process I do using the bio field, I don't want to have to find a workaround for each new issue.
In that event, just use setTimeout(<time-delay-in-milliseconds>,<callback function>) and stuff an anonymous callback function in there. Just be aware if people buy things within close enough intervals you're going to have problems since you're delaying your write. setTimeout(200, function() { shop.set('bio',updatedBio); });
Based on various experiments, I think the best way to solve this problem would be to pull a variable from within the callback function. var shopBio = ""; shop.get("bio", function(bio) { log(bio); shopBio = bio; }); log(shopBio); After a few dozen variations on this, I still cannot get the result I'm looking for. As far as I can tell, the callback function is the last thing performed. Because when I call the log it comes out: "" "Bio field" If I'm wrong on either theory, let me know.
1426678941
Lithl
Pro
Sheet Author
API Scripter
Kerberos said: Based on various experiments, I think the best way to solve this problem would be to pull a variable from within the callback function. var shopBio = ""; shop.get("bio", function(bio) { log(bio); shopBio = bio; }); log(shopBio); After a few dozen variations on this, I still cannot get the result I'm looking for. As far as I can tell, the callback function is the last thing performed. Because when I call the log it comes out: "" "Bio field" If I'm wrong on either theory, let me know. As I said earlier, getting the value of the bio field is an asynchronous operation. That means Roll20 will continue with the rest of your script while it works on getting the bio for you at the same time. When it's finally done with that process, the callback fires. It's not that the callback is always the "last thing performed" -- if you had some long-running process that takes place after you ask for the bio value, you might see the callback run in the middle of that process. The callback runs whenever the API server pulls up your bio information; it just doesn't stop and wait before continuing with the rest of your code. Any pattern used to ensure the async operation has completed so that you know you have your information basically boils down to calling the rest of your code from the end of the callback. There are some implementation that are a bit more complicated than that, but that's all they are at their core.
I am proud to announce, thanks to Brian's explanation about the order of operations and Ken's setTimeout idea I get the stupid thing working finally. I just set a variable inside of the bio callback function, then transposed the rest of my code into the timeout function. So it has 200ms to pull the bio string before doing anything with it. I would like to thank you both for putting up with me and I know how long it can take me to grasp something. I shall take what I have learned and go forward to make better code.