TeensyMud - 'A ruby mud server.'

Subject: Displaying descs based on properties
Subject: Displaying descs based on properties
Author: Massaria
Posted: 01/27/2006 11:08AM


I'm trying to work out a good way to implement the level of awareness that a character has. A blind character, for example, shouldn't be able to see anything.
While this 'yes/no' scenario would be easy enough to implement, I'm looking for more detail, allowing characters not only to have eyes or not, but also to have good eyes or bad eyes.

The way I see it, so far without too much consideration, there are a few different ways to achieve this:
1) In-desc checks, where I'd put a check into the description itself, allowing the following text to show only if the character's eyes are good enough to pass the test,
2) Have several descs for the sense, calling the descs only if some method says that the character has that particular level of sensitivity, or
3) Use the farts system in some way.

I have no clue whether 1 and 3 is at all possible in tmud, and 2 appears very labour and memory intensive. Perhaps I've missed some options?

In any case, what I'm looking for is some advice on how you would go about doing this.

Thanks
Tobias.

reply
Subject: Displaying descs based on properties
Author: Tyche
Posted: 01/28/2006 05:23PM

Massaria wrote:
> I have no clue whether 1 and 3 is at all possible in tmud, and 2 appears very labour and memory intensive. Perhaps I've missed some options?
>
> In any case, what I'm looking for is some advice on how you would go about doing this.

I do it rather oddly now. In the look command I issue events for each room and object to describe itself. The main reason for that was to test the event system actually. If you do Player#sendto there you'll find those messages appearing first. But back to the question...

I don't see any other way than to have the room/object itself construct a modified description based on the viewer/player. So it needs to be able to query information about the viewer's state to decide what and what not to reveal or put in the description.

TML is intended to be one easy way of doing that. You could extend it to use something like [!colorblind]blah blah something colorful[/colorblind] or [detecthidden 50]show hidden stuffs[/detecthidden].

In any case I don't how one gets around putting much of the onus on the builder to write conditional descriptions.






reply
Subject: Displaying descs based on properties
Author: Massaria
Posted: 01/30/2006 08:59AM

Tyche wrote:
> I don't see any other way than to have the room/object itself construct a
> modified description based on the viewer/player. So it needs to be able to > query information about the viewer's state to decide what and what not to
> reveal or put in the description.
>
> TML is intended to be one easy way of doing that. You could extend it to
> use something like [!colorblind]blah blah something colorful[/colorblind] or
> [detecthidden 50]show hidden stuffs[/detecthidden].

I read up on the TML, and while it provides amble examples of how to use the existing options, it says very little about how to extend it - with the [CLASS] for example, or with your own methods.

I went and looked at Room.rb:
def ass(e)
    case e.kind
    when :describe
      msg = "[COLOR=green](#{id.to_s}) #{name}[/COLOR]\n#{desc}\n"
      $engine.world.eventmgr.add_event(id,e.from,:show,msg)
      fart(e)
...


and I understand that the #{desc} part would pick up my [thingies] and parse them along with the colors. Then I took a brief look at the files in the protocol directory, and promptly came here - that stuff is way over my head.

Could you give me an example of how to write up either a [CLASS] or a 'custom', assuming I have a player property like
senses = {"vision" => 10}
and want to show some part of the desc to players who has a minimum of 10 in vision?

Thanks in advance,
Mass.

reply
Subject: Displaying descs based on properties
Author: Tyche
Posted: 01/30/2006 12:55PM

Massaria wrote:
>
> I read up on the TML, and while it provides amble examples of how to use the existing options, it says very little about how to extend it - with the [CLASS] for example, or with your own methods.

Everything that TML currently implements is in the Protocol Filters, terminalfilter.rb and colorfilter.rb The codes used in vt100codes.rb and /vendor/bbcode.rb. CLASS is not implemented but would probably be part of the ZMP protocol (and Pueblo or MXP if I do them). It's really intended for protocols which allow the client to distinguish between classes of messages, so it can format them how it wants.

Since the kind of markup you are doing is not network/client dependent at all but game dependent you would do it before you send it on the the network.

Just some thoughts and sample code on how it might be done.
http://sourcery.dyndns.org/wiki.cgi?DynamicDescription?



reply
Subject: Displaying descs based on properties
Author: Massaria
Posted: 02/02/2006 09:11AM

Tyche wrote:

> Just some thoughts and sample code on how it might be done.
> http://sourcery.dyndns.org/wiki.cgi?DynamicDescription?

I think I understand most of the ruby code shown there, but I can't figure out how to mix in the method that will check whether the senses of the player is good enough to see the hidden thing. I mean, this code only check for a sort of 'yes/no' state - either showing or not showing, depending on whether or not a 'stat' or property is set on the player - not 'how good' the property is.

These two lines is where my chain jumps off:
  pf.each {|k| str.gsub!(/\[#{k}\](.*?)\[\/#{k}\]/mi,'\1')}
  Keywords.each {|k| str.gsub!(/\[#{k}\].*?\[\/#{k}\]/mi,'')}


I understand as much as this is where the real 'magic' is happening, but I'm very uncertain what exactly is happening. I think the first lines picks up whatever is between the matching brackets, or perhaps it removes what is between those that don't match, and the second line removes the brackets themselves.
But how on earth I'd pick up a certain kind of bracket and pass the associated 'difficulty' of the check to a method, which would then allow or disallow the match, is beyond me.

I'd normally try out some kind of testing before coming here, but I have no idea where or how to start. Please help.

Thanks
Mass.


reply
Subject: Displaying descs based on properties
Author: Tyche
Posted: 02/02/2006 10:23PM

Massaria wrote:
>
>   pf.each {|k| str.gsub!(/\[#{k}\](.*?)\[\/#{k}\]/mi,'\1')}
>   Keywords.each {|k| str.gsub!(/\[#{k}\].*?\[\/#{k}\]/mi,'')}
> 


Both lines will remove all the markup tags, but the first one keeps the text between the tags while the second one doesn't.

The first line looks through the list of keywords that are true, `pf` built on the fly from examining the player. It's got a grouping (.*?) though, so the matched text between the markup will be returned in $1. gsub() is odd in that the second parameter, the substitution has to use the \1 notation for $1 and be in single quotes for it to work.

The second line looks through all the keywords and strips everything out including the markup tags and text between them.

Since the first line already stripped out and substituted for things that are true, the second actually ends up cleaning up everything that was false.

Yes the keywords in that code are simple positive/false matches.

To implement something more complex like [level 5]...
[will continue later after coffee]




reply
Subject: Displaying descs based on properties
Author: Massaria
Posted: 02/03/2006 07:21AM

Tyche wrote:
> Massaria wrote:
> >
> >   pf.each {|k| str.gsub!(/\[#{k}\](.*?)\[\/#{k}\]/mi,'\1')}
> >   Keywords.each {|k| str.gsub!(/\[#{k}\].*?\[\/#{k}\]/mi,'')}
> > 

>
> To implement something more complex like [level 5]...
> [will continue later after coffee]
>

... you'd capture the brackets and call a method if it include an integer?

something like:

pf.each {|k| str.gsub!(/\([#{k}\])(.*?)\[\/#{k}\]/mi,'\2')}
"if $1 contains integer, call so-and-so method" ...?

Heh *blush*
Yeah, still need help :-)
Mass.


reply
Subject: Displaying descs based on properties
Author: Tyche
Posted: 02/04/2006 02:26AM

Massaria wrote:
>
> ... you'd capture the brackets and call a method if it include an integer?
>
> something like:
>
> pf.each {|k| str.gsub!(/\([#{k}\])(.*?)\[\/#{k}\]/mi,'\2')}
> "if $1 contains integer, call so-and-so method" ...?

Sorry it was a long cup of coffee. But yes, you'd have a another class of Keywords that took parameters, as opposed to those that don't.

For example:
# a simple dynamic description markup processor
require 'pp'
require 'ostruct';
Keywords = ['level', 'foo']

Desc=<<-EOD
You are a [level 4+]high[/level][level 2-]low[/level]
[level 3]mid[/level] level player.
[foo]not visible[/foo]
[foo 1-]not visible[/foo]
[foo 33]not visible[/foo]
[foo 10+]not visible[/foo]
[level]not visible[/level]
EOD

def show(s, p)
  str=s.dup
  Keywords.each do |k|
    str.gsub!(/\[#{k}\s*?(\d*)?([+-]?)\](.*?)\[\/#{k}\]/mi) do
#      pp $1, $2, $3
      case $2
      when "+"
        $3 if $1 != "" && p.send(k) && p.send(k) >= $1.to_i
      when "-"
        $3 if $1 != "" && p.send(k) && p.send(k) <= $1.to_i
      when ""
        $3 if $1 != "" && p.send(k) && p.send(k) == $1.to_i
      else
        ""
      end
    end
  end
  Keywords.each {|k| str.gsub!(/\[#{k}\s*?\d*?[+-]?\].*?\[\/#{k}\]/mi,'')}
  str = wordwrap(str, 40)
end

def wordwrap(s, len)
  str = s
  str.gsub!(/\n/,' ');str.squeeze!(' ')
  str.gsub!(/(\S{#{len}})(?=\S)/,'\1 ')
  str.scan(/(.{1,#{len}})(?:\s+|$)/).flatten.join("\n")
end

def mockplayer
  p = OpenStruct.new
  p.level=rand(5)
  p
end

10.times do
  p=mockplayer
  puts "Level:#{p.level}"
  puts show(Desc,p)
  puts
end




I refactored the stuff that does wordwrap out to it's own function. I'm also using the version of gsub! that takes a single parameter and a block. When using a block in gsub you use the $1 instead of \1 notation. But the return value at the end of the block is what gets substituted in for the match.

The above produces something like:
Level:3
You are a mid level player. 
Level:1
You are a low level player. 
Level:4
You are a high level player. 
Level:1
You are a low level player. 
Level:0
You are a low level player. 
...





In the article I mentioned I didn't like the idea of turning builders into coders. However there's another way that fully embraces embedding ruby code into the descriptions themselves.
# a simple dynamic description markup processor
require 'ostruct';
require 'erb';
Termwidth=[40,60,80];Sex=%w(male female);Race=%w(human elf fairy)
TOD = %w(day night)

Desc=<<-EOD
You are in a <% if p.race=="fairy" %>huge<% end %> cavern.
<% if p.tod=="day" %>Shafts of light descend from
an opening high in the cavern ceiling.<% end %> Writhing on the cavern floor
is a decaying corpse writhing with maggots. Feasting on the corpse is a
<% if p.race=="elf" %>foul-smelling<% end %>
<% if p.race=="fairy" %>disgusting<% end %>
<% if p.race=="human" %>hideous<% end %> hobbit.
<% if p.sex=="female" %> You shriek hysterically. <% end %>
EOD

def show(s, p)
  str=s.dup
  t = ERB.new(str,3)
  str = t.result(binding)
  str = wordwrap(str, p.tw)
end

def wordwrap(s, len)
  str = s
  str.gsub!(/\n/,' ');str.squeeze!(' ')
  str.gsub!(/(\S{#{len}})(?=\S)/,'\1 ')
  str.scan(/(.{1,#{len}})(?:\s+|$)/).flatten.join("\n")
end

def mockplayer
  p = OpenStruct.new
  p.sex=Sex[rand(Sex.size)]
  p.race=Race[rand(Race.size)]
  p.tod=TOD[rand(TOD.size)]
  p.tw=Termwidth[rand(Termwidth.size)]
  p
end

5.times do
  p=mockplayer
  puts "Sex:#{p.sex} Race:#{p.race} TOD:#{p.tod} TW:#{p.tw}"
  puts show(Desc,p)
  puts
end


That's the first example using ERB, or ERuby, which is how this site's web pages are built. It can be used for any sort of generic templating. It's very similar to how PHP is embedded, and is quite ugly. You can use everything you can in regular ruby. However you can only access variables that are in scope at the time of the call (see binding). As for security and safety, I believe you can set safe levels but I've never explored it.

Jon


reply
Subject: Displaying descs based on properties
Author: Massaria
Posted: 02/04/2006 09:38AM

> In the article I mentioned I didn't like the idea of turning builders into coders. However there's another way that fully embraces embedding ruby code into the descriptions themselves.

Awesome!
I've got this working in the basic 'mock player' way you've written up here, but I'm having problems getting a hold of the player object.
When I try to do

p = YamlStore?.get(e.from)

I get told

06-02-04 14:56:21 [FATAL] (Engine) Caught NoMethodError?: undefined method `get' for YamlStore?:Class
./lib/db/room.rb:74:in `ass'
tmud.rb:213:in `run'
tmud.rb:311

... which is kinda frustrating when I'm sitting here staring at it.

What I'd like to be able to do is something like this:
msg = "[COLOR=green](#{id.to_s}) #{name}[/COLOR]\n"
msg << parse_desc(self.desc, make_struct(e.from))  #parse_desc is your 'show'
...
def make_struct(id)
  p = YamlStore.get(id)
  p_info = OpenStruct.new
  p_info.name = p.name
  p_info.vision = p.senses["vision"]
  ...etc etc
end


This is hard when I can't find the get method - any clue why that is?

Also, I had a security issue with erb ( "t = ERB.new(str,3)" ):

06-02-04 15:10:50 [FATAL] (Engine) Caught SecurityError?: Insecure operation - eval
/usr/local/lib/ruby/1.8/erb.rb:737:in `eval'
/usr/local/lib/ruby/1.8/erb.rb:739:in `value'
/usr/local/lib/ruby/1.8/erb.rb:739:in `result'
./lib/db/room.rb:128:in `parse_desc'
./lib/db/room.rb:76:in `ass'
tmud.rb:213:in `run'
tmud.rb:311

I changed the security level to '0' and that did the trick, but is it something that should concern me? I can't find or figure out what it really means.

Thanks for this great functionality!
Mass.

reply
Subject: Displaying descs based on properties
Author: Tyche
Posted: 02/04/2006 11:47AM

Massaria wrote:
> p = YamlStore?.get(e.from)

There are no class/singleton methods defined on YamlStore?, only instance methods. The instance of the database in 2.7.0 is `db` on World.

p = $engine.world.db.get(e.from)

In the HEAD revision its on Engine either...
Engine.instance.db.get(e.from)
or
get_object(e.from)

If you have a player, `p` there's probably no reason to build a mockup player struct. What I mean is the code examples are just takeoffs on the generic Ruby/C++ code examples.

> Also, I had a security issue with erb ( "t = ERB.new(str,3)" ):
...
> I changed the security level to '0' and that did the trick, but is it something that should concern me? I can't find or figure out what it really means.

Probably, as anyone who can create descriptions (@set desc) can put ruby code in them that executes on your machine.

http://www.ruby-doc.org/docs/ProgrammingRuby?/html/taint.html#S1
http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/index.html

There is no security in TeensyMud right now.


reply