ID:2065711
 
(See the best response by D4RK3 54B3R.)
Code:
mob
proc
UpdateInventory()
var/c = 0
var/r = 1
winset(usr,"inventory",{"cells="5x5""})
for(var/obj/O in src.inventory)
if(c==5)
r+=1
c=0
src << output(O,"inventory:[++c],[r]")


Problem description:

Hi everyone,

I've been using BYOND on and off since 2004 and have been programming a bit here and there making some fun little projects...

My latest project is one where you can control the users inventory onscreen easily for the user by clicking on the object and having a menu appear above it.

https://gyazo.com/da9c3465df99d4d0e935d0779fad8a3b

My problem is with the Grid inventory, I've scoured the forums and google as much as I can (like 7 hours over the last 2 days) trying to figure out a nice way to make it work.

I feel like the idiot I am... anyway, my issue is that the Grid control doesn't seem very flexible. Like if i wanted to have a background image behind the icons, or have a mouse over description on the inventory icons to show the obj's variables I haven't really found a way to do that.

Can anyone recommend a good tutorial or a way of handling inventory that is color/image customizable, and has alot of the features of inventory systems in modern rpg systems..

I've looked at hudgroups, and a bunch of other demos but can't seem to make heads or tails of it... (thus me feeling really dumb)

Anyway, if anyone is experienced in these things I wouldn't mind even paying for a couple hours of someone's time to teach me what I'm missing!

Thanks!!
Best response
You can have background images behind icons, and you can have mouse-over descriptions, in grids. You can also fix the grid size so it doesn't do weird scaling stuff when you don't have anything in that cell.

For displaying mouse-over descriptions for your inventory, you have three different ways of going about it: DMF, html5, and client.screen. DMF is probably the worst option of the three.

See if you were going to stick to using DMF grids...
You can capture mouseEntered events in the grid, which is nice, but you can't have a tooltip that hovers over the grid itself without doing something super hacky.
The closest thing you can do is to make an item description pane adjacent to the grid and build the tooltip (label text and images and so forth) according to the mouseEntered and mouseExited events.

Example 1: In this example, the menus are grids, the item icon is a grid, and the text boxes are labels.

Example 2: In this example, the inventory on the bottom right is a grid.

Additionally, dynamic DMF stuff is usually very laggy. In the first example, the GUI can take over 0.1 seconds to update, and is very unresponsive.


So one of the other ways you can go about this, is to replicate Doohl's html5 work in Severed World. This approach is webclient-only, as I understand it. I'm not knowledgeable on how to pull this off, so Doohl would be the one to poke for help with this. Doohl did write some introductory writeups for interfacing HTML5 and the BYOND webclient, but it seems to be incomplete...


The last alternative is to just stick to using client.screen objects.
I realize this is the most traditional approach, but I like having dreamseeker compatibility, and despite being the most traditional, it can still be very good looking.

Example 3

Example 4

The downside here is, this still won't perform as well as an html5 GUI, and maptext bounds are still a bit funky to try to size, since you can't accurately compute the size of the rendered text.
Wow, amazing examples.

Thanks so much for the great reply D4RK3 54B3R.

I've taken Doohl's Web Client tutorials, really wish there was a lesson 3... I'll try to figure out what's next on my own and hope that works out.

I'm pretty familiar with JS/JQuery/JSON/CSS/HTML5 so I think this is the route I want to go! I'll give it a shot... if I ever figure it out i'll produce a tutorial lol
One thing you can do here is modify the grid control for the webclient for your own purposes, and make it a new control type. You can give your webclient version a custom skin that uses this special control, while DS can have standard grids as a fallback.

You could also output things differently depending on the value of client.connection, so for instance if you have a custom webclient control that can use JSON, you can get everything sent in one single message.
Good to know Lummox... Once i'm better i'm sure i'll understand what you're talking about lol... Being such a noob is tough... just gunna keep grinding my brains out.

This is where i'm at so far with the web client!

https://gyazo.com/9dbfb1faf7d9bb2dc943c53b8d90e917

Was able to make a div sit on top of the map and the gray box that has "yayayay" is going to be the inventory... Just trying to figure out how to pass a list of obj's into the inventory box.. I can add the names of each item in the list but I cannot seem to clear the list?!

This is my code so far..

<byondclass name="bags">

<!-- Our CSS styling goes here -->

<style>
#bags {
width: 200px;
height: 200px;

position: absolute;
left: calc(90% - 200px/2);
top: calc(20% - 200px/2);
color: #fff;
background: #444444;
box-shadow: 0px 0px 5px #00FF00;
border-radius: 5px;

padding: 15px;
text-align: center;
}

.button {
width: 80px;
height: 25px;

margin: auto;
border: 1px solid grey;
background: #BABABA;
}
</style>

<!-- Our JavaScript code goes here -->

<script>
{
fn: {
output: function(obj) {
if(obj.value == "") {
this.ui.inv.innerHTML = "";
}
if(obj.hasOwnProperty("text")) {
this.ui.inv.innerHTML += obj.text + "<br>";
}
}
}
}
</script>

<div id="inv" class="inv"></div>

</byondclass>


mob/verb
bags()
//var/count = length(src.inventory)
for(var/obj/O in src.inventory)
src << output(O.name,"bags")
clearinv()
src << output("","bags")
after another 3 hours of trying to figure it out...

I think I came up with a hack-job of a solution..

https://gyazo.com/70aef5b1d8911b653cf6a28574cd73d1

        bags(mob/M as mob)
//var/count = length(src.inventory)
//var/params = list2params(usr.inventory)
var/html = ""

for(var/obj/O in M.inventory)
html += "<b>[O.name] </b>: [O.quality]<br>"
// << params
M << output(html,"bags")
M << html


Is this how i'm supposed to insert an inventory into a div? by making the HTML markup in DM then outputing to the innerHTML of that DIV?

Seems to work but I'm not sure how to get the Icon to show, or to reference a click back to the server if i clicked on an item in the div since it's just text...?

ps. I'm still serious about paying someone (well) to teach me for an hour or two!
If this is for a webclient control, I'd just build the HTML in JS.
Yeah I tried that but how do I get an inventory list of objs into a readable list by JS? I tried list2params but it just give me "the+stone" and none of the other properties of the objs in inventory
        bags(mob/M as mob)
//var/count = length(src.inventory)
//var/params = list2params(usr.inventory)
var/html = ""

for(var/obj/O in M.inventory)
html += "<div class=item data-ref=\ref[O] >[O.name] : [O.quality]</div><br>"
// << params
M << output(html,"bags")

<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
</head>
<script>
(function(){
var execute_look = function(pos) {
byond.fn.topic("action=look;value=" + pos);
};

return {
fn: {
create: function() {
function init_ui() {

$(".item").click(function() {
execute_look($(this).data("ref"));
alert($(this).data("ref"));
});
//byond.fn.topic("action=ui_ready");
}

(function check_jquery() {
setTimeout(function() {
if(window.jQuery) {
init_ui();

} else {
check_jquery();
}
}, 500)
})();
},
output: function(obj,sub) {
//this.elem.innerHTML = obj.null ? '' : obj.text;
$("#inventory").html("test" + obj.text);
}
}
};
})()
</script>


Why doesn't this work?

I'm trying to build the code off of Doohl's example..
Well, three problems here:

- Custom .dms controls don't have a <head> section.

- init_ui() is adding a click() to all elements with the item class, not just to ones under this element. (For that you'd want $(".item",this.top) instead. Although you'd probably need a var in the closure to hold the value of this.)

- init_ui() is called on creation, not on output, so there are no .item elements to find.

Seems like this would be cleaner:
<script>
(function(){
function clickhandler(e) {
byond.fn.topic("action=look;value=" + e.target.getAttribute("data-ref"));
e.stopPropagation();
e.preventDefault();
};

return {
fn: {
output: function(obj,sub) {
var e = this.elem, a, i;
e.innerHTML = obj.null ? '' : obj.text;
a = e.querySelectorAll('.item');
for(i=a.length; i--;) a[i].addEventListener('click', clickhandler);
}
}
};
})()
</script>
AHh okay, that makes sense.

Thanks so much Lummox... However the click handler doesnt seem to be working... it's converting the <divs> in the HTML i pass to the output function to spans...

https://gyazo.com/fa3b5b5e2969ce6850727021f3b57a70

Is that why the click handling is not working?
Oh, that actually makes sense. All output gets filtered.

But I did suggest in the first place that you don't build the HTML on the server end. Rather, it makes more sense to build the HTML on the JS end.
Yeah I tried that... but I'm not sure how to pass all the object's properties to the JS...

M << output(list2params(M.inventory),"bags")

Gives me: the+stone&the+stone...

If I try to make a loop through the inventory I can really only send 1 variable..

How can I send a list to JS and have JS parse the list?

Thank you again and I'm sorry im an idiot :(
You probably want json_encode() instead, but I wouldn't pass in the inventory directly.
Ahhh I was just trying

                for (var key in obj) {
alert(JSON.stringify(obj[key]));
}


When I read this.. whew! I'm making progress lol thanks again!
There are a couple of ways to go about this. The webclient should let you do a list itself as the output, so you could try this:

player << output(inventory, "bags")

Then in the JS for the control, you'd look for obj.list and handle that accordingly. Each member of obj.list is an object like output() would normally handle, so it would have text, atom, icon, etc. as members. Look at web\output.dms in your BYOND installation for guidance on that one.

If you wanted to go the JSON route, here's roughly what I would suggest for your inventory:

var/list/items = new
for(var/obj/O in inventory)
items[++items.len] = list(\
"name" = O.name,
"icon" = O.icon,
"ref" = "\ref[O]")
player << output(json_encode(items), "bags")

Note that when you have JSON from the output, a <br/> tag will get appended, so you'll just want to snip that off in the JS before you parse it.
IT WORKS!!!!!!! :D

After like 13 hours of grinding my stupid brain over this I got it! Thanks to you Lummox JR! I'm like little-girl-at-a-beiber-concert-excited. lol

Anywhere here it is in action:
https://gyazo.com/a2d8940db56b0fdaf7844e7546234301

And here's the JS:

<script>
(function(){
function clickhandler(e) {
byond.fn.topic("action=look;value=" + e.target.getAttribute("data-ref"));
e.stopPropagation();
e.preventDefault();
};

return {
fn: {
output: function(obj,sub) {
var e = this.elem, a, i, html;
// decode and fix up the json_encode'd list from the output
var string = byond.htmlDecode(obj.text);
string = string.replace(/\n+$/,'');
string = string.replace(/^\n+/,'');
string = string.replace('< br/>',''); // space in the BR/ so i can paste here
// make a new array that will parse the fixed up string from above
var inv_array = JSON.parse(string);
html = '';
// loop through the parsed array to find the objects
for(var key in inv_array) {
if(string.hasOwnProperty(key)) {
// find the first key and parse it too, so that we can dig into the items actual data
var item = JSON.stringify(inv_array[key]);
item = JSON.parse(item);

// add the item's properties into it's own div!
html += '<div data-ref='+item.ref+' class=item>'+item.quality+'</div>';
}
}
//console.log(html);
e.innerHTML = html;
a = e.querySelectorAll('.item');
for(i=a.length; i--;) a[i].addEventListener('click', clickhandler);

}
}
};
})()
</script>
Minor nit: I'd change string.replace('<br/>','') to string.replace(/<br\s*\/?>/gi,'') instead. Although the server is producing the BR tag with the slash, and in lowercase, it's always good to build in a little robustness.

I'm glad you got that all working. I've found basic JS fairly easy to work with for controls, not really requiring any jQuery, since many of the tasks it was best at are well-supported in modern browsers now.
        bags(mob/M as mob)
var/list/items = new
for(var/obj/O in M.inventory)
items[++items.len] = list(\
"name" = O.name,
"icon" = O.icon,
"icon_state" = O.icon_state,
"quality" = O.quality,
"ref" = "\ref[O]")
M << output(json_encode(items), "bags")


One last question I promise!

What's the best way to handle icons to the divs? I could have 100000 .dmi files, but is there a way to pass icon state through the JSON to display in an <img tag as well?

Or is it better to host a local web server and reference the "icon_states.png" that way?
Hrm, it occurs to me I may have steered you wrong on the "name"=O.icon part, since a cache reference isn't going to translate as anything useful.

If the atom is already known to the webclient, then probably the easiest way to get the icon is through this:

var id = item.ref, m, icon='';
if(id && (m=id.match(/\[0x([0-9a-f]+)\]/i))) {
id = parseInt(m[1],16);
icon = '<img atom='+id+'>';
}

You'd call byond.fillAtomIcons() after you created all the HTML, so that it would look for any images with an atom=xxxx attribute and fill them in.

If the atom is not known to the webclient--this is possible if it's not in your direct inventory or on the visible map--then you need to send it to the control.

M << output(M.inventory, "bags")   // send the inventory list
M << output(json_encode(items), "bags") // send the JSON

You'll want to ignore the obj.list case, and for extra safety you could always send the items to bags:list and simply ignore any output when the "sub" argument is set.
Page: 1 2