ID:1884728
 
Something I do for pretty much any project I'm working on is plugging in a simple command line parser I wrote a while back. It's main function was in-game variable editing without having to exit the game and recompile. There are many ways to do this, but I find a simple command line interface to be the easiest, fastest and broadest solution.

Click Target handling. This example uses any clicks to set the targeting, but you can edit it to only function on right clicks, for example. You can also toggle it on and off or just use a different method of setting targets. Either way, it's an option.
client
var/atom/__cmd_target
var/__click_target = 1
Click(object,location,control,params)
..()
if(__click_target)
__cmd_target = object
src << "Target set to [__cmd_target]"
verb
toggleClickTarget()
if(__click_target)
__click_target=0
__cmd_target = mob
else __click_target=1
src << "Click targeting [__click_target?"activated":"deactivated"]."


Command parsing
mob
proc
__command_line(t as text)
t = lowertext(t) //Makes the text nice and uniform.
var/list/token = tokenize(t) //Tokenizes the string into separate entities to be parsed.
if(!token) return //If there are no tokens to parse, just exit.

switch(token[1])
if("set") //Set is the only command in this example, but it's designed to be easily expandable if you need other commands such as /who, /ban or whatever you need.
if(token.len<3||token.len>4) //Set has 3 to 4 arguments.
//Call error
return
switch(token.len)
if(3) //No defined target. Target is self.
if(findtext(token[2],"client.")==1)
token[2] = copytext(token[2],8)
for(var/v in client.vars) //Parsing client variables
if(v==token[2])
//This whole section just formats token[3] from text into the proper variable.
switch(token[3])
if("null") token[3] = null
if("true") token[3] = TRUE
if("false") token[3] = FALSE
if(isnum(client.vars[v])) token[3] = text2num(token[3])
else if(ispath(client.vars[v])) token[3] = text2path(token[3])

try
client.vars[v] = token[3] //The try-catch block is to make sure that the token variable is compatible with the variable being edited. Obviously you can't expect to replace a list with a string.
catch
//Call error
return
if(client.vars[v]!=token[3])
//Call error
return
src << "\[*\] Variable <tt>client.[v]</tt> has been set to [client.vars[v]]"
return
for(var/v in vars) //parsing player variables
if(v==token[2])
switch(token[3])
if("null") token[3] = null
if("true") token[3] = TRUE
if("false") token[3] = FALSE
if(isnum(vars[v])) token[3] = text2num(token[3])
else if(ispath(vars[v])) token[3] = text2path(token[3])
try
vars[v] = token[3]
catch
//Call error
return
if(vars[v]!=token[3]) //A redundant double-check to make sure that it was actually changed properly.
//Call error
return
src << "\[*\] Variable <tt>[v]</tt> has been set to <tt>[vars[v]]</tt>"
return
//Call error
if(4) //Defined target
if(token[2]=="~") //By default, the only extra argument the code looks for is "~", but this can be expanded
//on by accepting a player's key and searching for them to use as a target instead.
if(!client.__cmd_target) //Make sure the __cmd_target var isn't null, or it'll throw a runtime error.
//Call error
return
for(var/v in client.__cmd_target.vars) //parsing player variables
if(v==token[3])
switch(token[4])
if("null") token[4] = null
if("true") token[4] = TRUE
if("false") token[4] = FALSE
if(isnum(client.__cmd_target.vars[v])) token[4] = text2num(token[4])
else if(ispath(client.__cmd_target.vars[v])) token[4] = text2path(token[4])
try
client.__cmd_target.vars[v] = token[4]
catch
//Call error
return
if(vars[v]!=token[4])
//Call error
return
src << "\[*\] Variable <tt>[v]</tt> for [client.__cmd_target.] has been set to <tt>[client.__cmd_target.vars[v]]</tt>"
return

tokenize(t as text)
var/list/token = new
var/i=1
for(var/pos=1;pos<=length(t);pos++)
if(pos==length(t)) //Caps off the last token and return the function
token.len++
token[i] = t
break
switch(text2ascii(t,pos))
if(32) //Whitespace
token.len++
token[i] = lowertext(copytext(t,1,pos))
t = copytext(t,++pos)
i++
pos=0
continue
if(34) //Quotation mark
var/end = findtext(t,"\"",pos+1)
if(!end) return null
token.len++
token[i] = copytext(t,2,end)
t = copytext(t,end+2)
i++
pos=0
continue
return token


Alright, so we actually have the basis for the command line. The tokenize() function breaks up text by whitespace or quotation marks. Using set, you can choose to specify a target or not. If you don't explicitly define your target, it defaults to you. In this example, the only explicit definition allowed is using "~" to default to your current __cmd_target variable. You can easily add on to check for a player's ckey as well, letting you edit another player's variables.

Now we just need to tie that to a chat function to make sure that it can be used. I usually use "/" as the macro character.
var/const/MACRO_CHAR = "/"

//Example
Whatever_Chat_Function_Youre_Using(t as text)
//Add this to it
if(copytext(t,1,2)==MACRO_CHAR) //Just checks of the first character is the macro character.
__command_line(copytext(t,2))
//Chat output stuff.


This whole snippet lets you do interesting and very quick things such as using /set density 0 to make yourself non dense or /set ~ icon_state "foobar 2" to change what things look like on the fly. Just something to help you when you're testing things out in-game.

EDIT: Something I should have made sure to note is that you can access both mob variables and client variables independently. To access client variables, simply use client.[variable] for the variable argument. It's a hard-coded solution inside, but it works well at least.

Alternately, I really should move the __command_line() function to a client, rather than a mob, but I'll probably update it later.
Hey Kats, if you like command line interfaces you might be interested in checking this out:

http://www.byond.com/developer/AlexPeterson/Interpreter

I wrote it on an ALT account of mine. It can interpret plain English sentences or straight forward commands.
Also, to add (I double post a lot). I did mine in an object orientated way to avoid the mammoth amount if-else code you get when you do it in a single function.

I find it more readable.
The main function probably could stand to be broken down into separate functions to make things simpler to read. Unfortunately, like most of my snippet posts, it's an adjusted copy-paste from my own source code.

The only in-else statement blocks I have are more for determining what kind of variable the target var is and adjusting the token to match that format. I wanted to see if there was a better way of handling it, but there's unfortunately no function to just return what type of variable it is. Otherwise that'd be nice.

The main goal of this is to just help with editing internal variables faster. I'm sure there are plenty of better ways and, if I really needed a natural language interpreter, I could do it. This was more of a simple, plug-and-play source I use a lot to make my life easier when debugging and testing. Lol.

Thank you for the input, though.

EDIT: Wait, just found a place where I could clean it up a bit more.
In response to Kats
Ha yeah I know what you mean. C.L.I is pretty highly specialized. It took me years before I finally figured out how to make that. I don't think anyone has even ever used it but it's amazing for text games :)
Just finished altering the jumbled token checking into a switch statement. Not sure why I didn't catch myself if-elseing sooner. Lol. I'm such a noob scrub.

Also, I can't say how much I love having try-catch blocks now. It makes my life so much easier.
Check out DM.stdlib here:

http://www.byond.com/ forum/?post=1872243&page=2#comment15565770

It has a tokenizer that's going to be faster than the one you are using.
In response to Ter13
Why exactly is that version faster, if you don't mind me asking. I mean, other than the fact it's using findtext() instead of iterating through each character in the string.

I understand that iterating is not nearly as fast as findtext(), but it made the solution I was using less roundabout. At first, when I was only checking for whitespace as a delimiter, I was using findtext(), but once I decided to account for quotation marks to denote token blocks, I found iteration a bit simpler.
Your tokenizer is pretty inefficient o-o , could be so much faster using ascii ^_^ It's what I did for something similar - http://www.byond.com/forum/?post=1832115
In response to Kozuma3
Unfortunately
Looks cool! Although my brain is unable to comprehend that much logic in one screenshot.

But it does look cool. Got the source code to it?
In response to Zecronious
Zecronious wrote:
Unfortunately
Looks cool! Although my brain is unable to comprehend that much logic in one screenshot.

But it does look cool. Got the source code to it?

Of coarse, just haven't touched after I got bored :B
In response to Kozuma3
Haha, been there. Many many times.
In response to Kozuma3
Like I'm asking, what makes it inefficient? Especially what makes it inefficient in respect to a single-line command interface. Any "optimizations" done will be absolutely negligible in the grand scheme of things.

I know that optimization is some holy grail, but in functions that are called a few times at most and aren't called with any relative frequency, making it faster might only improve it by a few milliseconds at best.

Make it work, THEN make it fast, and even then, only if it needs to be faster. There's very little sense in optimizing code that isn't a factor in any bottlenecks. The ratio of work to actual progress just means you're doing a whole lot of work for not a whole lot or progress.

But, I guess it wouldn't be a programmers forum if people weren't always worried about it being faster for no reason.
In response to Kats
BYONDers are OCD about efficiency, it's just a thing. If you can scratch off 5 nano seconds from a proc that will only ever be called 10 times then you're a hero around here.

My standpoint is functionality being the holy grail and readability being it's partner. Only after those two do I consider efficiency.

Bit of a rant but half the reason nothing ever gets done on BYOND is people spend too much time nit picking stupid stuff.


In response to Zecronious
Zecronious wrote:
BYONDers are OCD about efficiency, it's just a thing. If you can scratch off 5 nano seconds from a proc that will only ever be called 10 times then you're a hero around here.

That's a habit that gets broken really quick when you're programming on a deadline. In fact, the only time I care about readability is that it's fine as long as I can read it, since most of the time, I'm the only one messing with the code anyway.

Programming a tokenizer for a single-line function is very different from programming one for a full scripting language.
In response to Kats
Ah.. That's a really bad habit to get into Kats. In the real world if you program in a company you'll be writing code that will most likely be maintained by someone else. Getting into the habit of writing code that only you can read... Not good.

I like to release most of what I do so I'm in the habit of trying to make things as readable as possible. I'd hate to get a team of people together to write a game and then I can't understand what they're doing or them me.
In response to Zecronious
I know, and it's not like I have any trouble programming readable code for a team. I used to work as an application developer for a local company, but when it's just me working on my projects, I'm not too picky about it.
Make it work, THEN make it fast, and even then, only if it needs to be faster. There's very little sense in optimizing code that isn't a factor in any bottlenecks. The ratio of work to actual progress just means you're doing a whole lot of work for not a whole lot or progress.

I agree, but if you already have a tokenizer that's optimal at your disposal, why reinvent the square wheel?

The major issue is that you are using copytext() to create single-character strings, which is slow. text2ascii() is faster because it doesn't actually modify the string table.

The way you are doing things right now is generate a string per character, which is causing heavy hits on the string table.

Optimization is a non-issue for me. I don't spend all of my time making micro-optimizations on my code because I understand how BYOND works internally and am able to plan things ahead of time to be fast.

I was just throwing it out there. I didn't want to start a debate about it. Just showing you that there was a highly optimal tokenizer already out there for you that didn't require iteration through the entire string one character at a time and also only creates the minimum required number of strings.
In response to Ter13
Ah, I see what you're talking about with text2ascii() now. Haven't read the documentation on it or had any reason to use it for a while now. If does seem like a cleaner way of doing it, at least.
I adjusted the code the tokenizer to use text2ascii() rather than copytext(). Works just fine and doesn't appear any faster. Lol. At least it looks a little better at least.
Page: 1 2