Acme-MUDLike
view release on metacpan or search on metacpan
lib/Acme/MUDLike.pm view on Meta::CPAN
sub header {
qq{
<html><head>
<script src="/jquery.js" type="text/javascript"></script>
<script src="/chat.js" type="text/javascript"></script>
</head><body>
};
}
sub footer { qq{</body></html>\n}; }
sub login {
my $request = shift;
#
# per-user variables
#
my $player;
# STDERR->print("debug: " . $request->request->url->path . "\n"); # XXX
# STDERR->print("debug: " . $request->request->as_string . "\n"); # XXX
$SIG{PIPE} = 'IGNORE'; # XXX not helping at all. grr.
#
# static files
#
if($request->request->url->path eq '/chat.js') {
# warn "handling chat.js XXX: ". $request->request->url->path;
$request->print(Acme::MUDLike::data->chat_js());
return;
} elsif($request->request->url->path eq '/jquery.js') {
# warn "handling jquery.js XXX: ". $request->request->url->path;
$request->print(Acme::MUDLike::data->jquery());
return;
}
#
# login
#
while(1) {
my $nick_tmp = $request->param('nick');
my $admin_tmp = $request->param('admin');
if(defined($nick_tmp) and defined($admin_tmp) and $nick_tmp =~ m/^[a-z]{2,20}$/i and $admin_tmp eq $password) {
my $nick = $nick_tmp;
$player = $players->named($nick) || $players->insert(Acme::MUDLike::player->new(name => $nick), );
$player->request = $request;
# @_ = ($player, $request,); goto &{Acme::MUDLike::player->can('command')};
$player->command($request); # doesn't return
}
# warn "trying login again XXX";
$nick_tmp ||= ''; $admin_tmp ||= '';
$nick_tmp =~ s/[^a-z]//gi; $admin_tmp =~ s/[^a-z0-9]//gi;
$request->print(
header, # $msg,
qq{
<form method="post" action="/">
<input type="text" name="nick" value="$nick_tmp"> <-- nickname<br>
<input type="password" name="admin" value="$admin_tmp"> <-- admin password<br>
<input type="submit" value="Enter"><br>
</form>
},
footer,
);
$request->next();
}
}
#
# object
#
package Acme::MUDLike::object;
sub new { my $package = shift; bless { @_ }, $package; }
sub name :lvalue { $_[0]->{name} }
sub environment :lvalue { $_[0]->{environment} }
sub use { }
sub player { 0 }
sub desc { }
sub tell_object { }
sub get { 1 } # may be picked up
sub id { 0 }
#
# inventory
#
package Acme::MUDLike::inventory;
sub new {
# subclass this to build little container classes or create instances of it directly
my $package = shift; bless [ ], $package;
}
sub delete {
my $self = shift;
my $name = shift;
for my $i (0..$#$self) {
return splice @$self, $i, 1, () if $self->[$i]->id($name);
}
}
sub insert {
my $self = shift;
my $ob = shift;
UNIVERSAL::isa($ob, 'Acme::MUDLike::object') or Carp::confess('lit: ' . $ob . ' ref: ' . ref($ob));
push @$self, $ob;
$ob->environment = $self;
$ob;
}
sub named {
my $self = shift;
my $name = shift;
for my $i (@$self) {
return $i if $i->id($name);
}
lib/Acme/MUDLike.pm view on Meta::CPAN
my $pack = shift;
bless {
inventory => Acme::MUDLike::inventory->new,
messages => [ ],
@_,
}, $pack;
}
sub request :lvalue { $_[0]->{request} }
sub id { $_[0]->{name} eq $_[1] or $_[0] eq $_[1] }
sub name { $_[0]->{name} }
sub password { $_[0]->{password} }
sub x :lvalue { $_[0]->{x} }
sub y :lvalue { $_[0]->{y} }
sub xy { $_[0]->{x}, $_[0]->{y} }
sub get { 0; } # can't be picked up
sub inventory { $_[0]->{inventory} }
sub evalcode :lvalue { $_[0]->{evalcode } }
sub current_item :lvalue { $_[0]->{current_item} }
sub tell_object {
my $self = shift;
my $msg = shift;
push @{$self->{messages}}, $msg;
shift @{$self->{messages}} if @{$self->{messages}} > 100;
$got_message = 1; # XXX wish this didn't happen for each player but only once after all players got their message
}
sub get_html_messages {
my $self = shift;
return join "<br>\n", map { s{<}{\<}gs; s{\n}{<br>\n}g; $_ } $self->get_messages;
}
sub get_messages {
my $self = shift;
my @ret;
# this is written out long because I keep changing it around
for my $i (1..20) {
exists $self->{messages}->[-$i] or last;
my $msg = $self->{messages}->[-$i];
push @ret, $msg;
}
return reverse @ret;
}
sub header () { Acme::MUDLike::header() }
sub footer () { Acme::MUDLike::footer() }
sub command {
my $self = shift;
my $request = shift;
# this is called by login() immediately after verifying credientials
if($request->request->url->path =~ m/pushstream/) {
# warn "pushstream path_session handling XXX";
my $w = Coro::Event->var(var => \$got_message, poll => 'w');
while(1) {
$w->next;
# warn "got_message diddled XXX";
# on submitting the form without a JS background post, the poll HTTP connection gets broken
$SIG{PIPE} = 'IGNORE';
$request->print( join "<br>\n", map { s{<}{\<}gs; s{\n}{<br>\n}g; $_ } $self->get_messages );
$request->next;
}
}
if($request->request->url->path =~ m/sendmessage/) {
while(1) {
# warn "sendmessage path_session handling XXX";
my $msg = $request->param('message');
$self->parse_command($msg);
# $request->print("Got message.\n");
$request->print($self->get_html_messages());
$request->next;
}
}
#
# players get three execution contexts:
# * one for AJAX message posts without header/footer in the reply
# * one for COMET message pulls
# * the main HTML one below (which might only run once); arbitrarily selected as being the main one cuz its longest
#
$floor->insert($self);
while(1) {
$request->print(header);
#
# chat/commands
#
if($request->param('action') and $request->param('action') eq 'chat') {
# chat messages first so they appear in the log below
# there's only one action defined right now -- chat. everything else hangs off of that.
my $msg = $request->param('message');
$self->parse_command($msg);
};
do {
$request->print(qq{
<b>Chat/Command:</b>
<form method="post" id="f" action="/">
<input type="hidden" name="action" value="chat">
<input type="hidden" id="nick" name="nick" value="@{[ $self->name ]}">
<input type="hidden" id="admin" name="admin" value="$password">
<input type="text" id="message" name="message" size="50">
<!-- <input type="submit" name="sendbutton" value="Send" id="sendbutton"> -->
<input type="submit" name="sendbutton" value="Send" id="sendbutton">
<span id="status"></span>
</form>
<br>
<div id="log">@{[ $self->get_html_messages ]}</div>
});
};
} continue {
$request->print(footer);
$request->next();
} # end while
}
sub parse_command {
my $self = shift;
my $msg = shift;
warn "parse_command: msg: ``$msg''";
$self->tell_object("> $msg");
if($msg and $msg =~ m{^/}) {
my @args = split / /, $msg;
(my $cmd) = shift(@args) =~ m{/(\w+)};
# XXX I'd like to see template matching, like V N A N, then preact/act/postact
if( $self->can("_$cmd") ) {
eval { $self->can("_$cmd")->($self, @args); 1; } or $self->tell_object("Error in command: ``$@''.");
} else {
$self->tell_object("No such command: $cmd.");
}
} elsif($msg) {
$floor->tell_object($self->name . ': ' . $msg); # XXX should be $self->environment->tell_object
# $request->print("Got it!\n");
}
}
sub item_by_arg {
my $self = shift;
my $item = shift;
my $ob;
return $self->current_item if $item eq 'current';
if($item =~ m/^\d+$/) {
my @stuff = $self->environment->contents;
$ob = $stuff[$item] if $item < @stuff;
}
$ob or $ob = $self->inventory->named($item); # thing in our inventory with that name
$ob or $ob = $self->environment->named($item); # thing in our environment with that name
$ob or $ob = $item if exists &{$item.'::new'}; # raw package name
$ob or do {
# Foo::Bar=HASH(0x812ea54)
my $hex;
($hex) = $item =~ m{^[a-z][a-z_:]+\((0x[0-9a-z]+)\)}i;
$hex or ($hex) = $item =~ m{^0x([0-9a-z]+)}i;
if($hex) {
$ob = Devel::Pointer::deref(hex($hex));
}
};
return $ob;
}
# actions
sub _call {
my $self = shift;
# XXX call a method an in object
# XXX call sword name
my $item = shift;
my $func = shift;
my @args = @_; # XXX for each arg, go through the item finding code below, except keep identify if not found
my $ob = $self->item_by_arg($item) or do {
$self->tell_object("call: no item by that name/number/package name here");
return;
};
for my $i (0..$#args) {
my $x = $self->item_by_arg($args[$i]);
$args[$i] = $x if $x;
}
$ob->can($func) or do {
$self->tell_object("call: item ``$item'' has no ``$func'' method");
return;
};
$self->tell_object(join '', "Call: ", eval { $ob->can($func)->($ob, @args); } || "Error: ``$@''.");
1;
}
lib/Acme/MUDLike.pm view on Meta::CPAN
.each( callback, [res.responseText, status, res] );
else
callback.apply( self, [res.responseText, status, res] );
}
});
return this;
},
serialize: function() {
return jQuery.param( this );
},
evalScripts: function() {
return this.find("script").each(function(){
if ( this.src )
jQuery.getScript( this.src );
else
jQuery.globalEval( this.text || this.textContent || this.innerHTML || "" );
}).end();
}
});
// If IE is used, create a wrapper for the XMLHttpRequest object
if ( !window.XMLHttpRequest )
XMLHttpRequest = function(){
return new ActiveXObject("Microsoft.XMLHTTP");
};
// Attach a bunch of functions for handling common AJAX events
jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){
jQuery.fn[o] = function(f){
return this.bind(o, f);
};
});
jQuery.extend({
get: function( url, data, callback, type, ifModified ) {
// shift arguments if data argument was ommited
if ( jQuery.isFunction( data ) ) {
callback = data;
data = null;
}
return jQuery.ajax({
url: url,
data: data,
success: callback,
dataType: type,
ifModified: ifModified
});
},
getIfModified: function( url, data, callback, type ) {
return jQuery.get(url, data, callback, type, 1);
},
getScript: function( url, callback ) {
return jQuery.get(url, null, callback, "script");
},
getJSON: function( url, data, callback ) {
return jQuery.get(url, data, callback, "json");
},
post: function( url, data, callback, type ) {
if ( jQuery.isFunction( data ) ) {
callback = data;
data = {};
}
return jQuery.ajax({
type: "POST",
url: url,
data: data,
success: callback,
dataType: type
});
},
// timeout (ms)
//timeout: 0,
ajaxTimeout: function( timeout ) {
jQuery.ajaxSettings.timeout = timeout;
},
ajaxSetup: function( settings ) {
jQuery.extend( jQuery.ajaxSettings, settings );
},
ajaxSettings: {
global: true,
type: "GET",
timeout: 0,
contentType: "application/x-www-form-urlencoded",
processData: true,
async: true,
data: null
},
// Last-Modified header cache for next request
lastModified: {},
ajax: function( s ) {
// TODO introduce global settings, allowing the client to modify them for all requests, not only timeout
s = jQuery.extend({}, jQuery.ajaxSettings, s);
// if data available
if ( s.data ) {
// convert data if not already a string
if (s.processData && typeof s.data != "string")
s.data = jQuery.param(s.data);
// append data to url for get requests
if( s.type.toLowerCase() == "get" ) {
// "?" + data or "&" + data (in case there are already params)
s.url += ((s.url.indexOf("?") > -1) ? "&" : "?") + s.data;
// IE likes to send both get and post data, prevent this
s.data = null;
}
}
// Watch for a new set of requests
if ( s.global && ! jQuery.active++ )
jQuery.event.trigger( "ajaxStart" );
var requestDone = false;
// Create the request object
var xml = new XMLHttpRequest();
// Open the socket
xml.open(s.type, s.url, s.async);
// Set the correct header, if data is being sent
if ( s.data )
xml.setRequestHeader("Content-Type", s.contentType);
// Set the If-Modified-Since header, if ifModified mode.
if ( s.ifModified )
xml.setRequestHeader("If-Modified-Since",
jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" );
// Set header so the called script knows that it's an XMLHttpRequest
xml.setRequestHeader("X-Requested-With", "XMLHttpRequest");
// Make sure the browser sends the right content length
if ( xml.overrideMimeType )
xml.setRequestHeader("Connection", "close");
// Allow custom headers/mimetypes
if( s.beforeSend )
s.beforeSend(xml);
if ( s.global )
jQuery.event.trigger("ajaxSend", [xml, s]);
// Wait for a response to come back
var onreadystatechange = function(isTimeout){
// The transfer is complete and the data is available, or the request timed out
if ( xml && (xml.readyState == 4 || isTimeout == "timeout") ) {
requestDone = true;
// clear poll interval
if (ival) {
clearInterval(ival);
ival = null;
}
var status;
try {
status = jQuery.httpSuccess( xml ) && isTimeout != "timeout" ?
s.ifModified && jQuery.httpNotModified( xml, s.url ) ? "notmodified" : "success" : "error";
// Make sure that the request was successful or notmodified
if ( status != "error" ) {
// Cache Last-Modified header, if ifModified mode.
var modRes;
try {
( run in 3.209 seconds using v1.01-cache-2.11-cpan-ceb78f64989 )